2026-08-03 15:13:43 +01:00
|
|
|
{
|
|
|
|
|
"$comment": [
|
|
|
|
|
"Shared conformance cases for the tool I/O compatibility check.",
|
|
|
|
|
"Run by Java (ToolChainValidatorConformanceTest), the frontend (toolIOCompat.test.ts) and the",
|
|
|
|
|
"engine (test_tool_io_compat.py) against their own implementations, which must agree.",
|
|
|
|
|
"Specs are synthetic, so retyping a real endpoint never invalidates a case.",
|
|
|
|
|
"'expected' is matched on stepIndex/severity/code; messages are free text."
|
|
|
|
|
],
|
|
|
|
|
"specs": {
|
|
|
|
|
"pdfToPdf": { "accepts": ["PDF"], "produces": "PDF", "arity": "SISO", "cases": [] },
|
|
|
|
|
"pdfToPdfSplit": { "accepts": ["PDF"], "produces": "PDF", "arity": "SIMO", "cases": [] },
|
|
|
|
|
"pdfMerge": { "accepts": ["PDF"], "produces": "PDF", "arity": "MISO", "cases": [] },
|
|
|
|
|
"addPassword": { "accepts": ["PDF"], "produces": "PDF_ENCRYPTED", "arity": "SISO", "cases": [] },
|
|
|
|
|
"removePassword": { "accepts": ["PDF_ENCRYPTED"], "produces": "PDF", "arity": "SISO", "cases": [] },
|
|
|
|
|
"extractImages": { "accepts": ["PDF"], "produces": "IMAGE", "arity": "SIMO", "cases": [] },
|
|
|
|
|
"imageToPdf": { "accepts": ["IMAGE"], "produces": "PDF", "arity": "MISO", "cases": [] },
|
|
|
|
|
"getAttachments": { "accepts": ["PDF"], "produces": "ZIP", "arity": "SISO", "cases": [] },
|
|
|
|
|
"getInfo": { "accepts": ["PDF"], "produces": "JSON", "arity": "SISO", "cases": [] },
|
|
|
|
|
"validateOnly": { "accepts": ["PDF"], "produces": "NONE", "arity": "SISO", "cases": [] },
|
|
|
|
|
"anyToPdf": { "accepts": ["ANY"], "produces": "PDF", "arity": "SISO", "cases": [] },
|
|
|
|
|
"pdfToAny": { "accepts": ["PDF"], "produces": "ANY", "arity": "SISO", "cases": [] },
|
|
|
|
|
"pdfOrImage": { "accepts": ["PDF", "IMAGE"], "produces": "PDF", "arity": "SISO", "cases": [] },
|
|
|
|
|
"pdfToImageConfigurable": {
|
|
|
|
|
"accepts": ["PDF"],
|
|
|
|
|
"produces": "IMAGE",
|
|
|
|
|
"arity": "SIMO",
|
|
|
|
|
"cases": [
|
|
|
|
|
{
|
|
|
|
|
"when": [{ "param": "singleOrMultiple", "matches": ["single"] }],
|
|
|
|
|
"produces": "IMAGE",
|
|
|
|
|
"arity": "SISO"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"addPasswordConfigurable": {
|
|
|
|
|
"accepts": ["PDF"],
|
|
|
|
|
"produces": "PDF_ENCRYPTED",
|
|
|
|
|
"arity": "SISO",
|
|
|
|
|
"cases": [
|
|
|
|
|
{
|
|
|
|
|
"when": [{ "param": "password", "matches": [""] }],
|
|
|
|
|
"produces": "PDF",
|
|
|
|
|
"arity": "SISO"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"pdfToImageMixedCase": {
|
|
|
|
|
"accepts": ["PDF"],
|
|
|
|
|
"produces": "IMAGE",
|
|
|
|
|
"arity": "SIMO",
|
|
|
|
|
"cases": [
|
|
|
|
|
{
|
|
|
|
|
"when": [{ "param": "singleOrMultiple", "matches": [" SiNgLe "] }],
|
|
|
|
|
"produces": "IMAGE",
|
|
|
|
|
"arity": "SISO"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"addPasswordTwoKeys": {
|
|
|
|
|
"accepts": ["PDF"],
|
|
|
|
|
"produces": "PDF_ENCRYPTED",
|
|
|
|
|
"arity": "SISO",
|
|
|
|
|
"cases": [
|
|
|
|
|
{
|
|
|
|
|
"when": [
|
|
|
|
|
{ "param": "password", "matches": [""] },
|
|
|
|
|
{ "param": "ownerPassword", "matches": [""] }
|
|
|
|
|
],
|
|
|
|
|
"produces": "PDF",
|
|
|
|
|
"arity": "SISO"
|
|
|
|
|
}
|
|
|
|
|
]
|
2026-08-10 12:00:16 +01:00
|
|
|
},
|
|
|
|
|
"autoRotateShape": {
|
|
|
|
|
"accepts": ["PDF"],
|
|
|
|
|
"produces": "PDF",
|
|
|
|
|
"arity": "SISO",
|
|
|
|
|
"cases": [
|
|
|
|
|
{
|
|
|
|
|
"when": [{ "param": "dryRun", "matches": ["true"], "default": "false" }],
|
|
|
|
|
"produces": "JSON",
|
|
|
|
|
"arity": "SISO"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"changePermsShape": {
|
|
|
|
|
"accepts": ["PDF"],
|
|
|
|
|
"produces": "PDF_ENCRYPTED",
|
|
|
|
|
"arity": "SISO",
|
|
|
|
|
"cases": [
|
|
|
|
|
{
|
|
|
|
|
"when": [
|
|
|
|
|
{ "param": "password", "matches": [""], "default": "" },
|
|
|
|
|
{ "param": "ownerPassword", "matches": [""], "default": "" }
|
|
|
|
|
],
|
|
|
|
|
"produces": "PDF",
|
|
|
|
|
"arity": "SISO"
|
|
|
|
|
}
|
|
|
|
|
]
|
2026-08-03 15:13:43 +01:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"cases": [
|
|
|
|
|
{
|
|
|
|
|
"name": "a plain pdf chain is clean",
|
|
|
|
|
"steps": [{ "spec": "pdfToPdf" }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": []
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "an empty chain is clean",
|
|
|
|
|
"steps": [],
|
|
|
|
|
"expected": []
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "a single step is never a transition",
|
|
|
|
|
"steps": [{ "spec": "extractImages" }],
|
|
|
|
|
"expected": []
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "encrypting then editing is rejected",
|
|
|
|
|
"steps": [{ "spec": "addPassword" }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "encrypting then decrypting is allowed",
|
|
|
|
|
"steps": [{ "spec": "addPassword" }, { "spec": "removePassword" }],
|
|
|
|
|
"expected": []
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "decrypting then editing is allowed",
|
|
|
|
|
"steps": [{ "spec": "removePassword" }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": []
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "an ordinary tool cannot be handed an encrypted pdf from source",
|
|
|
|
|
"sourceFormat": "PDF_ENCRYPTED",
|
|
|
|
|
"steps": [{ "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": [{ "stepIndex": 0, "severity": "ERROR", "code": "source-mismatch" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "remove-password is the one thing an encrypted source can start with",
|
|
|
|
|
"sourceFormat": "PDF_ENCRYPTED",
|
|
|
|
|
"steps": [{ "spec": "removePassword" }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": []
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "extracted images cannot feed a pdf tool",
|
|
|
|
|
"steps": [{ "spec": "extractImages" }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "extracted images can feed an image tool, and fan in",
|
|
|
|
|
"steps": [{ "spec": "extractImages" }, { "spec": "imageToPdf" }],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "INFO", "code": "fan-in" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "splitting then a single-file tool fans out",
|
|
|
|
|
"steps": [{ "spec": "pdfToPdfSplit" }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "INFO", "code": "fan-out" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "splitting then merging fans in",
|
|
|
|
|
"steps": [{ "spec": "pdfToPdfSplit" }, { "spec": "pdfMerge" }],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "INFO", "code": "fan-in" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "an archive deliverable does not feed a pdf tool",
|
|
|
|
|
"steps": [{ "spec": "getAttachments" }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "a json report does not feed a pdf tool",
|
|
|
|
|
"steps": [{ "spec": "getInfo" }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "a step producing no file terminates the chain",
|
|
|
|
|
"steps": [{ "spec": "validateOnly" }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "a tool accepting anything swallows any upstream output",
|
|
|
|
|
"steps": [{ "spec": "extractImages" }, { "spec": "anyToPdf" }],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "INFO", "code": "fan-out" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "a tool producing anything satisfies any downstream input",
|
|
|
|
|
"steps": [{ "spec": "pdfToAny" }, { "spec": "imageToPdf" }],
|
|
|
|
|
"expected": []
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "a source of unknown type is not checked against step one",
|
|
|
|
|
"steps": [{ "spec": "imageToPdf" }],
|
|
|
|
|
"expected": []
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "a multi-format tool accepts either branch",
|
|
|
|
|
"steps": [{ "spec": "extractImages" }, { "spec": "pdfOrImage" }],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "INFO", "code": "fan-out" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "an unconfigured parameter-dependent output only warns",
|
|
|
|
|
"steps": [{ "spec": "addPasswordConfigurable" }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "WARN", "code": "output-uncertain" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "configuring the parameter to the encrypting branch makes it an error",
|
|
|
|
|
"steps": [
|
|
|
|
|
{ "spec": "addPasswordConfigurable", "parameters": { "password": "hunter2" } },
|
|
|
|
|
{ "spec": "pdfToPdf" }
|
|
|
|
|
],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "configuring the parameter to the non-encrypting branch clears it",
|
|
|
|
|
"steps": [
|
|
|
|
|
{ "spec": "addPasswordConfigurable", "parameters": { "password": "" } },
|
|
|
|
|
{ "spec": "pdfToPdf" }
|
|
|
|
|
],
|
|
|
|
|
"expected": []
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "a multi-condition case needs every condition to hold",
|
|
|
|
|
"steps": [
|
|
|
|
|
{
|
|
|
|
|
"spec": "addPasswordTwoKeys",
|
|
|
|
|
"parameters": { "password": "", "ownerPassword": "hunter2" }
|
|
|
|
|
},
|
|
|
|
|
{ "spec": "pdfToPdf" }
|
|
|
|
|
],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "a multi-condition case applies when every condition holds",
|
|
|
|
|
"steps": [
|
|
|
|
|
{ "spec": "addPasswordTwoKeys", "parameters": { "password": "", "ownerPassword": "" } },
|
|
|
|
|
{ "spec": "pdfToPdf" }
|
|
|
|
|
],
|
|
|
|
|
"expected": []
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "a multi-condition case is unresolved when one condition is unconfigured",
|
|
|
|
|
"steps": [
|
|
|
|
|
{ "spec": "addPasswordTwoKeys", "parameters": { "password": "" } },
|
|
|
|
|
{ "spec": "pdfToPdf" }
|
|
|
|
|
],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "WARN", "code": "output-uncertain" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "the single branch of an arity case produces one file, so no fan-out",
|
|
|
|
|
"steps": [
|
|
|
|
|
{ "spec": "pdfToImageConfigurable", "parameters": { "singleOrMultiple": "single" } },
|
|
|
|
|
{ "spec": "imageToPdf" }
|
|
|
|
|
],
|
|
|
|
|
"expected": []
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "the default branch of an arity case fans in",
|
|
|
|
|
"steps": [
|
|
|
|
|
{ "spec": "pdfToImageConfigurable", "parameters": { "singleOrMultiple": "multiple" } },
|
|
|
|
|
{ "spec": "imageToPdf" }
|
|
|
|
|
],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "INFO", "code": "fan-in" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "a condition matches regardless of the casing and padding it was declared with",
|
|
|
|
|
"steps": [
|
|
|
|
|
{ "spec": "pdfToImageMixedCase", "parameters": { "singleOrMultiple": "SINGLE" } },
|
|
|
|
|
{ "spec": "imageToPdf" }
|
|
|
|
|
],
|
|
|
|
|
"expected": []
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "a condition declared in mixed case still fails on a genuinely different value",
|
|
|
|
|
"steps": [
|
|
|
|
|
{ "spec": "pdfToImageMixedCase", "parameters": { "singleOrMultiple": "multiple" } },
|
|
|
|
|
{ "spec": "imageToPdf" }
|
|
|
|
|
],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "INFO", "code": "fan-in" }]
|
|
|
|
|
},
|
2026-08-10 12:00:16 +01:00
|
|
|
{
|
|
|
|
|
"name": "an absent parameter takes its default, which here does not trigger the case",
|
|
|
|
|
"steps": [{ "spec": "autoRotateShape" }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": []
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "an explicit value overrides the default and triggers the case",
|
|
|
|
|
"steps": [
|
|
|
|
|
{ "spec": "autoRotateShape", "parameters": { "dryRun": "true" } },
|
|
|
|
|
{ "spec": "pdfToPdf" }
|
|
|
|
|
],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "an absent parameter whose default triggers the case resolves to that branch",
|
|
|
|
|
"steps": [{ "spec": "changePermsShape" }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": []
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "setting the branch parameter away from its default flips the outcome",
|
|
|
|
|
"steps": [
|
|
|
|
|
{
|
|
|
|
|
"spec": "changePermsShape",
|
|
|
|
|
"parameters": { "password": "hunter2", "ownerPassword": "hunter2" }
|
|
|
|
|
},
|
|
|
|
|
{ "spec": "pdfToPdf" }
|
|
|
|
|
],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
|
|
|
},
|
2026-08-03 15:13:43 +01:00
|
|
|
{
|
|
|
|
|
"name": "an undeclared step warns and stops the chain being checked past it",
|
|
|
|
|
"steps": [{ "spec": "addPassword" }, { "spec": null }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "WARN", "code": "undeclared-operation" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "the source format is not re-applied to a step that follows an undeclared one",
|
|
|
|
|
"sourceFormat": "PDF_ENCRYPTED",
|
|
|
|
|
"steps": [{ "spec": "removePassword" }, { "spec": null }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": [{ "stepIndex": 1, "severity": "WARN", "code": "undeclared-operation" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "an undeclared first step does not pass the source format on to the next step",
|
|
|
|
|
"sourceFormat": "PDF_ENCRYPTED",
|
|
|
|
|
"steps": [{ "spec": null }, { "spec": "pdfToPdf" }],
|
|
|
|
|
"expected": [{ "stepIndex": 0, "severity": "WARN", "code": "undeclared-operation" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "every broken transition is reported, not just the first",
|
|
|
|
|
"steps": [{ "spec": "extractImages" }, { "spec": "pdfToPdf" }, { "spec": "imageToPdf" }],
|
|
|
|
|
"expected": [
|
|
|
|
|
{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" },
|
|
|
|
|
{ "stepIndex": 2, "severity": "ERROR", "code": "format-mismatch" }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|