diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml new file mode 100644 index 0000000..4277990 --- /dev/null +++ b/.github/workflows/contracts.yml @@ -0,0 +1,22 @@ +name: Contract conformance +on: + pull_request: + push: + branches: [main] +permissions: + contents: read +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + matrix: + node: [22, 24] + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: ${{ matrix.node }} + cache: npm + - run: npm ci --ignore-scripts + - run: npm test diff --git a/.gitignore b/.gitignore index 29306e7..b67a42f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ dist/ build/ tmp/ .cache/ + +# Local conformance dependencies +node_modules/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 11c8dd3..242b628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ compatible, or editorial. ## Unreleased +- Added Comic Manifest 1.0.0 (new additive, unreleased family) for Studio #89: + RFC 0007, three closed record schemas, detached approval bindings, normative + identity/privacy/compatibility semantics and offline synthetic conformance. + Added pinned development test dependencies and CI for contract conformance. + Released Prompt SDK, Context Package and Context Builder bytes are unchanged. + - Added Context Builder 1.0.0 (new additive, unreleased contract family) for Studio #76: RFC 0006, closed producer schemas, deterministic selection/assembly semantics, protected lineage/public receipts and synthetic conformance cases. diff --git a/README.md b/README.md index 805bc0a..c707917 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ Stable specifications and contracts for Definitely Secure Studio systems. +The proposed [Comic Manifest v1](specs/manifests/comic-manifest-v1.md) separates +production, build-result and public release payloads with exact detached approvals. +See its [conformance fixtures](fixtures/comic-manifest-v1.md) for validation commands. + The unreleased [Context Builder v1 candidate](specs/context-builder/context-builder-v1.md) defines explicit preparation requests, source evidence, prepared results and public-safe receipt boundaries. It preserves released Context Package v1 bytes. diff --git a/fixtures/comic-manifest-v1-cases.json b/fixtures/comic-manifest-v1-cases.json new file mode 100644 index 0000000..9ebd326 --- /dev/null +++ b/fixtures/comic-manifest-v1-cases.json @@ -0,0 +1,1115 @@ +[ + { + "name": "empty panel list", + "layer": "schema", + "edits": [ + { + "path": [ + "production", + "panels" + ], + "value": [] + } + ] + }, + { + "name": "oversized title", + "layer": "schema", + "edits": [ + { + "path": [ + "production", + "title" + ], + "value": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + } + ] + }, + { + "name": "missing inputs", + "layer": "schema", + "edits": [ + { + "path": [ + "production", + "inputs" + ], + "value": null + } + ] + }, + { + "name": "nonopaque draft ID", + "layer": "schema", + "edits": [ + { + "path": [ + "production", + "production_id" + ], + "value": "draft-title" + } + ] + }, + { + "name": "zero revision", + "layer": "schema", + "edits": [ + { + "path": [ + "production", + "revision" + ], + "value": 0 + } + ] + }, + { + "name": "unknown classification", + "layer": "schema", + "edits": [ + { + "path": [ + "production", + "classification" + ], + "value": "secret" + } + ] + }, + { + "name": "public record cannot claim internal", + "layer": "schema", + "edits": [ + { + "path": [ + "release", + "classification" + ], + "value": "internal" + } + ] + }, + { + "name": "zero publication number", + "layer": "schema", + "edits": [ + { + "path": [ + "release", + "episode_id" + ], + "value": "DS-0000" + } + ] + }, + { + "name": "malformed publication number", + "layer": "schema", + "edits": [ + { + "path": [ + "release", + "episode_id" + ], + "value": "DS-1" + } + ] + }, + { + "name": "public output cannot use private handle", + "layer": "schema", + "edits": [ + { + "path": [ + "release", + "outputs", + 0, + "artifact", + "artifact_uri" + ], + "value": "urn:uuid:10000000-0000-4000-8000-000000000001" + } + ] + }, + { + "name": "public nested private field", + "layer": "schema", + "edits": [ + { + "path": [ + "release", + "private_context", + "package_id" + ], + "value": "private" + } + ] + }, + { + "name": "missing private attestation", + "layer": "schema", + "edits": [ + { + "path": [ + "release", + "private_context" + ], + "value": { + "influenced": true + } + } + ] + }, + { + "name": "false influence cannot include attestation", + "layer": "schema", + "edits": [ + { + "path": [ + "release", + "private_context" + ], + "value": { + "influenced": false, + "attestation_reference": "urn:uuid:10000000-0000-4000-8000-000000000001" + } + } + ] + }, + { + "name": "protected record link forbidden in release", + "layer": "schema", + "edits": [ + { + "path": [ + "release", + "production" + ], + "value": { + "production_id": "urn:uuid:10000000-0000-4000-8000-000000000001", + "revision": 1, + "identity": { + "canonicalization": "studio-json-v1", + "byte_size": 3359, + "sha256": "sha256:9447b652a4ab121bf5355e5faa21a224b2d6d4259d403c005580b757511d1e19" + } + } + } + ] + }, + { + "name": "digest malformed", + "layer": "schema", + "edits": [ + { + "path": [ + "result", + "outputs", + 0, + "artifact", + "sha256" + ], + "value": "sha256:abcd" + } + ] + }, + { + "name": "negative artifact size", + "layer": "schema", + "edits": [ + { + "path": [ + "result", + "outputs", + 0, + "artifact", + "byte_size" + ], + "value": -1 + } + ] + }, + { + "name": "complete with diagnostic", + "layer": "schema", + "edits": [ + { + "path": [ + "result", + "diagnostics" + ], + "value": [ + "OUTPUT_MISSING" + ] + } + ] + }, + { + "name": "failed without diagnostic", + "layer": "schema", + "edits": [ + { + "path": [ + "result", + "status" + ], + "value": "failed" + } + ] + }, + { + "name": "invalid approval date", + "layer": "schema", + "edits": [ + { + "path": [ + "approvals", + 0, + "expires_at" + ], + "value": "tomorrow" + } + ] + }, + { + "name": "unknown generation parameter shape", + "layer": "schema", + "edits": [ + { + "path": [ + "result", + "execution", + "generation" + ], + "value": [ + { + "model": "x" + } + ] + } + ] + }, + { + "name": "floating dependency version", + "layer": "schema", + "edits": [ + { + "path": [ + "production", + "inputs", + "canon", + "version" + ], + "value": "^1.0.0" + } + ] + }, + { + "name": "unknown context version", + "layer": "schema", + "edits": [ + { + "path": [ + "production", + "inputs", + "prompts", + 0, + "context", + "contract_version" + ], + "value": "2.0.0" + } + ] + }, + { + "name": "panel command forbidden", + "layer": "schema", + "edits": [ + { + "path": [ + "production", + "panels", + 0, + "command" + ], + "value": "run anything" + } + ] + }, + { + "name": "duplicate panel identity", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "panels", + 1, + "panel_id" + ], + "value": "first" + } + ], + "code": "PANEL_ID", + "rebind": true + }, + { + "name": "duplicate text identity", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "panels", + 1, + "text", + 0, + "text_id" + ], + "value": "caption-one" + } + ], + "code": "TEXT_ID", + "rebind": true + }, + { + "name": "dangling panel asset", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "panels", + 0, + "asset_ids" + ], + "value": [ + "missing" + ] + } + ], + "code": "ASSET_LINK", + "rebind": true + }, + { + "name": "dangling prompt binding", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "panels", + 0, + "prompt_bindings" + ], + "value": [ + "missing" + ] + } + ], + "code": "PROMPT_LINK", + "rebind": true + }, + { + "name": "caption with speaker", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "panels", + 0, + "text", + 0, + "speaker" + ], + "value": "synthetic" + } + ], + "code": "SPEAKER", + "rebind": true + }, + { + "name": "production classification downgrade", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "classification" + ], + "value": "public" + } + ], + "code": "CLASSIFICATION", + "rebind": true + }, + { + "name": "first revision with predecessor", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "previous" + ], + "value": { + "production_id": "urn:uuid:10000000-0000-4000-8000-000000000001", + "revision": 1, + "identity": { + "canonicalization": "studio-json-v1", + "byte_size": 3359, + "sha256": "sha256:9447b652a4ab121bf5355e5faa21a224b2d6d4259d403c005580b757511d1e19" + } + } + } + ], + "code": "REVISION", + "rebind": true + }, + { + "name": "no required rendition", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "renditions", + 0, + "required" + ], + "value": false + } + ], + "code": "REQUIRED_OUTPUT", + "rebind": true + }, + { + "name": "dimensions incompatible with text", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "renditions", + 0, + "dimensions" + ], + "value": { + "width": 1, + "height": 1 + } + } + ], + "code": "DIMENSIONS", + "rebind": true + }, + { + "name": "unexpected output", + "layer": "relational", + "edits": [ + { + "path": [ + "result", + "outputs", + 0, + "rendition_id" + ], + "value": "unknown" + } + ], + "code": "UNEXPECTED_OUTPUT", + "rebind": true + }, + { + "name": "wrong output media", + "layer": "relational", + "edits": [ + { + "path": [ + "result", + "outputs", + 0, + "artifact", + "media_type" + ], + "value": "image/png" + } + ], + "code": "MEDIA", + "rebind": true + }, + { + "name": "wrong output digest", + "layer": "relational", + "edits": [ + { + "path": [ + "result", + "outputs", + 0, + "artifact", + "sha256" + ], + "value": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "code": "OUTPUT_DIGEST", + "rebind": true + }, + { + "name": "wrong output length", + "layer": "relational", + "edits": [ + { + "path": [ + "result", + "outputs", + 0, + "artifact", + "byte_size" + ], + "value": 999 + } + ], + "code": "OUTPUT_SIZE", + "rebind": true + }, + { + "name": "wrong output rights", + "layer": "relational", + "edits": [ + { + "path": [ + "result", + "outputs", + 0, + "rights_notice" + ], + "value": "different" + } + ], + "code": "OUTPUT_REQUIREMENT", + "rebind": true + }, + { + "name": "package substitution", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "inputs", + "prompts", + 0, + "context", + "instance_id" + ], + "value": "urn:uuid:10000000-0000-4000-8000-000000000001" + } + ], + "code": "PACKAGE_LINK", + "rebind": true + }, + { + "name": "wrong context purpose", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "inputs", + "prompts", + 0, + "context", + "purpose" + ], + "value": "different" + } + ], + "code": "CONTEXT_PURPOSE", + "rebind": true + }, + { + "name": "wrong context sections", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "inputs", + "prompts", + 0, + "context", + "sections" + ], + "value": [ + "other" + ] + } + ], + "code": "CONTEXT_SECTIONS", + "rebind": true + }, + { + "name": "use grant substitution", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "inputs", + "prompts", + 0, + "context", + "use_authorization_reference" + ], + "value": "urn:uuid:10000000-0000-4000-8000-000000000001" + } + ], + "code": "USE_LINK", + "rebind": true + }, + { + "name": "future canon substituted", + "layer": "relational", + "edits": [ + { + "path": [ + "release", + "input_canon", + "commit" + ], + "value": "2222222222222222222222222222222222222222" + } + ], + "code": "CANON_INPUT", + "rebind": true + }, + { + "name": "wrong public dependencies", + "layer": "relational", + "edits": [ + { + "path": [ + "release", + "dependencies", + 0, + "commit" + ], + "value": "2222222222222222222222222222222222222222" + } + ], + "code": "DEPENDENCY_LINK", + "rebind": true + }, + { + "name": "public output tampering", + "layer": "relational", + "edits": [ + { + "path": [ + "release", + "outputs", + 0, + "artifact", + "sha256" + ], + "value": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "code": "RELEASE_OUTPUTS", + "rebind": true + }, + { + "name": "failed public gate", + "layer": "relational", + "edits": [ + { + "path": [ + "release", + "gates", + 0, + "disposition" + ], + "value": "fail" + } + ], + "code": "GATE_FAILED", + "rebind": true + }, + { + "name": "inconclusive public gate", + "layer": "relational", + "edits": [ + { + "path": [ + "release", + "gates", + 0, + "disposition" + ], + "value": "inconclusive" + } + ], + "code": "GATE_FAILED", + "rebind": true + }, + { + "name": "unreviewed not-applicable gate", + "layer": "relational", + "edits": [ + { + "path": [ + "release", + "gates", + 0, + "disposition" + ], + "value": "not-applicable" + } + ], + "code": "GATE_NA", + "rebind": true + }, + { + "name": "duplicate public gate", + "layer": "relational", + "edits": [ + { + "path": [ + "release", + "gates", + 1, + "gate" + ], + "value": "editorial" + } + ], + "code": "GATES", + "rebind": true + }, + { + "name": "false private influence", + "layer": "relational", + "edits": [ + { + "path": [ + "release", + "private_context" + ], + "value": { + "influenced": false + } + } + ], + "code": "PRIVATE_INFLUENCE", + "rebind": true + }, + { + "name": "unassigned episode", + "layer": "relational", + "edits": [ + { + "path": [ + "release", + "episode_id" + ], + "value": "DS-0002" + } + ], + "code": "ASSIGNMENT", + "rebind": true + }, + { + "name": "public private prose sentinel", + "layer": "relational", + "edits": [ + { + "path": [ + "release", + "canon_scope" + ], + "value": "SYNTHETIC-PROTECTED-SENTINEL" + } + ], + "code": "PRIVATE_LEAK", + "rebind": true + }, + { + "name": "public private hash in URL", + "layer": "relational", + "edits": [ + { + "path": [ + "release", + "outputs", + 0, + "artifact", + "artifact_uri" + ], + "value": "https://example.invalid/sha256:a287bcbe601550c2a4df68b296c64d622591fc91aa5713e8ea6e2e6119f04eab" + } + ], + "code": "PRIVATE_LEAK", + "rebind": true + }, + { + "name": "floating input tag", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "inputs", + "canon", + "tag" + ], + "value": "main" + } + ], + "code": "FLOATING_REFERENCE", + "rebind": true + }, + { + "name": "dependency digest mismatch", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "inputs", + "canon", + "artifact", + "sha256" + ], + "value": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "code": "DEPENDENCY_DIGEST", + "rebind": true + }, + { + "name": "backwards execution time", + "layer": "relational", + "edits": [ + { + "path": [ + "result", + "execution", + "finished_at" + ], + "value": "2026-09-14T00:00:00Z" + } + ], + "code": "TIME", + "rebind": true + }, + { + "name": "changed panels invalidate prior result", + "layer": "relational", + "edits": [ + { + "path": [ + "production", + "panels" + ], + "value": [ + { + "panel_id": "second", + "description": "A synthetic red sphere.", + "text": [ + { + "text_id": "caption-two", + "kind": "caption", + "speaker": null, + "text": "A sphere." + } + ], + "asset_ids": [], + "prompt_bindings": [] + }, + { + "panel_id": "first", + "description": "A synthetic blue cube.", + "text": [ + { + "text_id": "caption-one", + "kind": "caption", + "speaker": null, + "text": "A cube." + } + ], + "asset_ids": [], + "prompt_bindings": [ + "description" + ] + } + ] + } + ], + "code": "PRODUCTION_LINK", + "rebind": false + }, + { + "name": "wrong result input revision", + "layer": "relational", + "edits": [ + { + "path": [ + "result", + "production", + "revision" + ], + "value": 2 + } + ], + "code": "PRODUCTION_LINK", + "rebind": false + }, + { + "name": "wrong actual inputs", + "layer": "relational", + "edits": [ + { + "path": [ + "result", + "inputs", + "canon", + "commit" + ], + "value": "2222222222222222222222222222222222222222" + } + ], + "code": "INPUT_LINK", + "rebind": false + }, + { + "name": "approval bound to other bytes", + "layer": "relational", + "edits": [ + { + "path": [ + "approvals", + 0, + "subject", + "sha256" + ], + "value": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "code": "APPROVAL_SUBJECT", + "rebind": false + }, + { + "name": "approval changed audience", + "layer": "relational", + "edits": [ + { + "path": [ + "approvals", + 0, + "scope", + "audience" + ], + "value": "everyone" + } + ], + "code": "APPROVAL_SCOPE", + "rebind": false + }, + { + "name": "expired approval", + "layer": "relational", + "edits": [ + { + "path": [ + "at" + ], + "value": "2026-09-16T00:00:00Z" + } + ], + "code": "APPROVAL_TIME", + "rebind": false + }, + { + "name": "revoked approval", + "layer": "relational", + "edits": [ + { + "path": [ + "trust", + "revoked" + ], + "value": [ + "urn:uuid:10000000-0000-4000-8000-000000000199" + ] + } + ], + "code": "APPROVAL_TRUST", + "rebind": false + }, + { + "name": "untrusted approval issuer", + "layer": "relational", + "edits": [ + { + "path": [ + "approvals", + 0, + "authority_reference" + ], + "value": "urn:uuid:10000000-0000-4000-8000-000000000001" + } + ], + "code": "APPROVAL_TRUST", + "rebind": false + }, + { + "name": "missing approval trust", + "layer": "relational", + "edits": [ + { + "path": [ + "trust", + "approvals" + ], + "value": [] + } + ], + "code": "APPROVAL_TRUST", + "rebind": false + }, + { + "name": "missing attestation trust", + "layer": "relational", + "edits": [ + { + "path": [ + "trust", + "attestation" + ], + "value": null + } + ], + "code": "ATTESTATION_TRUST", + "rebind": false + }, + { + "name": "attestation mapped to other result", + "layer": "relational", + "edits": [ + { + "path": [ + "linkage", + "result_identity", + "sha256" + ], + "value": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "code": "LINEAGE", + "rebind": false + } +] diff --git a/fixtures/comic-manifest-v1.json b/fixtures/comic-manifest-v1.json new file mode 100644 index 0000000..2f03519 --- /dev/null +++ b/fixtures/comic-manifest-v1.json @@ -0,0 +1,671 @@ +{ + "production": { + "kind": "comic-production", + "spec_version": "1.0.0", + "production_id": "urn:uuid:10000000-0000-4000-8000-000000000001", + "revision": 1, + "previous": null, + "classification": "internal", + "title": "Synthetic Shapes", + "intent": "Offline conformance only; no Studio canon.", + "inputs": { + "canon": { + "repository": "synthetic/canon", + "version": "1.0.0", + "tag": "canon/v1.0.0", + "commit": "1111111111111111111111111111111111111111", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/canon", + "media_type": "application/json", + "byte_size": 18, + "sha256": "sha256:a8198524f58e72b56283ab71ebddada22840f108b46ef7165bb3fca9919c5558" + } + }, + "dependencies": [ + { + "repository": "synthetic/mock-tool", + "version": "1.0.0", + "tag": "mock-tool/v1.0.0", + "commit": "1111111111111111111111111111111111111111", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/mock-tool", + "media_type": "application/json", + "byte_size": 22, + "sha256": "sha256:b5b94381f12f81d18bc1fc6fed0b04254162f21361e4d999ab356cc14cc464cf" + } + }, + { + "repository": "synthetic/comic-manifest", + "version": "1.0.0", + "tag": "comic-manifest/v1.0.0", + "commit": "1111111111111111111111111111111111111111", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/comic-manifest", + "media_type": "application/json", + "byte_size": 29, + "sha256": "sha256:836b922aa06e1e4759a9ee86412b96b8502bfe992092a10bfc08d97e390ffa47" + } + } + ], + "prompts": [ + { + "binding_id": "description", + "definition": { + "repository": "synthetic/prompt", + "version": "1.0.0", + "tag": "prompt/v1.0.0", + "commit": "1111111111111111111111111111111111111111", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/prompt", + "media_type": "application/json", + "byte_size": 2629, + "sha256": "sha256:7d127a76b06a3db563f2935dc0f0d03efa2fcb5d8fe4a13492860497ee6b9064" + } + }, + "prompt_id": "prompt.reference.context-builder", + "prompt_version": "1.0.0", + "identity": { + "canonicalization": "studio-json-v1", + "byte_size": 2629, + "sha256": "sha256:7d127a76b06a3db563f2935dc0f0d03efa2fcb5d8fe4a13492860497ee6b9064" + }, + "context": { + "contract_version": "1.0.0", + "package_id": "context-package.reference.builder", + "package_version": "1.0.0", + "instance_id": "urn:uuid:00000000-0000-4000-8000-000000000064", + "manifest_identity": { + "canonicalization": "studio-json-v1", + "byte_size": 1769, + "sha256": "sha256:a287bcbe601550c2a4df68b296c64d622591fc91aa5713e8ea6e2e6119f04eab" + }, + "classification": "internal", + "purpose": "Prepare synthetic context for contract conformance.", + "sections": [ + "approved_notes", + "context_facts" + ], + "builder_result_identity": { + "canonicalization": "studio-json-v1", + "byte_size": 2848, + "sha256": "sha256:729ce7ad380821185cecb4166e6bdd681048097450aa9256505f18c3dafa48b4" + }, + "preparation_reference": "urn:uuid:00000000-0000-4000-8000-000000000063", + "use_authorization_reference": "urn:uuid:00000000-0000-4000-8000-000000000071" + } + } + ], + "assets": [] + }, + "panels": [ + { + "panel_id": "first", + "description": "A synthetic blue cube.", + "text": [ + { + "text_id": "caption-one", + "kind": "caption", + "speaker": null, + "text": "A cube." + } + ], + "asset_ids": [], + "prompt_bindings": [ + "description" + ] + }, + { + "panel_id": "second", + "description": "A synthetic red sphere.", + "text": [ + { + "text_id": "caption-two", + "kind": "caption", + "speaker": null, + "text": "A sphere." + } + ], + "asset_ids": [], + "prompt_bindings": [] + } + ], + "renditions": [ + { + "rendition_id": "transcript", + "required": true, + "media_type": "text/plain", + "dimensions": null, + "alt_text": "A blue cube, followed by a red sphere.", + "transcript": "Panel 1: A cube.\nPanel 2: A sphere.", + "rights_notice": "Synthetic conformance fixture; Apache-2.0." + } + ] + }, + "result": { + "kind": "comic-build-result", + "spec_version": "1.0.0", + "result_id": "urn:uuid:10000000-0000-4000-8000-000000000002", + "attempt_id": "urn:uuid:10000000-0000-4000-8000-000000000003", + "production": { + "production_id": "urn:uuid:10000000-0000-4000-8000-000000000001", + "revision": 1, + "identity": { + "canonicalization": "studio-json-v1", + "byte_size": 3359, + "sha256": "sha256:9447b652a4ab121bf5355e5faa21a224b2d6d4259d403c005580b757511d1e19" + } + }, + "classification": "internal", + "inputs": { + "canon": { + "repository": "synthetic/canon", + "version": "1.0.0", + "tag": "canon/v1.0.0", + "commit": "1111111111111111111111111111111111111111", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/canon", + "media_type": "application/json", + "byte_size": 18, + "sha256": "sha256:a8198524f58e72b56283ab71ebddada22840f108b46ef7165bb3fca9919c5558" + } + }, + "dependencies": [ + { + "repository": "synthetic/mock-tool", + "version": "1.0.0", + "tag": "mock-tool/v1.0.0", + "commit": "1111111111111111111111111111111111111111", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/mock-tool", + "media_type": "application/json", + "byte_size": 22, + "sha256": "sha256:b5b94381f12f81d18bc1fc6fed0b04254162f21361e4d999ab356cc14cc464cf" + } + }, + { + "repository": "synthetic/comic-manifest", + "version": "1.0.0", + "tag": "comic-manifest/v1.0.0", + "commit": "1111111111111111111111111111111111111111", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/comic-manifest", + "media_type": "application/json", + "byte_size": 29, + "sha256": "sha256:836b922aa06e1e4759a9ee86412b96b8502bfe992092a10bfc08d97e390ffa47" + } + } + ], + "prompts": [ + { + "binding_id": "description", + "definition": { + "repository": "synthetic/prompt", + "version": "1.0.0", + "tag": "prompt/v1.0.0", + "commit": "1111111111111111111111111111111111111111", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/prompt", + "media_type": "application/json", + "byte_size": 2629, + "sha256": "sha256:7d127a76b06a3db563f2935dc0f0d03efa2fcb5d8fe4a13492860497ee6b9064" + } + }, + "prompt_id": "prompt.reference.context-builder", + "prompt_version": "1.0.0", + "identity": { + "canonicalization": "studio-json-v1", + "byte_size": 2629, + "sha256": "sha256:7d127a76b06a3db563f2935dc0f0d03efa2fcb5d8fe4a13492860497ee6b9064" + }, + "context": { + "contract_version": "1.0.0", + "package_id": "context-package.reference.builder", + "package_version": "1.0.0", + "instance_id": "urn:uuid:00000000-0000-4000-8000-000000000064", + "manifest_identity": { + "canonicalization": "studio-json-v1", + "byte_size": 1769, + "sha256": "sha256:a287bcbe601550c2a4df68b296c64d622591fc91aa5713e8ea6e2e6119f04eab" + }, + "classification": "internal", + "purpose": "Prepare synthetic context for contract conformance.", + "sections": [ + "approved_notes", + "context_facts" + ], + "builder_result_identity": { + "canonicalization": "studio-json-v1", + "byte_size": 2848, + "sha256": "sha256:729ce7ad380821185cecb4166e6bdd681048097450aa9256505f18c3dafa48b4" + }, + "preparation_reference": "urn:uuid:00000000-0000-4000-8000-000000000063", + "use_authorization_reference": "urn:uuid:00000000-0000-4000-8000-000000000071" + } + } + ], + "assets": [] + }, + "status": "complete", + "outputs": [ + { + "rendition_id": "transcript", + "artifact": { + "artifact_uri": "urn:uuid:10000000-0000-4000-8000-000000000010", + "media_type": "text/plain", + "byte_size": 36, + "sha256": "sha256:5aa128a0dd0945b00ba3d71a5f7b5916e06f40c53048c31d74432eb03f4e764d" + }, + "dimensions": null, + "rights_notice": "Synthetic conformance fixture; Apache-2.0.", + "alt_text": "A blue cube, followed by a red sphere.", + "transcript": "Panel 1: A cube.\nPanel 2: A sphere." + } + ], + "gates": [ + { + "gate": "integrity", + "disposition": "pass", + "evidence_reference": "urn:uuid:10000000-0000-4000-8000-000000000020", + "rationale": "Synthetic bytes verified." + } + ], + "execution": { + "tool": { + "repository": "synthetic/mock-tool", + "version": "1.0.0", + "tag": "mock-tool/v1.0.0", + "commit": "1111111111111111111111111111111111111111", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/mock-tool", + "media_type": "application/json", + "byte_size": 22, + "sha256": "sha256:b5b94381f12f81d18bc1fc6fed0b04254162f21361e4d999ab356cc14cc464cf" + } + }, + "workflow_id": "synthetic-offline", + "run_uri": null, + "started_at": "2026-09-15T12:00:00Z", + "finished_at": "2026-09-15T12:00:01Z", + "transformations": [ + { + "step_id": "mock", + "input_digests": [ + "sha256:a8198524f58e72b56283ab71ebddada22840f108b46ef7165bb3fca9919c5558", + "sha256:9447b652a4ab121bf5355e5faa21a224b2d6d4259d403c005580b757511d1e19" + ], + "output_digests": [ + "sha256:5aa128a0dd0945b00ba3d71a5f7b5916e06f40c53048c31d74432eb03f4e764d" + ], + "private_inputs_withheld": false, + "private_outputs_withheld": false + } + ], + "generation": [], + "reproducibility": "exact", + "limitations": "Fixed synthetic text only; no external generation." + }, + "diagnostics": [] + }, + "release": { + "kind": "comic-public-release", + "spec_version": "1.0.0", + "release_id": "urn:uuid:10000000-0000-4000-8000-000000000004", + "episode_id": "DS-0001", + "revision": 1, + "previous": null, + "classification": "public", + "title": "Synthetic Shapes", + "production_credit": "A Definitely Secure Studio production.", + "canon_scope": "None; synthetic test identity does not reserve a real publication number.", + "scope": { + "destination": "https://example.invalid/synthetic/archive", + "audience": "synthetic-test", + "purpose": "Offline conformance only.", + "publication_time": "2026-09-15T12:10:00Z" + }, + "input_canon": { + "repository": "synthetic/canon", + "version": "1.0.0", + "tag": "canon/v1.0.0", + "commit": "1111111111111111111111111111111111111111", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/canon", + "media_type": "application/json", + "byte_size": 18, + "sha256": "sha256:a8198524f58e72b56283ab71ebddada22840f108b46ef7165bb3fca9919c5558" + } + }, + "dependencies": [ + { + "repository": "synthetic/mock-tool", + "version": "1.0.0", + "tag": "mock-tool/v1.0.0", + "commit": "1111111111111111111111111111111111111111", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/mock-tool", + "media_type": "application/json", + "byte_size": 22, + "sha256": "sha256:b5b94381f12f81d18bc1fc6fed0b04254162f21361e4d999ab356cc14cc464cf" + } + }, + { + "repository": "synthetic/comic-manifest", + "version": "1.0.0", + "tag": "comic-manifest/v1.0.0", + "commit": "1111111111111111111111111111111111111111", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/comic-manifest", + "media_type": "application/json", + "byte_size": 29, + "sha256": "sha256:836b922aa06e1e4759a9ee86412b96b8502bfe992092a10bfc08d97e390ffa47" + } + }, + { + "repository": "synthetic/prompt", + "version": "1.0.0", + "tag": "prompt/v1.0.0", + "commit": "1111111111111111111111111111111111111111", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/prompt", + "media_type": "application/json", + "byte_size": 2629, + "sha256": "sha256:7d127a76b06a3db563f2935dc0f0d03efa2fcb5d8fe4a13492860497ee6b9064" + } + } + ], + "outputs": [ + { + "rendition_id": "transcript", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/transcript.txt", + "media_type": "text/plain", + "byte_size": 36, + "sha256": "sha256:5aa128a0dd0945b00ba3d71a5f7b5916e06f40c53048c31d74432eb03f4e764d" + }, + "dimensions": null, + "rights_notice": "Synthetic conformance fixture; Apache-2.0.", + "alt_text": "A blue cube, followed by a red sphere.", + "transcript": "Panel 1: A cube.\nPanel 2: A sphere." + } + ], + "gates": [ + { + "gate": "editorial", + "disposition": "pass", + "evidence_reference": "urn:uuid:10000000-0000-4000-8000-000000000100", + "rationale": "Synthetic reviewed fixture only." + }, + { + "gate": "canon-continuity", + "disposition": "pass", + "evidence_reference": "urn:uuid:10000000-0000-4000-8000-000000000101", + "rationale": "Synthetic reviewed fixture only." + }, + { + "gate": "visual-text", + "disposition": "pass", + "evidence_reference": "urn:uuid:10000000-0000-4000-8000-000000000102", + "rationale": "Synthetic reviewed fixture only." + }, + { + "gate": "integrity", + "disposition": "pass", + "evidence_reference": "urn:uuid:10000000-0000-4000-8000-000000000103", + "rationale": "Synthetic reviewed fixture only." + }, + { + "gate": "provenance", + "disposition": "pass", + "evidence_reference": "urn:uuid:10000000-0000-4000-8000-000000000104", + "rationale": "Synthetic reviewed fixture only." + }, + { + "gate": "security-privacy", + "disposition": "pass", + "evidence_reference": "urn:uuid:10000000-0000-4000-8000-000000000105", + "rationale": "Synthetic reviewed fixture only." + }, + { + "gate": "rights", + "disposition": "pass", + "evidence_reference": "urn:uuid:10000000-0000-4000-8000-000000000106", + "rationale": "Synthetic reviewed fixture only." + }, + { + "gate": "accessibility", + "disposition": "pass", + "evidence_reference": "urn:uuid:10000000-0000-4000-8000-000000000107", + "rationale": "Synthetic reviewed fixture only." + }, + { + "gate": "packaging", + "disposition": "pass", + "evidence_reference": "urn:uuid:10000000-0000-4000-8000-000000000108", + "rationale": "Synthetic reviewed fixture only." + } + ], + "execution": { + "tool": { + "repository": "synthetic/mock-tool", + "version": "1.0.0", + "tag": "mock-tool/v1.0.0", + "commit": "1111111111111111111111111111111111111111", + "artifact": { + "artifact_uri": "https://example.invalid/synthetic/mock-tool", + "media_type": "application/json", + "byte_size": 22, + "sha256": "sha256:b5b94381f12f81d18bc1fc6fed0b04254162f21361e4d999ab356cc14cc464cf" + } + }, + "workflow_id": "synthetic-offline", + "run_uri": null, + "started_at": "2026-09-15T12:00:00Z", + "finished_at": "2026-09-15T12:00:01Z", + "transformations": [ + { + "step_id": "mock", + "input_digests": [ + "sha256:a8198524f58e72b56283ab71ebddada22840f108b46ef7165bb3fca9919c5558" + ], + "output_digests": [ + "sha256:5aa128a0dd0945b00ba3d71a5f7b5916e06f40c53048c31d74432eb03f4e764d" + ], + "private_inputs_withheld": true, + "private_outputs_withheld": false + } + ], + "generation": [], + "reproducibility": "exact", + "limitations": "Fixed synthetic text only; no external generation." + }, + "approvers": [ + { + "decision_id": "urn:uuid:10000000-0000-4000-8000-000000000200", + "role": "publisher", + "actor": "synthetic-publisher", + "decided_at": "2026-09-15T12:02:00Z" + }, + { + "decision_id": "urn:uuid:10000000-0000-4000-8000-000000000201", + "role": "canon-editor", + "actor": "synthetic-canon-editor", + "decided_at": "2026-09-15T12:02:00Z" + }, + { + "decision_id": "urn:uuid:10000000-0000-4000-8000-000000000202", + "role": "disclosure-reviewer", + "actor": "synthetic-disclosure-reviewer", + "decided_at": "2026-09-15T12:02:00Z" + } + ], + "private_context": { + "influenced": true, + "attestation_reference": "urn:uuid:10000000-0000-4000-8000-000000000300" + } + }, + "approvals": [ + { + "kind": "comic-approval-binding", + "spec_version": "1.0.0", + "decision_id": "urn:uuid:10000000-0000-4000-8000-000000000199", + "role": "production-reviewer", + "actor": "synthetic-production-reviewer", + "decided_at": "2026-09-15T12:02:00Z", + "subject": { + "canonicalization": "studio-json-v1", + "byte_size": 3359, + "sha256": "sha256:9447b652a4ab121bf5355e5faa21a224b2d6d4259d403c005580b757511d1e19" + }, + "artifact_digests": [], + "scope": { + "destination": "https://example.invalid/synthetic/archive", + "audience": "synthetic-test", + "purpose": "Offline conformance only.", + "publication_time": "2026-09-15T12:10:00Z" + }, + "expires_at": "2026-09-16T00:00:00Z", + "authority_reference": "urn:uuid:10000000-0000-4000-8000-000000000400" + }, + { + "kind": "comic-approval-binding", + "spec_version": "1.0.0", + "decision_id": "urn:uuid:10000000-0000-4000-8000-000000000200", + "role": "publisher", + "actor": "synthetic-publisher", + "decided_at": "2026-09-15T12:02:00Z", + "subject": { + "canonicalization": "studio-json-v1", + "byte_size": 5269, + "sha256": "sha256:1d2b5e1bfc49f40ceea1662b04d7380735b82f730f55a61cc71f5a6696b750c2" + }, + "artifact_digests": [ + "sha256:5aa128a0dd0945b00ba3d71a5f7b5916e06f40c53048c31d74432eb03f4e764d" + ], + "scope": { + "destination": "https://example.invalid/synthetic/archive", + "audience": "synthetic-test", + "purpose": "Offline conformance only.", + "publication_time": "2026-09-15T12:10:00Z" + }, + "expires_at": "2026-09-16T00:00:00Z", + "authority_reference": "urn:uuid:10000000-0000-4000-8000-000000000400" + }, + { + "kind": "comic-approval-binding", + "spec_version": "1.0.0", + "decision_id": "urn:uuid:10000000-0000-4000-8000-000000000201", + "role": "canon-editor", + "actor": "synthetic-canon-editor", + "decided_at": "2026-09-15T12:02:00Z", + "subject": { + "canonicalization": "studio-json-v1", + "byte_size": 5269, + "sha256": "sha256:1d2b5e1bfc49f40ceea1662b04d7380735b82f730f55a61cc71f5a6696b750c2" + }, + "artifact_digests": [ + "sha256:5aa128a0dd0945b00ba3d71a5f7b5916e06f40c53048c31d74432eb03f4e764d" + ], + "scope": { + "destination": "https://example.invalid/synthetic/archive", + "audience": "synthetic-test", + "purpose": "Offline conformance only.", + "publication_time": "2026-09-15T12:10:00Z" + }, + "expires_at": "2026-09-16T00:00:00Z", + "authority_reference": "urn:uuid:10000000-0000-4000-8000-000000000400" + }, + { + "kind": "comic-approval-binding", + "spec_version": "1.0.0", + "decision_id": "urn:uuid:10000000-0000-4000-8000-000000000202", + "role": "disclosure-reviewer", + "actor": "synthetic-disclosure-reviewer", + "decided_at": "2026-09-15T12:02:00Z", + "subject": { + "canonicalization": "studio-json-v1", + "byte_size": 5269, + "sha256": "sha256:1d2b5e1bfc49f40ceea1662b04d7380735b82f730f55a61cc71f5a6696b750c2" + }, + "artifact_digests": [ + "sha256:5aa128a0dd0945b00ba3d71a5f7b5916e06f40c53048c31d74432eb03f4e764d" + ], + "scope": { + "destination": "https://example.invalid/synthetic/archive", + "audience": "synthetic-test", + "purpose": "Offline conformance only.", + "publication_time": "2026-09-15T12:10:00Z" + }, + "expires_at": "2026-09-16T00:00:00Z", + "authority_reference": "urn:uuid:10000000-0000-4000-8000-000000000400" + } + ], + "linkage": { + "production": { + "production_id": "urn:uuid:10000000-0000-4000-8000-000000000001", + "revision": 1, + "identity": { + "canonicalization": "studio-json-v1", + "byte_size": 3359, + "sha256": "sha256:9447b652a4ab121bf5355e5faa21a224b2d6d4259d403c005580b757511d1e19" + } + }, + "result_identity": { + "canonicalization": "studio-json-v1", + "byte_size": 4465, + "sha256": "sha256:871cb595c8d3b44d6245fd9c86daf9aa9185b76081f918b5a44c4568062f1dcf" + }, + "release_identity": { + "canonicalization": "studio-json-v1", + "byte_size": 5269, + "sha256": "sha256:1d2b5e1bfc49f40ceea1662b04d7380735b82f730f55a61cc71f5a6696b750c2" + }, + "attestation_reference": "urn:uuid:10000000-0000-4000-8000-000000000300" + }, + "at": "2026-09-15T12:10:00Z", + "output_bytes": { + "transcript": "Panel 1: A cube.\nPanel 2: A sphere.\n" + }, + "dependency_bytes": { + "https://example.invalid/synthetic/canon": "{\"synthetic\":true}", + "https://example.invalid/synthetic/mock-tool": "synthetic offline tool", + "https://example.invalid/synthetic/comic-manifest": "synthetic contract descriptor", + "https://example.invalid/synthetic/prompt": "{\"capabilities\":{\"optional\":[],\"required\":[\"text-generation\"]},\"context_slots\":[{\"accepted_classifications\":[\"public\",\"internal\"],\"accepted_media_types\":[\"text/plain\"],\"description\":\"Explicit synthetic notes.\",\"max_bytes\":100,\"name\":\"approved_notes\",\"required\":true},{\"accepted_classifications\":[\"internal\"],\"accepted_media_types\":[\"application/json\"],\"description\":\"Synthetic facts.\",\"max_bytes\":100,\"name\":\"context_facts\",\"required\":true},{\"accepted_classifications\":[\"public\"],\"accepted_media_types\":[\"text/plain\"],\"description\":\"Optional synthetic footer.\",\"max_bytes\":100,\"name\":\"optional_footer\",\"required\":false}],\"description\":\"Synthetic renderer fixture for an explicitly supplied public item.\",\"governance\":{\"constitution\":{\"commit\":\"a9cc8a503aa30e17820edc62ac95f7cbe10e0564\",\"tag\":\"constitution/v1.0.0\",\"version\":\"1.0.0\"},\"decision_owner\":\"@andrewperis\",\"evidence\":[\"https://github.com/DefinitelySecureStudio/studio/issues/63\"]},\"id\":\"prompt.reference.context-builder\",\"inputs\":[{\"classification\":\"public\",\"constraints\":{\"min_length\":1},\"description\":\"Synthetic public item name.\",\"name\":\"item\",\"required\":true,\"type\":\"string\"},{\"classification\":\"public\",\"description\":\"Synthetic attributes.\",\"name\":\"attributes\",\"required\":true,\"type\":\"object\"},{\"classification\":\"public\",\"default\":true,\"description\":\"Request concise output.\",\"name\":\"concise\",\"required\":false,\"type\":\"boolean\"}],\"lifecycle\":{\"status\":\"draft\"},\"name\":\"Describe a public item\",\"output\":{\"description\":\"A synthetic plain-text description for human review.\",\"kind\":\"text\",\"media_type\":\"text/plain\"},\"owners\":[\"@andrewperis\"],\"provenance\":{\"created_at\":\"2026-08-17T00:00:00Z\",\"created_by\":\"@andrewperis\",\"origin\":\"studio-original\",\"rights\":{\"basis\":\"studio-original\",\"notices\":[],\"reviewed_at\":\"2026-08-17T00:00:00Z\",\"reviewed_by\":\"@andrewperis\"},\"source_references\":[]},\"purpose\":\"Exercise provider-neutral rendering; it does not retrieve data, approve output, or establish Canon.\",\"spec_version\":\"1.0.0\",\"template\":{\"format\":\"studio-messages-v1\",\"messages\":[{\"parts\":[{\"text\":\"Describe the explicit item using only supplied inputs and context.\",\"type\":\"text\"}],\"role\":\"instruction\"},{\"parts\":[{\"text\":\"Item: \",\"type\":\"text\"},{\"name\":\"item\",\"type\":\"input\"},{\"text\":\"\\nAttributes: \",\"type\":\"text\"},{\"format\":\"json\",\"name\":\"attributes\",\"type\":\"input\"},{\"text\":\"\\nConcise: \",\"type\":\"text\"},{\"format\":\"text\",\"name\":\"concise\",\"type\":\"input\"},{\"text\":\"\\nNotes: \",\"type\":\"text\"},{\"slot\":\"approved_notes\",\"type\":\"context\"},{\"format\":\"json\",\"slot\":\"context_facts\",\"type\":\"context\"},{\"slot\":\"optional_footer\",\"type\":\"context\"}],\"role\":\"user\"}]},\"version\":\"1.0.0\"}" + }, + "protected_sentinels": [ + "SYNTHETIC-PROTECTED-SENTINEL" + ], + "trust": { + "approvals": [ + { + "decision_id": "urn:uuid:10000000-0000-4000-8000-000000000199", + "sha256": "sha256:21cf0df439e7d871bb1af47e40b9a8b65e87c3d0e0d1b347612cc5a6c3ec09d5" + }, + { + "decision_id": "urn:uuid:10000000-0000-4000-8000-000000000200", + "sha256": "sha256:9b08c55b7da322ba912971c7a04c66518b62972acfc9a01e76e52c765f397603" + }, + { + "decision_id": "urn:uuid:10000000-0000-4000-8000-000000000201", + "sha256": "sha256:ec2b4192b3c31f50be3ec5628a97d181090530d30baa1fe56c5a4295c67740c8" + }, + { + "decision_id": "urn:uuid:10000000-0000-4000-8000-000000000202", + "sha256": "sha256:310efb68d2464edebedc0505456c402e04770c26e3f782c1d89104a7b2fffc94" + } + ], + "revoked": [], + "not_applicable": [], + "assignment": { + "production_id": "urn:uuid:10000000-0000-4000-8000-000000000001", + "episode_id": "DS-0001" + }, + "attestation": { + "reference": "urn:uuid:10000000-0000-4000-8000-000000000300", + "linkage_identity": { + "canonicalization": "studio-json-v1", + "byte_size": 627, + "sha256": "sha256:0fa52c096dcc5e041979fd5887bf0938abc0a486b4b659c19b6414ae43b43e0b" + } + } + } +} diff --git a/fixtures/comic-manifest-v1.md b/fixtures/comic-manifest-v1.md new file mode 100644 index 0000000..d516ff5 --- /dev/null +++ b/fixtures/comic-manifest-v1.md @@ -0,0 +1,37 @@ +# Comic Manifest synthetic conformance fixtures + +[Base scenario](comic-manifest-v1.json) contains production, result, release and +detached approval payloads, plus **non-normative** test harness byte maps, restricted +linkage, trust and explicit evaluation time. It references the unchanged +[Context Builder fixture](context-builder-v1.json). Every character, authority, +source, attestation, URL, commit and publication number is synthetic; no network +access or real canon/private material is required or permitted. Fixed UUIDs model +shape only. Fake public dependency tuples are not release assets. + +[Negative overlays](comic-manifest-v1-cases.json) are machine-readable fixtures: +clone the base, apply each `edits` path in order, then check the declared `layer`. +Schema cases fail before relationships. Relational cases MUST remain schema-valid +and fail with the declared invariant code. `rebind: true` requests fresh synthetic +hashes and explicit fake decisions to isolate the semantic error; it never means +real edits preserve or renew approvals. `rebind: false` retains historical evidence +to test invalidation. See the [test-only oracle](../tests/support/comic-manifest-oracle.cjs). + +The suite includes protected-context and authored public-only success, append-only +correction, partial/failed evidence, deterministic identity, unknown versions/fields, +ordered identities, outputs, immutable references, package bindings, stale/revoked/ +forged approvals, public gates and private-context projection. Output text bytes and +public dependency bytes are explicit fixture inputs and independently hashed. +Tests never create an approval from a production runtime or execute a provider. + +Run `npm ci --ignore-scripts`, then `npm run test:comic`. Run `npm test` from a clean +committed tree to include existing release-bundle reproducibility tests (the release +builder intentionally refuses dirty trees). Node >=22; dependencies are pinned in +the lockfile. Neither command needs network after dependency installation. + +Coverage limits are explicit: the oracle exercises fixture relationships, not a +hardened parser, production trust verifier, media decoder, issuer, secure store or +public projection implementation. Current package use is tested by the companion +Platform proof using the released SDK. Unicode/duplicate-key/size/depth raw parsing, +real revocation adapters, denied-read guarantees, free-text disclosure and artifact +rights need their later runtime and qualified human checks. Passing fixtures alone +never establishes permission, safe publication or creative canon. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..995df94 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,95 @@ +{ + "name": "@definitely-secure-studio/codex-conformance", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@definitely-secure-studio/codex-conformance", + "version": "0.0.0", + "devDependencies": { + "ajv": "8.20.0", + "ajv-formats": "3.0.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1faa86a --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "@definitely-secure-studio/codex-conformance", + "version": "0.0.0", + "private": true, + "engines": { "node": ">=22" }, + "scripts": { + "test": "node --test tests/*.test.cjs tests/*.test.mjs", + "test:comic": "node --test tests/comic-manifest.test.cjs" + }, + "devDependencies": { "ajv": "8.20.0", "ajv-formats": "3.0.1" } +} diff --git a/rfcs/0007-comic-manifest-v1.md b/rfcs/0007-comic-manifest-v1.md new file mode 100644 index 0000000..4e7cf17 --- /dev/null +++ b/rfcs/0007-comic-manifest-v1.md @@ -0,0 +1,65 @@ +# RFC 0007: Comic Manifest v1 contracts + +Status: Proposed; acceptance requires owner merge and coordinated Platform review. +Owner: @andrewperis. Issue: https://github.com/DefinitelySecureStudio/studio/issues/89 +Constitution: v1.0.0, `constitution/v1.0.0`, commit +`a9cc8a503aa30e17820edc62ac95f7cbe10e0564`. + +## Decision and context + +Adopt the [Comic Manifest v1 specification](../specs/manifests/comic-manifest-v1.md) +and closed schema as a new additive, unreleased 1.0.0 contract family implementing +merged Studio ADR 0018. Production intent, observed build results and reader-safe +release candidates have separate immutable payloads and visibility. Detached +approval bindings avoid circular hashes and preserve exact scope without granting +authority from schema validity. Public private-context lineage uses approved random +attestations and protected mappings, not protected record IDs or hashes. + +Codex owns normative meaning. Platform validates and supplies a test-only consumer +proof against the existing released SDK/Builder output. Universe owns public +publication and canon; private source owners retain access/disclosure authority. +No released contract bytes change. Exact-version negotiation and unknown-field +rejection are deliberate exceptions to permissive minor-version consumption. + +## Alternatives and consequences + +One mutable record mixes intent, execution and publication and risks leaking private +lineage. A status flag or embedded signature in its own hashed payload conflates +identity with authority or creates circular identity. Permissive extension fields +make executable intent and protected-data projection harder to audit. Runtime-owned +schemas would split authority. These alternatives are rejected. + +Separate records require explicit linkage verification and detached decisions. +The first version limits rendition formats, publishes a selected result's entire +output set and leaves event/storage protocols and production trust to separately +reviewed work. Reviewers must inspect prose and output bytes; allowlists and hash +checks cannot establish disclosure safety or human authority. + +## Compatibility, verification and rollout + +See [versioning](../specs/manifests/COMIC-MANIFEST-VERSIONING.md) and +[synthetic fixtures](../fixtures/comic-manifest-v1.md). Positive/negative schema and +cross-record cases accompany the proposal; raw parser hardening, source readers, +trust/revocation, actual media inspection and orchestration are not implemented. +Platform consumes exact candidate fixture bytes with a test-only commit/digest lock; +merge Codex before the coordinated Platform consumer. Production adoption waits for +#99's immutable release, complete verified tuples and owner-reviewed conformance. +No migration of released SDK/Context Package/Builder is required. Rollback removes +the development consumer; it does not rewrite released artifacts or renew permission. + +## Scoped constitutional assessment + +Subject: this #89 candidate diff against Codex +`2301597014f6fefe8a3cf772e2e02527cda6a254`; exact revision is the adopting PR head. +Prepared 2026-09-17; accountable owner @andrewperis; required reviewers: Codex owner, +Platform maintainer, and editorial/source owners for affected release boundaries. +Status: **Not assessed** for final constitutional conformance; no self-approval. +Universal, specification and repository/production-system design profiles apply; +automated-workflow obligations inform the consumer seam. The specification's +ownership, gates, identity, provenance/privacy, rights, failure and compatibility +sections supply proposed design evidence. Qualified review against the Studio +checklist at `7b5065cef76bea9580609caba356b0d8fe7cc17c` remains required. Runtime, +creative and publication activities are excluded, so no production/canon/release +conformance is claimed. No amendment or exception is requested. Reassess on identity, +authority, classification, contract, storage or consumer-boundary changes and before +immutable publication. CODEOWNERS review records the actual acceptance decision. diff --git a/schemas/json/comic-manifest/v1/README.md b/schemas/json/comic-manifest/v1/README.md new file mode 100644 index 0000000..d3d69d2 --- /dev/null +++ b/schemas/json/comic-manifest/v1/README.md @@ -0,0 +1,11 @@ +# Comic Manifest 1.0.0 candidate schema + +[Schema](comic-manifest.schema.json): closed Draft 2020-12, standalone, no network +references. Root accepts production, build-result and public release payloads. +Use its `$defs/approval` entry point for detached approval bindings. + +[Normative semantics](../../../../specs/manifests/comic-manifest-v1.md), +[versioning](../../../../specs/manifests/COMIC-MANIFEST-VERSIONING.md) and +[fixture guide](../../../../fixtures/comic-manifest-v1.md) are required companions. +Schema validation is only the structural layer; it proves neither cross-record +integrity nor authority. Unreleased candidate; production consumption waits for #99. diff --git a/schemas/json/comic-manifest/v1/comic-manifest.schema.json b/schemas/json/comic-manifest/v1/comic-manifest.schema.json new file mode 100644 index 0000000..c340b02 --- /dev/null +++ b/schemas/json/comic-manifest/v1/comic-manifest.schema.json @@ -0,0 +1,1315 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:definitely-secure:contract:comic-manifest:1.0.0:comic-manifest", + "title": "Comic Manifest v1 candidate \u2014 three records; detached approval via $defs/approval", + "oneOf": [ + { + "$ref": "#/$defs/production" + }, + { + "$ref": "#/$defs/result" + }, + { + "$ref": "#/$defs/release" + } + ], + "$defs": { + "uuid": { + "type": "string", + "pattern": "^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + }, + "digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "name": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]{0,63}$" + }, + "time": { + "type": "string", + "format": "date-time", + "pattern": "Z$", + "maxLength": 32 + }, + "size": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "revision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991 + }, + "classification": { + "enum": [ + "public", + "internal", + "confidential", + "restricted" + ] + }, + "uri": { + "type": "string", + "format": "uri", + "pattern": "^https://", + "maxLength": 2048 + }, + "identity": { + "type": "object", + "additionalProperties": false, + "required": [ + "canonicalization", + "byte_size", + "sha256" + ], + "properties": { + "canonicalization": { + "const": "studio-json-v1" + }, + "byte_size": { + "$ref": "#/$defs/size" + }, + "sha256": { + "$ref": "#/$defs/digest" + } + } + }, + "artifact": { + "type": "object", + "additionalProperties": false, + "required": [ + "artifact_uri", + "media_type", + "byte_size", + "sha256" + ], + "properties": { + "artifact_uri": { + "$ref": "#/$defs/uri" + }, + "media_type": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "byte_size": { + "$ref": "#/$defs/size" + }, + "sha256": { + "$ref": "#/$defs/digest" + } + } + }, + "dependency": { + "type": "object", + "additionalProperties": false, + "required": [ + "repository", + "version", + "tag", + "commit", + "artifact" + ], + "properties": { + "repository": { + "type": "string", + "pattern": "^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$", + "maxLength": 200 + }, + "version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "tag": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "commit": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "artifact": { + "$ref": "#/$defs/artifact" + } + } + }, + "productionRef": { + "type": "object", + "additionalProperties": false, + "required": [ + "production_id", + "revision", + "identity" + ], + "properties": { + "production_id": { + "$ref": "#/$defs/uuid" + }, + "revision": { + "$ref": "#/$defs/revision" + }, + "identity": { + "$ref": "#/$defs/identity" + } + } + }, + "releaseRef": { + "type": "object", + "additionalProperties": false, + "required": [ + "release_id", + "episode_id", + "revision", + "identity" + ], + "properties": { + "release_id": { + "$ref": "#/$defs/uuid" + }, + "episode_id": { + "type": "string", + "pattern": "^DS-(?!0000)[0-9]{4}$" + }, + "revision": { + "$ref": "#/$defs/revision" + }, + "identity": { + "$ref": "#/$defs/identity" + } + } + }, + "context": { + "type": "object", + "additionalProperties": false, + "required": [ + "contract_version", + "package_id", + "package_version", + "instance_id", + "manifest_identity", + "classification", + "purpose", + "sections", + "builder_result_identity", + "preparation_reference", + "use_authorization_reference" + ], + "properties": { + "contract_version": { + "const": "1.0.0" + }, + "package_id": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "package_version": { + "type": "string", + "minLength": 1, + "maxLength": 32 + }, + "instance_id": { + "$ref": "#/$defs/uuid" + }, + "manifest_identity": { + "$ref": "#/$defs/identity" + }, + "classification": { + "$ref": "#/$defs/classification" + }, + "purpose": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "sections": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "minItems": 1, + "maxItems": 256 + }, + "builder_result_identity": { + "$ref": "#/$defs/identity" + }, + "preparation_reference": { + "$ref": "#/$defs/uuid" + }, + "use_authorization_reference": { + "$ref": "#/$defs/uuid" + } + } + }, + "prompt": { + "type": "object", + "additionalProperties": false, + "required": [ + "binding_id", + "definition", + "prompt_id", + "prompt_version", + "identity", + "context" + ], + "properties": { + "binding_id": { + "$ref": "#/$defs/name" + }, + "definition": { + "$ref": "#/$defs/dependency" + }, + "prompt_id": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "prompt_version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "identity": { + "$ref": "#/$defs/identity" + }, + "context": { + "anyOf": [ + { + "$ref": "#/$defs/context" + }, + { + "type": "null" + } + ] + } + } + }, + "asset": { + "type": "object", + "additionalProperties": false, + "required": [ + "asset_id", + "classification", + "reference", + "rights_notice" + ], + "properties": { + "asset_id": { + "$ref": "#/$defs/name" + }, + "classification": { + "$ref": "#/$defs/classification" + }, + "reference": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "dependency" + ], + "properties": { + "kind": { + "const": "public" + }, + "dependency": { + "$ref": "#/$defs/dependency" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "handle", + "media_type", + "byte_size", + "sha256" + ], + "properties": { + "kind": { + "const": "protected" + }, + "handle": { + "$ref": "#/$defs/uuid" + }, + "media_type": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "byte_size": { + "$ref": "#/$defs/size" + }, + "sha256": { + "$ref": "#/$defs/digest" + } + } + } + ] + }, + "rights_notice": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + } + } + }, + "inputs": { + "type": "object", + "additionalProperties": false, + "required": [ + "canon", + "dependencies", + "prompts", + "assets" + ], + "properties": { + "canon": { + "$ref": "#/$defs/dependency" + }, + "dependencies": { + "type": "array", + "items": { + "$ref": "#/$defs/dependency" + }, + "minItems": 1, + "maxItems": 256 + }, + "prompts": { + "type": "array", + "items": { + "$ref": "#/$defs/prompt" + }, + "minItems": 0, + "maxItems": 256 + }, + "assets": { + "type": "array", + "items": { + "$ref": "#/$defs/asset" + }, + "minItems": 0, + "maxItems": 256 + } + } + }, + "panel": { + "type": "object", + "additionalProperties": false, + "required": [ + "panel_id", + "description", + "text", + "asset_ids", + "prompt_bindings" + ], + "properties": { + "panel_id": { + "$ref": "#/$defs/name" + }, + "description": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "text": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "text_id", + "kind", + "speaker", + "text" + ], + "properties": { + "text_id": { + "$ref": "#/$defs/name" + }, + "kind": { + "enum": [ + "dialogue", + "caption" + ] + }, + "speaker": { + "anyOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + } + } + }, + "minItems": 0, + "maxItems": 256 + }, + "asset_ids": { + "type": "array", + "items": { + "$ref": "#/$defs/name" + }, + "minItems": 0, + "maxItems": 256 + }, + "prompt_bindings": { + "type": "array", + "items": { + "$ref": "#/$defs/name" + }, + "minItems": 0, + "maxItems": 256 + } + } + }, + "dimensions": { + "anyOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "width", + "height" + ], + "properties": { + "width": { + "type": "integer", + "minimum": 1, + "maximum": 32768 + }, + "height": { + "type": "integer", + "minimum": 1, + "maximum": 32768 + } + } + }, + { + "type": "null" + } + ] + }, + "rendition": { + "type": "object", + "additionalProperties": false, + "required": [ + "rendition_id", + "required", + "media_type", + "dimensions", + "alt_text", + "transcript", + "rights_notice" + ], + "properties": { + "rendition_id": { + "$ref": "#/$defs/name" + }, + "required": { + "type": "boolean" + }, + "media_type": { + "enum": [ + "image/png", + "image/jpeg", + "image/webp", + "application/pdf", + "text/plain" + ] + }, + "dimensions": { + "$ref": "#/$defs/dimensions" + }, + "alt_text": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "transcript": { + "type": "string", + "minLength": 1, + "maxLength": 32768 + }, + "rights_notice": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + } + } + }, + "production": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "spec_version", + "production_id", + "revision", + "previous", + "classification", + "title", + "intent", + "inputs", + "panels", + "renditions" + ], + "properties": { + "kind": { + "const": "comic-production" + }, + "spec_version": { + "const": "1.0.0" + }, + "production_id": { + "$ref": "#/$defs/uuid" + }, + "revision": { + "$ref": "#/$defs/revision" + }, + "previous": { + "anyOf": [ + { + "$ref": "#/$defs/productionRef" + }, + { + "type": "null" + } + ] + }, + "classification": { + "$ref": "#/$defs/classification" + }, + "title": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "intent": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "inputs": { + "$ref": "#/$defs/inputs" + }, + "panels": { + "type": "array", + "items": { + "$ref": "#/$defs/panel" + }, + "minItems": 1, + "maxItems": 256 + }, + "renditions": { + "type": "array", + "items": { + "$ref": "#/$defs/rendition" + }, + "minItems": 1, + "maxItems": 32 + } + } + }, + "output": { + "type": "object", + "additionalProperties": false, + "required": [ + "rendition_id", + "artifact", + "dimensions", + "rights_notice", + "alt_text", + "transcript" + ], + "properties": { + "rendition_id": { + "$ref": "#/$defs/name" + }, + "artifact": { + "$ref": "#/$defs/artifact" + }, + "dimensions": { + "$ref": "#/$defs/dimensions" + }, + "rights_notice": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "alt_text": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "transcript": { + "type": "string", + "minLength": 1, + "maxLength": 32768 + } + } + }, + "buildOutput": { + "type": "object", + "additionalProperties": false, + "required": [ + "rendition_id", + "artifact", + "dimensions", + "rights_notice", + "alt_text", + "transcript" + ], + "properties": { + "rendition_id": { + "$ref": "#/$defs/name" + }, + "artifact": { + "type": "object", + "additionalProperties": false, + "required": [ + "artifact_uri", + "media_type", + "byte_size", + "sha256" + ], + "properties": { + "artifact_uri": { + "oneOf": [ + { + "$ref": "#/$defs/uri" + }, + { + "$ref": "#/$defs/uuid" + } + ] + }, + "media_type": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "byte_size": { + "$ref": "#/$defs/size" + }, + "sha256": { + "$ref": "#/$defs/digest" + } + } + }, + "dimensions": { + "$ref": "#/$defs/dimensions" + }, + "rights_notice": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "alt_text": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "transcript": { + "type": "string", + "minLength": 1, + "maxLength": 32768 + } + } + }, + "gate": { + "type": "object", + "additionalProperties": false, + "required": [ + "gate", + "disposition", + "evidence_reference", + "rationale" + ], + "properties": { + "gate": { + "enum": [ + "editorial", + "canon-continuity", + "visual-text", + "integrity", + "provenance", + "security-privacy", + "rights", + "accessibility", + "packaging" + ] + }, + "disposition": { + "enum": [ + "pass", + "fail", + "inconclusive", + "not-run", + "not-applicable" + ] + }, + "evidence_reference": { + "$ref": "#/$defs/uuid" + }, + "rationale": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + } + } + }, + "generation": { + "type": "object", + "additionalProperties": false, + "required": [ + "provider", + "model", + "parameters", + "evidence_state", + "limitations" + ], + "properties": { + "provider": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "model": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "parameters": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "$ref": "#/$defs/name" + }, + "value": { + "type": "string", + "minLength": 1, + "maxLength": 1024 + } + } + }, + "minItems": 0, + "maxItems": 256 + }, + "evidence_state": { + "enum": [ + "recorded", + "unavailable", + "withheld" + ] + }, + "limitations": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + } + } + }, + "execution": { + "type": "object", + "additionalProperties": false, + "required": [ + "tool", + "workflow_id", + "run_uri", + "started_at", + "finished_at", + "transformations", + "generation", + "reproducibility", + "limitations" + ], + "properties": { + "tool": { + "$ref": "#/$defs/dependency" + }, + "workflow_id": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "run_uri": { + "anyOf": [ + { + "$ref": "#/$defs/uri" + }, + { + "type": "null" + } + ] + }, + "started_at": { + "$ref": "#/$defs/time" + }, + "finished_at": { + "$ref": "#/$defs/time" + }, + "transformations": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "step_id", + "input_digests", + "output_digests", + "private_inputs_withheld", + "private_outputs_withheld" + ], + "properties": { + "step_id": { + "$ref": "#/$defs/name" + }, + "input_digests": { + "type": "array", + "items": { + "$ref": "#/$defs/digest" + }, + "minItems": 0, + "maxItems": 256 + }, + "output_digests": { + "type": "array", + "items": { + "$ref": "#/$defs/digest" + }, + "minItems": 0, + "maxItems": 256 + }, + "private_inputs_withheld": { + "type": "boolean" + }, + "private_outputs_withheld": { + "type": "boolean" + } + } + }, + "minItems": 1, + "maxItems": 256 + }, + "generation": { + "type": "array", + "items": { + "$ref": "#/$defs/generation" + }, + "minItems": 0, + "maxItems": 256 + }, + "reproducibility": { + "enum": [ + "exact", + "partial", + "audit-only" + ] + }, + "limitations": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + } + } + }, + "result": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "spec_version", + "result_id", + "attempt_id", + "production", + "classification", + "inputs", + "status", + "outputs", + "gates", + "execution", + "diagnostics" + ], + "properties": { + "kind": { + "const": "comic-build-result" + }, + "spec_version": { + "const": "1.0.0" + }, + "result_id": { + "$ref": "#/$defs/uuid" + }, + "attempt_id": { + "$ref": "#/$defs/uuid" + }, + "production": { + "$ref": "#/$defs/productionRef" + }, + "classification": { + "$ref": "#/$defs/classification" + }, + "inputs": { + "$ref": "#/$defs/inputs" + }, + "status": { + "enum": [ + "complete", + "partial", + "failed" + ] + }, + "outputs": { + "type": "array", + "items": { + "$ref": "#/$defs/buildOutput" + }, + "minItems": 0, + "maxItems": 32 + }, + "gates": { + "type": "array", + "items": { + "$ref": "#/$defs/gate" + }, + "minItems": 0, + "maxItems": 9 + }, + "execution": { + "$ref": "#/$defs/execution" + }, + "diagnostics": { + "type": "array", + "items": { + "enum": [ + "INPUT_MISMATCH", + "OUTPUT_MISSING", + "OUTPUT_INTEGRITY", + "AUTHORITY_DENIED", + "VALIDATION_FAILED", + "CANCELLED" + ] + }, + "minItems": 0, + "maxItems": 32 + } + }, + "allOf": [ + { + "if": { + "properties": { + "status": { + "const": "complete" + } + } + }, + "then": { + "properties": { + "outputs": { + "minItems": 1 + }, + "diagnostics": { + "maxItems": 0 + } + } + }, + "else": { + "properties": { + "diagnostics": { + "minItems": 1 + } + } + } + } + ] + }, + "scope": { + "type": "object", + "additionalProperties": false, + "required": [ + "destination", + "audience", + "purpose", + "publication_time" + ], + "properties": { + "destination": { + "$ref": "#/$defs/uri" + }, + "audience": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "purpose": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "publication_time": { + "$ref": "#/$defs/time" + } + } + }, + "approver": { + "type": "object", + "additionalProperties": false, + "required": [ + "decision_id", + "role", + "actor", + "decided_at" + ], + "properties": { + "decision_id": { + "$ref": "#/$defs/uuid" + }, + "role": { + "enum": [ + "publisher", + "canon-editor", + "disclosure-reviewer" + ] + }, + "actor": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "decided_at": { + "$ref": "#/$defs/time" + } + } + }, + "release": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "spec_version", + "release_id", + "episode_id", + "revision", + "previous", + "classification", + "title", + "production_credit", + "canon_scope", + "scope", + "input_canon", + "dependencies", + "outputs", + "gates", + "execution", + "approvers", + "private_context" + ], + "properties": { + "kind": { + "const": "comic-public-release" + }, + "spec_version": { + "const": "1.0.0" + }, + "release_id": { + "$ref": "#/$defs/uuid" + }, + "episode_id": { + "type": "string", + "pattern": "^DS-(?!0000)[0-9]{4}$" + }, + "revision": { + "$ref": "#/$defs/revision" + }, + "previous": { + "anyOf": [ + { + "$ref": "#/$defs/releaseRef" + }, + { + "type": "null" + } + ] + }, + "classification": { + "const": "public" + }, + "title": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "production_credit": { + "const": "A Definitely Secure Studio production." + }, + "canon_scope": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "scope": { + "$ref": "#/$defs/scope" + }, + "input_canon": { + "$ref": "#/$defs/dependency" + }, + "dependencies": { + "type": "array", + "items": { + "$ref": "#/$defs/dependency" + }, + "minItems": 1, + "maxItems": 256 + }, + "outputs": { + "type": "array", + "items": { + "$ref": "#/$defs/output" + }, + "minItems": 1, + "maxItems": 32 + }, + "gates": { + "type": "array", + "items": { + "$ref": "#/$defs/gate" + }, + "minItems": 9, + "maxItems": 9 + }, + "execution": { + "$ref": "#/$defs/execution" + }, + "approvers": { + "type": "array", + "items": { + "$ref": "#/$defs/approver" + }, + "minItems": 2, + "maxItems": 16 + }, + "private_context": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "influenced" + ], + "properties": { + "influenced": { + "const": false + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "influenced", + "attestation_reference" + ], + "properties": { + "influenced": { + "const": true + }, + "attestation_reference": { + "$ref": "#/$defs/uuid" + } + } + } + ] + } + } + }, + "approval": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "spec_version", + "decision_id", + "role", + "actor", + "subject", + "artifact_digests", + "scope", + "decided_at", + "expires_at", + "authority_reference" + ], + "properties": { + "kind": { + "const": "comic-approval-binding" + }, + "spec_version": { + "const": "1.0.0" + }, + "decision_id": { + "$ref": "#/$defs/uuid" + }, + "role": { + "enum": [ + "production-reviewer", + "publisher", + "canon-editor", + "disclosure-reviewer" + ] + }, + "actor": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "subject": { + "$ref": "#/$defs/identity" + }, + "artifact_digests": { + "type": "array", + "items": { + "$ref": "#/$defs/digest" + }, + "minItems": 0, + "maxItems": 256 + }, + "scope": { + "$ref": "#/$defs/scope" + }, + "decided_at": { + "$ref": "#/$defs/time" + }, + "expires_at": { + "$ref": "#/$defs/time" + }, + "authority_reference": { + "$ref": "#/$defs/uuid" + } + } + } + } +} diff --git a/specs/manifests/COMIC-MANIFEST-VERSIONING.md b/specs/manifests/COMIC-MANIFEST-VERSIONING.md new file mode 100644 index 0000000..d0c4359 --- /dev/null +++ b/specs/manifests/COMIC-MANIFEST-VERSIONING.md @@ -0,0 +1,41 @@ +# Comic Manifest compatibility and release boundaries + +Comic Manifest 1.0.0 is a new additive **unreleased candidate family**. The three +record kinds and detached approval definition travel together in one contract +bundle. Their `spec_version` is independent of episode revisions, artifact logical +versions and the Context Package/Builder/Prompt SDK contract versions. + +Consumers must advertise exact supported kind/version pairs; a 1.0.0 validator +rejects 1.0.1/1.1.0 until explicitly supported. This is the RFC's intentional narrow +capability rule, not a promise of implicit minor compatibility. Closed records +cannot silently ignore new optional fields. New fields or changes to accepted +meaning need coordinated review, a versioned schema ID and support negotiation; +incompatible representations require a new major version under Codex policy. + +No released foundation changes or migrations are needed. Preserve the bytes of +Prompt Definition, Provider Execution, Context Package, Structured Output, +Execution Provenance and Context Builder, including their released artifacts. +This work changes neither release catalogs nor existing release tags. Draft source +pins used in tests are explicitly non-production and never replace a release lock. + +Before production use (#99): owner-accept RFC/spec/fixtures; complete Platform +conformance and affected-consumer reviews; add a separate release catalog; package +schema/spec/fixtures/license/notices; verify immutable release assets and record +repository, version, tag, commit, URI, media type, byte size and SHA-256 in every +consumer. Expected tag: `contract/comic-manifest/v1.0.0`. No tag, registry publication +or production trust deployment is authorized by #89 or its merge. + +Known consumers: Platform #90–#98 and future Epic #7; Universe's owner-reviewed +public release records; approved private production/provenance stores. Studio +coordinates architecture, never supplies a runtime dependency. Record Codex owner +and Platform feasibility/consumer review before adoption. Universe/source owners +review publication and protected-evidence boundaries before real use. + +A future migration must preserve production/panel/episode identity, immutable +history, classification, rights and exact evidence links; changed bytes get new +candidate identities and applicable reviews. It must inventory consumers, document +loss explicitly, provide side-by-side contracts/fixtures and a dated support window +approved by owners before deprecation. No retirement or support window is started +by this initial candidate. Old releases remain available; rollback selects an +explicit supported immutable version and cannot restore revoked authority. No +silent downgrade, lossy conversion or private-data disclosure is permitted. diff --git a/specs/manifests/README.md b/specs/manifests/README.md index ace56f7..de4e237 100644 --- a/specs/manifests/README.md +++ b/specs/manifests/README.md @@ -2,3 +2,6 @@ Contracts for production inputs, outputs, release records, provenance, and immutable cross-repository references belong here. + +The proposed [Comic Manifest v1](comic-manifest-v1.md) defines production, +build-result and public release contracts. See [versioning](COMIC-MANIFEST-VERSIONING.md). diff --git a/specs/manifests/comic-manifest-v1.md b/specs/manifests/comic-manifest-v1.md new file mode 100644 index 0000000..fe8a90e --- /dev/null +++ b/specs/manifests/comic-manifest-v1.md @@ -0,0 +1,270 @@ +# Comic Manifest v1 + +Status: proposed 1.0.0 candidate, **unreleased; not a production dependency**. +Owner: @andrewperis. [RFC 0007](../../rfcs/0007-comic-manifest-v1.md). +[Studio #89](https://github.com/DefinitelySecureStudio/studio/issues/89). +Authority: [Studio ADR 0018](https://github.com/DefinitelySecureStudio/studio/blob/7b5065cef76bea9580609caba356b0d8fe7cc17c/adr/0018-comic-manifest-architecture.md) +and [architecture](https://github.com/DefinitelySecureStudio/studio/blob/7b5065cef76bea9580609caba356b0d8fe7cc17c/comic-manifest/ARCHITECTURE.md). +Constitution: v1.0.0, `constitution/v1.0.0`, Studio commit +`a9cc8a503aa30e17820edc62ac95f7cbe10e0564`. MUST/MUST NOT/SHOULD/MAY are normative. + +## Scope, ownership and exact support + +The [closed Draft 2020-12 schema](../../schemas/json/comic-manifest/v1/comic-manifest.schema.json) +defines three record kinds: `comic-production`, `comic-build-result`, and +`comic-public-release`. Each carries `spec_version: "1.0.0"`. Its separately +addressable `$defs/approval` defines a detached `comic-approval-binding` envelope; +that envelope is not a fourth production/release state and the root schema does +not accept it. No embedded decision, state flag or schema-valid document grants +access, current package use, publication or canon authority. + +Support is negotiated by **exact kind/version**, a deliberate narrower rule than +Codex's general minor-version default. Consumers MUST reject unknown versions, +fields (including nested fields), enum values and unsupported capabilities before +use. No extension maps, defaults, coercion, executable expressions, implicit +schema retrieval or arbitrary JSON data fields are defined in v1. Types and +field-level maxima in the schema are normative. + +Codex owns these meanings and synthetic fixtures. Platform implements parsing, +validation, artifact verification, review and safe proposals. Epic #7 produces +results through explicit authorized execution. Universe editors own publication +identity, public records and canon decisions. Production records/results and +restricted linkage live in approved private storage or Lore according to their +classification, never in public tooling repositories. No direct Lore checkout, +source discovery, refresh, fallback credentials or orchestration is authorized. + +## Encoding, limits and identity + +Inputs MUST be UTF-8 JSON without BOM, duplicate object keys, non-finite numbers, +unpaired surrogates or invalid Unicode. Parsing MUST be bounded before allocation: +maximum 8 MiB per document, depth 32, 100,000 JSON values and 32,768 UTF-8 bytes +per individual string, in addition to schema limits (JSON Schema string lengths +count Unicode code points). Exceeding any bound fails; consumers may advertise +stricter resource bounds before accepting work. No partial document is successful. +Safe diagnostics name a code/stage, not protected values or raw parse excerpts. +Duplicate-key detection and byte/depth limits require a runtime parser, not Ajv +validation of an already-parsed object. + +`studio-json-v1` is the existing Studio canonical representation: recursively +sort object keys in ascending UTF-16 code-unit order, preserve array order, +serialize keys/strings/numbers/booleans/null as ECMAScript `JSON.stringify`, use no +insignificant whitespace, UTF-8 and no final newline. Do not normalize Unicode. +Integers in this contract MUST remain in the safe range specified by the schema. +An `identity` records this representation's byte size and SHA-256 with `sha256:` +prefix. It covers the **entire referenced record**, never itself. Raw artifact +size/digest instead cover exact stored bytes; canonical identity does not excuse +transport corruption. Approvals and publication events are detached so they do +not require rewriting the payload being approved. + +Production, attempt, result, release, authority and decision identities use random +UUID v4 URNs assigned outside deterministic processing. IDs MUST NOT encode or be +derived from private text, paths, digests, episode numbers or timestamps. Fixture +UUIDs are deliberately fixed synthetic values, not examples of secure issuance. +Panel/text/asset/binding/rendition names are local stable tokens, not global IDs. + +Production revision 1 has `previous: null`. Every subsequent revision references +the exact same production ID, revision minus one, and predecessor canonical +identity; the referenced prior bytes MUST verify. New bytes MUST NOT reuse an +existing identity/revision. Panels and text entries are ordered arrays; panel IDs +are unique per production and text IDs unique across its panels. IDs stay stable +for the same element across revisions; deletion/reordering is a new revision, +not renumbering of element identities. Asset, binding and rendition IDs are unique +within their lists. Panel reference arrays are unique and MUST resolve locally. +Captions require null speaker; dialogue requires an explicit speaker. Content is +data, never a command, permission or executable prompt-policy override. + +## Inputs and foundation bindings + +`inputs.canon` pins input snapshot C(n). Each public `dependency` records repository, +logical semantic version, immutable tag, full commit and artifact URI, media type, +size and SHA-256. Branches, `latest`, mutable tags, version ranges and implicit +alternate sources MUST fail. HTTPS URIs are locations, never permission to fetch; +configured readers MUST verify exact bytes before use and refuse redirects or +locations outside their authorized scope. Equality of tags/digests alone does not +prove authenticity. Public fixtures use `example.invalid` and fake tuples; no test +may fetch them or present them as published releases. + +`inputs.dependencies` MUST contain the exact Comic Manifest contract and execution +tool tuples plus every other public contract/tool input. A prompt binding pins +its public reviewed definition artifact, logical id/version and complete canonical +identity. Panel bindings refer only to those reviewed definitions. A contextual +binding additionally pins the exact Context Package id/version/instance, complete +manifest identity, classification, purpose, ordered delivered section names, +Builder result identity, preparation reference and **separate** use-authorization +reference. Sections are unique and must match the actual package exactly. + +The verified package, Builder result and prompt bytes MUST satisfy their original +released contracts. Prompt identity/version, Builder output package and request +relationship, package purpose/classification/sections and authorization decision +reference MUST agree. Only Context Package contract 1.0.0 is supported. Context +Package artifact version is its own semantic version; it is not the contract +version. A context-free prompt has `context: null` and implies no context access. +Authored-only production MAY have no prompt bindings. + +Preparation authority is verified before reads by the Builder boundary. At actual +use, the consumer MUST independently verify current exact-instance use authority, +including recipient, prompt/version, purpose, sections, classification ceiling, +time and revocation using trusted out-of-band policy. Historical preparation, +matching hashes or the presence of a decision UUID cannot substitute. Unavailable +trust/revocation evidence denies the affected use. Tests use explicit historical +times and synthetic decisions; they do not authorize use today. + +Public assets carry full dependency tuples and MUST have public classification. +Protected assets carry only an opaque secure-store handle, media type and expected +size/digest in protected records. A trusted reader resolves the handle to approved +immutable bytes; no public object location is inferred. Asset rights notices and +intended uses need human rights review. Production classification MUST be at least +the maximum of assets and package classifications; result classification MUST be +at least production classification and all actual contributing material. Processing +never lowers classification. A protected-source influence needs disclosure review +regardless of whether it happens to contain an innocuous string. + +## Renditions and build results + +A production declares at least one required rendition. IDs, required/optional +status, supported media type, dimensions, alt text, transcript and rights notice +are explicit. Images require dimensions; text/PDF use null dimensions in this v1 +baseline. The contract does not render or inspect media. Consumers must check +actual media signatures/dimensions and humans must assess accessibility/meaning; +a claimed media type or transcript is insufficient proof. Source editing formats, +layout engines and illustration generation are outside v1. + +A result references the exact complete production record and repeats its exact +`inputs`. It carries distinct result/attempt IDs; attempts never mutate production. +Outputs MUST have unique declared rendition IDs and match each requirement's media, +dimensions, alt text, transcript and rights. Raw bytes MUST verify against recorded +size/digest. A result may use HTTPS output locations or protected store UUID handles; +these are never automatically copied into public records. `complete` requires every +required rendition, at least one output and no diagnostics. Optional absent outputs +are allowed; unexpected outputs fail. `partial`/`failed` require a safe diagnostic +and cannot support a release. A successful tool call is not a successful release. + +`execution` identifies the actual tool tuple, workflow/run (null run URI means not +available), UTC start/end, ordered transformations, generation evidence, +reproducibility and limitations. Start MUST not exceed finish. The tool tuple MUST +occur in inputs.dependencies. Transformation step IDs are unique; input/output +digests identify actual consumed/produced bytes, not fabricated explanations. +Protected result transformations retain full input digests and set +`private_inputs_withheld: false` and `private_outputs_withheld: false`. Material intermediate outputs require preserved +immutable evidence accessible through authorized restricted provenance. External +model/provider identities and parameter names/values are recorded explicitly; +parameter names are unique within an entry. `unavailable` and `withheld` declare +gaps with limitations, never invented evidence. No generation uses an empty array. +An exact claim is allowed only for controlled reproducible processing; preserved +selected outputs support auditability, not guaranteed external regeneration. + +Gates use the ordered vocabulary: editorial, canon-continuity, visual-text, +integrity, provenance, security-privacy, rights, accessibility, packaging. Each +entry supplies disposition, an opaque evidence reference and rationale. Results +may record a unique subset while work is incomplete. Release candidates require +all nine exactly once in that order. A fail, inconclusive or not-run gate prevents +publication eligibility. `not-applicable` requires a qualified recorded rationale +and authorized disposition. V1 defines no release-waiver shortcut. Evidence +freshness and human applicability review remain runtime/reviewer obligations. + +## Public release and restricted linkage + +A public release is a **candidate payload**, not proof of publication. It carries +release ID, `DS-NNNN` episode identity, revision, exact predecessor or null, public +classification, final title (not `Untitled`), standard Studio credit, declared canon +scope, destination/audience/purpose/publication time, public inputs and outputs, +gates, safe execution evidence and public approver attribution. Creation/validation +of it does not make it active canon or published history. + +Universe editors assign permanent episode numbers in canonical publication order +before final approval. Drafts do not allocate numbers. Numbers 0001–9999 are +supported and never reused. Conflicts, exhausted numbering and parallel reservations +stop for editor resolution; v1 defines no automatic allocator or format extension. +Corrections retain episode ID, increment release revision, reference exact prior +bytes and use a new release ID. First release has null predecessor. Reposts and +translations retain episode identity; changed artifacts need new reviewed revisions. + +`input_canon` MUST equal production C(n), never a later C(n+1) containing this +release. Public `dependencies` are the ordered, exact-tuple deduplicated sequence +of production dependencies, prompt definitions in binding order, then public asset +dependencies in asset order. They include complete public contract/tool inputs; +protected references are excluded. Public outputs contain the exact selected +result's output list in result order, with identical content identities/requirements +and approved public HTTPS locations. V1 publishes that result's entire selected +output set; a different selection requires a separate result/candidate. Output +identity is independently verifiable without access to private systems. + +Public tool, workflow identity, times and reproducibility MUST agree with the +selected result. Public transformations preserve ordered step IDs. They retain only input/output +digests already present in the public dependency, canon or released-output set; +`private_inputs_withheld` and `private_outputs_withheld` explicitly signal omitted +identities. Omission is an approved safe summary, never an invented transformation. +If all inputs or outputs are protected, that public digest list may be empty with +its corresponding flag true. Protected result input/output lists remain nonempty. Other unsafe execution +fields require an explicitly reviewed safe representation or block release; no +heuristic may silently replace provider/model evidence. Public gate evidence uses +separately approved safe handles, not copied private evidence IDs. + +Construct public payloads from an allowlist, never by serializing a protected +record and stripping known keys. Private production/result IDs and hashes, Lore +URLs/commits/paths, private package/approval identities, source/object versions and +content-derived fingerprints MUST NOT appear in any public field. Free text, URLs, +filenames, accessibility text, generation parameters, errors and actual output bytes +need disclosure review; a closed schema cannot prove non-leakage. + +`private_context` is `{influenced:false}` when no protected context/assets +contributed. Otherwise it contains exactly `influenced:true` and one random, +non-derivable approved `attestation_reference`. An authorized issuer stores a +restricted mapping binding exact production revision, selected result, public +candidate, artifacts and private lineage/authority/retention evidence. Changing +any bound identity invalidates that mapping for the new candidate. The mapping is +outside the public record. Production signing, trust roots, secure storage and +retention/deletion onboarding are separately scoped; no caller-provided mapping +can establish authority. Synthetic scenario `trust`/`linkage` objects are test +harness data, not new normative production schemas. + +## Detached approvals, invalidation and events + +An approval binding includes exact whole-record `subject` identity, sorted selected +raw artifact digests (empty for production review), exact release `scope`, reviewer +role/actor, decision UUID, UTC decision/expiry and external authority reference. +A binding is necessary evidence, not an authenticated grant. Trusted verification +MUST bind the actual reviewer to the role, subject, scope and decision, check +revocation and current time, and fail closed on missing evidence. It MUST be valid +at the actual action and authorized publication time. Never trust an allow boolean. + +The production-reviewer approves the exact production intent for its declared +scope. Publisher and canon-editor decisions bind the exact public release payload +and outputs; protected influence additionally requires disclosure-reviewer approval. +Reserved A4 decisions require actual human review. Multiple roles may be held by +one qualified human only under Studio separation-of-duties policy; the producer +cannot approve itself. Public `approvers` enumerate only public release decisions +(role, actor, decision ID/time); every entry must match a detached binding and +required roles must be present. Private-source approvers remain restricted. +Decision IDs/attribution can be allocated before hashing the candidate; detached +approval signatures do not mutate the candidate to which they bind. + +Changes to production content/order, inputs or requirements create a new revision +and invalidate affected production review, results and downstream approval. +Changes to output bytes/selection, metadata, destination, audience, purpose, time, +rights, safe projection or evidence create a new release candidate and fresh +affected validation/approval. Identical byte digests do not preserve permission +after expiry/revocation. Historical facts remain immutable, never current grants. + +Constitution §9.1 transitions require explicit evidence: draft → candidate → +gate-complete → approved → published → superseded/withdrawn. A separate publication +event binds the approved payload identity and actual published bytes/time; this +contract does not implement a publisher or prescribe a storage/event API. Final +publication/canon decisions remain Universe-owned A4 gates. Corrections/withdrawals +append events and preserve original records. Expiry blocks new use; historical +publication consequences require owner review, not automatic deletion or rewrite. + +## Conformance and follow-up + +[Fixture guide](../../fixtures/comic-manifest-v1.md) separates schema checks, +relational checks and authority obligations. The test-only oracle checks bounded +synthetic relationships; it is not production validation and cannot certify trust, +rights, non-leakage, media quality or canon. Platform's companion proof must bind +these records to the unchanged released SDK/Context Package API at explicit time. + +#90 implements hardened parsing; #91–#94 implement structural/reference/foundation/ +rendition validation; #95 implements revision and approval boundaries; #96 implements +result verification and projection; #97 provides authoring/reference-consumer tools; +#98 consolidates focused conformance; #99 separately releases immutable artifacts. +[Versioning and release boundaries](COMIC-MANIFEST-VERSIONING.md) apply throughout. +No normative gap may be filled by a local Platform schema fork. diff --git a/tests/comic-manifest.test.cjs b/tests/comic-manifest.test.cjs new file mode 100644 index 0000000..68911e4 --- /dev/null +++ b/tests/comic-manifest.test.cjs @@ -0,0 +1,126 @@ +const { readFileSync } = require('node:fs'); +const { resolve } = require('node:path'); +const assert = require('node:assert/strict'); +const test = require('node:test'); +const Ajv = require('ajv/dist/2020'); +const addFormats = require('ajv-formats'); +const { verifyScenario, verifyProduction, verifyResult, productionRef, identity, canonical, sha } = require('./support/comic-manifest-oracle.cjs'); +const read = path => JSON.parse(readFileSync(resolve(__dirname, '..', path))); +const schema = read('schemas/json/comic-manifest/v1/comic-manifest.schema.json'); +const ajv = new Ajv({ strict: true, strictRequired: false, strictTypes: false, allErrors: true }); addFormats(ajv); +const validate = ajv.compile(schema), approval = ajv.compile({ $ref: schema.$id + '#/$defs/approval' }); +const base = read('fixtures/comic-manifest-v1.json'), foundation = read('fixtures/context-builder-v1.json'); +function schemaValid(s) { + for (const k of ['production','result','release']) assert.equal(validate(s[k]), true, `${k}: ${JSON.stringify(validate.errors)}`); + for (const a of s.approvals) assert.equal(approval(a), true, JSON.stringify(approval.errors)); +} +// Simulates fresh explicit fixture decisions after an edit, never runtime approval. +function rebind(s) { + s.result.production = productionRef(s.production); s.result.inputs = structuredClone(s.production.inputs); + s.linkage.production = productionRef(s.production); s.linkage.result_identity = identity(s.result); s.linkage.release_identity = identity(s.release); + for (const a of s.approvals) { + a.subject = identity(a.role === 'production-reviewer' ? s.production : s.release); + a.artifact_digests = a.role === 'production-reviewer' ? [] : s.release.outputs.map(x=>x.artifact.sha256).sort(); + a.scope = structuredClone(s.release.scope); + } + s.trust.approvals = s.approvals.map(a=>({ decision_id:a.decision_id, sha256:identity(a).sha256 })); + if (s.trust.attestation) s.trust.attestation.linkage_identity = identity(s.linkage); +} +test('synthetic three-record slice and detached approvals validate', () => { schemaValid(base); verifyScenario(base, foundation); }); +test('every structured object is closed', () => { + function walk(x) { + if (!x || typeof x !== 'object') return; + if (x.type === 'object') assert.equal(x.additionalProperties, false); + for (const v of Object.values(x)) walk(v); + } + walk(schema); +}); +for (const kind of ['production','result','release']) for (const [field,value] of [['unexpected',true],['spec_version','1.1.0'],['kind','unknown']]) + test(`${kind} rejects ${field}`, () => assert.equal(validate({...base[kind],[field]:value}),false)); +test('all detached approval fields are closed and exactly versioned', () => { + assert.equal(approval({...base.approvals[0],allow:true}),false); + assert.equal(approval({...base.approvals[0],spec_version:'2.0.0'}),false); +}); +for (const c of read('fixtures/comic-manifest-v1-cases.json')) test(c.name, () => { + const s = structuredClone(base); + for (const edit of c.edits) { + const parent = edit.path.slice(0,-1).reduce((o,k)=>o[k],s); + if (edit.remove) delete parent[edit.path.at(-1)]; else parent[edit.path.at(-1)] = edit.value; + } + if (c.layer === 'schema') { + const record = c.edits[0].path[0]; + assert.equal(record === 'approvals' ? approval(s.approvals[c.edits[0].path[1]]) : validate(s[record]), false); return; + } + if (c.rebind) rebind(s); + schemaValid(s); + assert.throws(()=>verifyScenario(s,foundation),new RegExp(c.code)); +}); +test('a public-only authored fixture needs no package or private attestation', () => { + const s=structuredClone(base); s.production.inputs.prompts=[]; + for(const p of s.production.panels)p.prompt_bindings=[]; + s.production.classification=s.result.classification='public';s.release.dependencies=structuredClone(s.production.inputs.dependencies); + s.release.private_context={influenced:false}; delete s.linkage.attestation_reference; + s.approvals=s.approvals.filter(a=>a.role!=='disclosure-reviewer');s.release.approvers=s.release.approvers.filter(a=>a.role!=='disclosure-reviewer'); + s.trust.attestation=null; rebind(s);schemaValid(s);verifyScenario(s,foundation); +}); +test('a correction preserves publication identity and history but gets new revisions/approvals', () => { + const original=canonical(base);const s=structuredClone(base); + s.production.previous=productionRef(base.production);s.production.revision=2;s.production.panels.reverse(); + s.release.previous={release_id:base.release.release_id,episode_id:base.release.episode_id,revision:1,identity:identity(base.release)}; + s.release.revision=2;s.release.release_id='urn:uuid:10000000-0000-4000-8000-000000000999'; + rebind(s);schemaValid(s);verifyScenario(s,foundation);assert.equal(canonical(base),original); +}); +test('partial and failed records preserve evidence but cannot support release', () => { + for(const status of ['partial','failed']) { + const s=structuredClone(base);s.result.status=status;s.result.outputs=[];s.result.diagnostics=['OUTPUT_MISSING'];rebind(s);schemaValid(s); + verifyResult(s.production,s.result,s.output_bytes);assert.throws(()=>verifyScenario(s,foundation),/RESULT_NOT_COMPLETE/); + } +}); +test('deterministic canonical identity preserves array order, Unicode and content', () => { + assert.deepEqual(identity({z:'é',a:[1,2]}),identity({a:[1,2],z:'é'})); + assert.notDeepEqual(identity({a:[1,2]}),identity({a:[2,1]})); + assert.notDeepEqual(identity({a:'é'}),identity({a:'e\u0301'})); + const s=structuredClone(base);s.production=Object.fromEntries(Object.entries(s.production).reverse());verifyScenario(s,foundation); +}); +test('nested asset, rendition and panel boundaries reject extensions', () => { + for(const [path,key] of [[['panels',0],'execute'],[['renditions',0],'extra'],[['inputs','prompts',0,'context'],'allow']]) { + const p=structuredClone(base.production);path.reduce((o,k)=>o[k],p)[key]='untrusted';assert.equal(validate(p),false); + } +}); +test('released foundation schema identities remain unchanged', () => { + for (const [path,digest] of [ + ['context-packages/v1/context-package.schema.json','d81e88780511c31099b2dd925f31aff26d6ba75e1173e953b98a37298764b617'], + ['context-builder/v1/context-builder.schema.json','4adebedcef5a26e009e1d53ec9c480d372a31b73211fec1c53d6509ebc7929a3'], + ['prompt-contracts/v1/prompt-definition.schema.json','6ac345956582d25c2db9c81b85cab9c73b2cbcfc79b8aac7a43d847c07173cb9'] + ]) assert.equal(sha(readFileSync(resolve(__dirname,'../schemas/json',path))),'sha256:'+digest); +}); +test('missing one required rendition fails even with another valid output', () => { + const s=structuredClone(base);s.production.renditions.push({...s.production.renditions[0],rendition_id:'second-required'});rebind(s);schemaValid(s); + assert.throws(()=>verifyScenario(s,foundation),/REQUIRED_OUTPUT/); +}); +test('public asset references resolve and contribute exact public dependencies', () => { + const s=structuredClone(base);s.production.inputs.assets=[{asset_id:'synthetic-asset',classification:'public',reference:{kind:'public',dependency:s.production.inputs.canon},rights_notice:'Synthetic fixture.'}]; + s.production.panels[0].asset_ids=['synthetic-asset'];s.release.dependencies.push(structuredClone(s.production.inputs.canon)); + rebind(s);schemaValid(s);verifyScenario(s,foundation); + s.production.inputs.assets[0].classification='internal';rebind(s);schemaValid(s);assert.throws(()=>verifyScenario(s,foundation),/CLASSIFICATION/); +}); +test('public transformation summaries cannot invent input lineage', () => { + const s=structuredClone(base);s.release.execution.transformations[0].input_digests=[s.release.dependencies[0].artifact.sha256];rebind(s);schemaValid(s); + assert.throws(()=>verifyScenario(s,foundation),/TRANSFORMATION_LINK/); +}); +test('altered artifacts, destination and private projection invalidate exact approvals', () => { + for(const mutate of [ + s=>s.release.scope.destination='https://example.invalid/other', + s=>s.release.private_context.attestation_reference='urn:uuid:10000000-0000-4000-8000-000000000888', + s=>s.release.outputs[0].artifact.artifact_uri='https://example.invalid/moved' + ]) { + const s=structuredClone(base);mutate(s);schemaValid(s);assert.throws(()=>verifyScenario(s,foundation),/LINEAGE/); + } +}); +test('protected intermediate digests are withheld without inventing public transformations', () => { + const s=structuredClone(base), hidden=sha('SYNTHETIC-PROTECTED-SENTINEL');s.protected_sentinels.push(hidden); + s.result.execution.transformations.unshift({step_id:'protected-intermediate',input_digests:[identity(s.production).sha256],output_digests:[hidden],private_inputs_withheld:false,private_outputs_withheld:false}); + s.release.execution.transformations.unshift({step_id:'protected-intermediate',input_digests:[],output_digests:[],private_inputs_withheld:true,private_outputs_withheld:true}); + rebind(s);schemaValid(s);verifyScenario(s,foundation); + s.release.execution.transformations[0].output_digests=[hidden];rebind(s);schemaValid(s);assert.throws(()=>verifyScenario(s,foundation),/TRANSFORMATION_LINK/); +}); diff --git a/tests/support/comic-manifest-oracle.cjs b/tests/support/comic-manifest-oracle.cjs new file mode 100644 index 0000000..7dc0d3e --- /dev/null +++ b/tests/support/comic-manifest-oracle.cjs @@ -0,0 +1,141 @@ +// Test-only relational oracle for public synthetic scenarios, not a runtime +// parser, authorization verifier, projection implementation or trust root. +const assert = require('node:assert/strict'); +const { canonical, identity, sha } = require('./context-builder-oracle.cjs'); +const eq = (a, b, code) => assert.deepEqual(a, b, code); +const check = (v, code) => assert.ok(v, code); +const unique = (xs, code) => eq(new Set(xs).size, xs.length, code); +const rank = c => ['public', 'internal', 'confidential', 'restricted'].indexOf(c); +const gates = ['editorial', 'canon-continuity', 'visual-text', 'integrity', 'provenance', 'security-privacy', 'rights', 'accessibility', 'packaging']; +const productionRef = p => ({ production_id: p.production_id, revision: p.revision, identity: identity(p) }); +function verifyProduction(p) { + eq(p.previous === null, p.revision === 1, 'REVISION'); + if (p.previous) { eq(p.previous.production_id, p.production_id, 'REVISION'); eq(p.previous.revision + 1, p.revision, 'REVISION'); } + unique(p.panels.map(x => x.panel_id), 'PANEL_ID'); + unique(p.panels.flatMap(x => x.text.map(t => t.text_id)), 'TEXT_ID'); + unique(p.inputs.assets.map(x => x.asset_id), 'ASSET_ID'); + unique(p.inputs.prompts.map(x => x.binding_id), 'PROMPT_ID'); + unique(p.renditions.map(x => x.rendition_id), 'RENDITION_ID'); + for (const panel of p.panels) { + unique(panel.asset_ids, 'ASSET_ID'); unique(panel.prompt_bindings, 'PROMPT_ID'); + check(panel.asset_ids.every(id => p.inputs.assets.some(a => a.asset_id === id)), 'ASSET_LINK'); + check(panel.prompt_bindings.every(id => p.inputs.prompts.some(a => a.binding_id === id)), 'PROMPT_LINK'); + for (const t of panel.text) eq(t.speaker === null, t.kind === 'caption', 'SPEAKER'); + } + for (const a of p.inputs.assets) { + check(rank(p.classification) >= rank(a.classification), 'CLASSIFICATION'); + if (a.reference.kind === 'public') eq(a.classification, 'public', 'CLASSIFICATION'); + } + for (const b of p.inputs.prompts) if (b.context) { + check(rank(p.classification) >= rank(b.context.classification), 'CLASSIFICATION'); + unique(b.context.sections, 'CONTEXT_SECTIONS'); + } + for (const r of p.renditions) eq(r.media_type.startsWith('image/'), r.dimensions !== null, 'DIMENSIONS'); + check(p.renditions.some(r => r.required), 'REQUIRED_OUTPUT'); +} +function verifyResult(p, r, bytes) { + eq(r.production, productionRef(p), 'PRODUCTION_LINK'); eq(r.inputs, p.inputs, 'INPUT_LINK'); + check(rank(r.classification) >= rank(p.classification), 'CLASSIFICATION'); + unique(r.outputs.map(x => x.rendition_id), 'OUTPUT_ID'); unique(r.gates.map(x => x.gate), 'GATE_ID'); + check(Date.parse(r.execution.started_at) <= Date.parse(r.execution.finished_at), 'TIME'); + check(p.inputs.dependencies.some(d=>canonical(d)===canonical(r.execution.tool)),'TOOL_LINK'); + unique(r.execution.transformations.map(t=>t.step_id),'TRANSFORMATION_ID'); + for(const t of r.execution.transformations) { check(t.input_digests.length>0 && t.output_digests.length>0,'TRANSFORMATION_INPUT');eq(t.private_inputs_withheld,false,'TRANSFORMATION_INPUT');eq(t.private_outputs_withheld,false,'TRANSFORMATION_INPUT'); } + for(const g of r.execution.generation) unique(g.parameters.map(p=>p.name),'PARAMETER_ID'); + for (const out of r.outputs) { + const req = p.renditions.find(x => x.rendition_id === out.rendition_id); check(req, 'UNEXPECTED_OUTPUT'); + eq(out.artifact.media_type, req.media_type, 'MEDIA'); eq(out.dimensions, req.dimensions, 'DIMENSIONS'); + for (const key of ['alt_text', 'transcript', 'rights_notice']) eq(out[key], req[key], 'OUTPUT_REQUIREMENT'); + const raw = bytes[out.rendition_id]; check(typeof raw === 'string', 'MISSING_BYTES'); + eq(out.artifact.byte_size, Buffer.byteLength(raw), 'OUTPUT_SIZE'); eq(out.artifact.sha256, sha(raw), 'OUTPUT_DIGEST'); + } + if (r.status === 'complete') { + eq(r.diagnostics, [], 'COMPLETE_DIAGNOSTICS'); + check(p.renditions.filter(x => x.required).every(req => r.outputs.some(out => out.rendition_id === req.rendition_id)), 'REQUIRED_OUTPUT'); + } else check(r.diagnostics.length > 0, 'FAILURE_DIAGNOSTIC'); +} +function verifyFoundation(p, foundation) { + for (const b of p.inputs.prompts) { + eq(b.identity, identity(foundation.prompt), 'PROMPT_IDENTITY'); + eq(b.prompt_id, foundation.prompt.id, 'PROMPT_IDENTITY'); eq(b.prompt_version, foundation.prompt.version, 'PROMPT_IDENTITY'); + if (!b.context) continue; + const c = b.context, pkg = foundation.result.package; + eq(c.instance_id, pkg.manifest.package.instance_id, 'PACKAGE_LINK'); + eq(c.package_id, pkg.manifest.package.id, 'PACKAGE_LINK'); eq(c.package_version, pkg.manifest.package.version, 'PACKAGE_LINK'); + eq(c.manifest_identity, identity(pkg.manifest), 'PACKAGE_LINK'); + eq(c.builder_result_identity, identity(foundation.result), 'BUILDER_LINK'); + eq(c.classification, pkg.manifest.classification, 'CLASSIFICATION'); + eq(c.purpose, pkg.manifest.purpose, 'CONTEXT_PURPOSE'); eq(c.sections, pkg.manifest.sections.map(x => x.slot), 'CONTEXT_SECTIONS'); + eq(c.preparation_reference, foundation.request.preparation_reference, 'PREPARATION_LINK'); + eq(c.use_authorization_reference, foundation.authorization.decision_id, 'USE_LINK'); + } +} +function verifyApproval(a, subject, artifacts, scope, s) { + eq(a.subject, identity(subject), 'APPROVAL_SUBJECT'); + eq(a.artifact_digests, artifacts.map(x => x.artifact.sha256).sort(), 'APPROVAL_ARTIFACTS'); + eq(a.scope, scope, 'APPROVAL_SCOPE'); + const at = Date.parse(s.at); + check(Date.parse(a.decided_at) <= at && at < Date.parse(a.expires_at) && Date.parse(scope.publication_time) < Date.parse(a.expires_at), 'APPROVAL_TIME'); + // A test harness supplies trusted decisions OUTSIDE untrusted record bytes. + const trusted = s.trust.approvals.find(x => x.decision_id === a.decision_id); + check(trusted && trusted.sha256 === identity(a).sha256 && !s.trust.revoked.includes(a.decision_id), 'APPROVAL_TRUST'); +} +function verifyScenario(s, foundation) { + const p = s.production, r = s.result, rel = s.release; + verifyProduction(p); verifyResult(p, r, s.output_bytes); verifyFoundation(p, foundation); + for (const d of [p.inputs.canon, ...p.inputs.dependencies, ...p.inputs.prompts.map(b => b.definition), + ...p.inputs.assets.filter(a => a.reference.kind === 'public').map(a => a.reference.dependency)]) { + check(!['main', 'master', 'latest'].includes(d.tag), 'FLOATING_REFERENCE'); + const bytes = s.dependency_bytes[d.artifact.artifact_uri]; check(typeof bytes === 'string', 'DEPENDENCY_MISSING'); + eq(Buffer.byteLength(bytes), d.artifact.byte_size, 'DEPENDENCY_SIZE'); eq(sha(bytes), d.artifact.sha256, 'DEPENDENCY_DIGEST'); + } + eq(r.status, 'complete', 'RESULT_NOT_COMPLETE'); + eq(s.linkage.production, productionRef(p), 'LINEAGE'); eq(s.linkage.result_identity, identity(r), 'LINEAGE'); + eq(s.linkage.release_identity, identity(rel), 'LINEAGE'); + eq(rel.input_canon, p.inputs.canon, 'CANON_INPUT'); + const publicDeps = [...p.inputs.dependencies, ...p.inputs.prompts.map(b=>b.definition), ...p.inputs.assets.filter(a=>a.reference.kind==='public').map(a=>a.reference.dependency)]; + eq(rel.dependencies, [...new Map(publicDeps.map(d=>[canonical(d),d])).values()], 'DEPENDENCY_LINK'); + eq(rel.title, p.title, 'TITLE'); check(rel.title !== 'Untitled', 'FINAL_TITLE'); + eq(rel.previous === null, rel.revision === 1, 'RELEASE_REVISION'); + if (rel.previous) { eq(rel.previous.episode_id, rel.episode_id, 'EPISODE_ID'); eq(rel.previous.revision + 1, rel.revision, 'RELEASE_REVISION'); check(rel.previous.release_id !== rel.release_id, 'RELEASE_REVISION'); } + unique(rel.outputs.map(x => x.rendition_id), 'OUTPUT_ID'); + eq(rel.outputs.map(x => x.rendition_id), r.outputs.map(x => x.rendition_id), 'RELEASE_OUTPUTS'); + for (let i=0; id.artifact.sha256),...rel.outputs.map(o=>o.artifact.sha256)]); + eq(rel.execution.transformations.length,r.execution.transformations.length,'TRANSFORMATION_LINK'); + for (let i=0;ipublicDigests.has(d)),'TRANSFORMATION_LINK');eq(b.private_outputs_withheld,b.output_digests.length!==a.output_digests.length,'TRANSFORMATION_LINK'); + eq(b.input_digests,a.input_digests.filter(d=>publicDigests.has(d)),'TRANSFORMATION_LINK'); + eq(b.private_inputs_withheld,b.input_digests.length!==a.input_digests.length,'TRANSFORMATION_LINK'); + } + eq(rel.gates.map(x => x.gate), gates, 'GATES'); + check(rel.gates.every(x => ['pass', 'not-applicable'].includes(x.disposition)), 'GATE_FAILED'); + for (const g of rel.gates) if (g.disposition === 'not-applicable') check(s.trust.not_applicable.includes(g.gate), 'GATE_NA'); + eq(s.trust.assignment, { production_id: p.production_id, episode_id: rel.episode_id }, 'ASSIGNMENT'); + const privateUsed = (foundation.result.package.manifest.sources.some(x => x.kind === 'approved-private') && p.inputs.prompts.some(x => x.context)) || p.inputs.assets.some(x => x.reference.kind === 'protected'); + eq(rel.private_context.influenced, privateUsed, 'PRIVATE_INFLUENCE'); + if (privateUsed) { + eq(s.linkage.attestation_reference, rel.private_context.attestation_reference, 'ATTESTATION'); + eq(s.trust.attestation, { reference: rel.private_context.attestation_reference, linkage_identity: identity(s.linkage) }, 'ATTESTATION_TRUST'); + } + const prodApproval = s.approvals.filter(x => x.role === 'production-reviewer'); eq(prodApproval.length, 1, 'PRODUCTION_REVIEW'); + verifyApproval(prodApproval[0], p, [], rel.scope, s); + unique(s.approvals.map(x => x.decision_id), 'APPROVAL_ID'); unique(rel.approvers.map(x => x.decision_id), 'APPROVAL_ID'); + const releaseApprovals = s.approvals.filter(x => x.role !== 'production-reviewer'); + eq(rel.approvers, releaseApprovals.map(({decision_id,role,actor,decided_at}) => ({decision_id,role,actor,decided_at})), 'PUBLIC_APPROVER'); + for (const role of ['publisher','canon-editor', ...(privateUsed ? ['disclosure-reviewer'] : [])]) check(releaseApprovals.some(a => a.role === role), 'APPROVAL_ROLE'); + for (const a of releaseApprovals) verifyApproval(a, rel, rel.outputs, rel.scope, s); + // Sentinel/identity checks demonstrate the boundary; they cannot prove prose + // is reader-safe. Trusted human disclosure review remains mandatory. + const publicText = JSON.stringify(rel); + for (const secret of [p.production_id, identity(p).sha256, r.result_id, r.attempt_id, identity(r).sha256, + ...p.inputs.prompts.flatMap(b => b.context ? [b.context.instance_id,b.context.manifest_identity.sha256,b.context.builder_result_identity.sha256,b.context.preparation_reference,b.context.use_authorization_reference] : []), + ...s.protected_sentinels]) check(!publicText.includes(secret), 'PRIVATE_LEAK'); +} +module.exports = { verifyProduction, verifyResult, verifyFoundation, verifyScenario, productionRef, gates, canonical, identity, sha };