From 01aee375900273dbbe30333148dc382bf7cd30d3 Mon Sep 17 00:00:00 2001 From: Matthew Thomas <25988124+Ma11hewThomas@users.noreply.github.com> Date: Sat, 24 Oct 2026 13:17:32 +0100 Subject: [PATCH 1/2] Clarify portable attachment references and digest metadata --- CHANGELOG.md | 2 + examples/portable-attachments.json | 71 +++++++++++++++++ schema/ctrf.schema.json | 8 +- spec/ctrf.md | 95 +++++++++++++++++++++-- tests/ctrf.test.json | 5 ++ tests/normative/valid-documents.test.json | 5 ++ 6 files changed, 177 insertions(+), 9 deletions(-) create mode 100644 examples/portable-attachments.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 948b2fc..fa513e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Clarified durable attachment references, optional producer-local paths and producer-defined digest metadata. + - Added coordinated cross-process retry examples and clarified logical-run correlation and final-attempt duration. - Clarified parameterized case identity, rename and path scopes, and execution identity across coordinated retry processes. diff --git a/examples/portable-attachments.json b/examples/portable-attachments.json new file mode 100644 index 0000000..1f3f2c9 --- /dev/null +++ b/examples/portable-attachments.json @@ -0,0 +1,71 @@ +{ + "reportFormat": "CTRF", + "specVersion": "0.2.0", + "results": { + "tool": { + "name": "example-runner" + }, + "summary": { + "tests": 1, + "passed": 1, + "failed": 0, + "skipped": 0, + "pending": 0, + "other": 0, + "flaky": 1, + "start": 1700000000000, + "stop": 1700000000300 + }, + "tests": [ + { + "name": "request succeeds after retry", + "status": "passed", + "duration": 100, + "retries": 1, + "flaky": true, + "attachments": [ + { + "name": "retry.log", + "contentType": "text/plain", + "path": "https://artifacts.example.com/run-42/retry.log", + "extra": { + "example.reporter": { + "localPath": "/work/results/retry.log", + "digest": { + "algorithm": "sha256", + "encoding": "hex", + "value": "385c7da7a539a88aafb70a49549d3642a4e52c0fb23cf58e80f1b5e82e69bc67" + } + } + } + } + ], + "retryAttempts": [ + { + "attempt": 1, + "status": "failed", + "duration": 200, + "message": "Request timed out", + "attachments": [ + { + "name": "initial.log", + "contentType": "text/plain", + "path": "https://artifacts.example.com/run-42/initial.log", + "extra": { + "example.reporter": { + "localPath": "/work/results/initial.log", + "digest": { + "algorithm": "sha256", + "encoding": "hex", + "value": "fe37d563cf87595eb141374a73f3f5e88cf7f097d56eebc2c34e766bdc66f7c7" + } + } + } + } + ] + } + ] + } + ] + } +} diff --git a/schema/ctrf.schema.json b/schema/ctrf.schema.json index bc3f018..25bdba5 100644 --- a/schema/ctrf.schema.json +++ b/schema/ctrf.schema.json @@ -346,7 +346,7 @@ "required": [ "name", "contentType", "path" ], "properties": { "attachmentId": { - "description": "Unique identifier for this attachment reference instance. UUID recommended", + "description": "Unique identifier for this attachment reference instance, not a content digest. UUID recommended", "type": "string", "minLength": 1 }, @@ -359,7 +359,7 @@ "type": "string" }, "path": { - "description": "Path or URI to the attachment", + "description": "Opaque attachment reference (URL or file path). Prefer a durable reference when available; an original local path may be retained in producer-namespaced extra. Uploading is not required", "type": "string" }, "extra": { @@ -421,7 +421,7 @@ "required": [ "name", "contentType", "path" ], "properties": { "attachmentId": { - "description": "Unique identifier for this attachment reference instance. UUID recommended", + "description": "Unique identifier for this attachment reference instance, not a content digest. UUID recommended", "type": "string", "minLength": 1 }, @@ -434,7 +434,7 @@ "type": "string" }, "path": { - "description": "Path or URI to the attachment", + "description": "Opaque attachment reference (URL or file path). Prefer a durable reference when available; an original local path may be retained in producer-namespaced extra. Uploading is not required", "type": "string" }, "extra": { diff --git a/spec/ctrf.md b/spec/ctrf.md index 92dfa4c..5abee8f 100644 --- a/spec/ctrf.md +++ b/spec/ctrf.md @@ -1635,10 +1635,12 @@ The specification does not impose any particular URI or path semantics. Consumers MUST treat attachment paths as opaque references and MUST NOT assume local filesystem access unless explicitly documented by the producer. +When both a durable artifact reference and a producer-local path are available, producers SHOULD prefer the durable reference in `path`. The original local path MAY be retained in producer-namespaced attachment `extra` when useful. A local file path remains valid when no durable reference is available; producers are not required to upload attachments. + ### 12.4. `attachmentId` **Description:** -A unique identifier for this attachment reference instance. +A unique identifier for this attachment reference instance. It is not a content digest and MUST NOT be interpreted as one. **Requirements:** `attachmentId` is OPTIONAL. @@ -1656,6 +1658,8 @@ An extensibility object containing arbitrary metadata. It MAY contain arbitrary fields. It is the ONLY permitted extension point directly under an attachment object. +Producer-specific original local paths and content-digest metadata MAY be carried in producer-namespaced objects here. CTRF does not standardize their keys or value structure. Producers carrying digest metadata SHOULD document its algorithm and encoding. See Appendix D.9 for an example on both the final result and a previous attempt. + --- ## 13. Step Object @@ -2458,7 +2462,7 @@ to this specification. "required": [ "name", "contentType", "path" ], "properties": { "attachmentId": { - "description": "Unique identifier for this attachment reference instance. UUID recommended", + "description": "Unique identifier for this attachment reference instance, not a content digest. UUID recommended", "type": "string", "minLength": 1 }, @@ -2471,7 +2475,7 @@ to this specification. "type": "string" }, "path": { - "description": "Path or URI to the attachment", + "description": "Opaque attachment reference (URL or file path). Prefer a durable reference when available; an original local path may be retained in producer-namespaced extra. Uploading is not required", "type": "string" }, "extra": { @@ -2533,7 +2537,7 @@ to this specification. "required": [ "name", "contentType", "path" ], "properties": { "attachmentId": { - "description": "Unique identifier for this attachment reference instance. UUID recommended", + "description": "Unique identifier for this attachment reference instance, not a content digest. UUID recommended", "type": "string", "minLength": 1 }, @@ -2546,7 +2550,7 @@ to this specification. "type": "string" }, "path": { - "description": "Path or URI to the attachment", + "description": "Opaque attachment reference (URL or file path). Prefer a durable reference when available; an original local path may be retained in producer-namespaced extra. Uploading is not required", "type": "string" }, "extra": { @@ -3787,3 +3791,84 @@ Each block below matches its named standalone example. } } ``` + + +### D.9. Portable Attachment References + +The final result and previous attempt use durable artifact references in `path`, with their original local paths and digests under the producer-owned `example.reporter` namespace. The `localPath` and `digest` structures shown here are illustrative producer-defined metadata, not standardized CTRF fields. The producer documents SHA-256 with hexadecimal encoding for this example. + +This example matches `examples/portable-attachments.json`. + +```json title="Portable attachment references" +{ + "reportFormat": "CTRF", + "specVersion": "0.2.0", + "results": { + "tool": { + "name": "example-runner" + }, + "summary": { + "tests": 1, + "passed": 1, + "failed": 0, + "skipped": 0, + "pending": 0, + "other": 0, + "flaky": 1, + "start": 1700000000000, + "stop": 1700000000300 + }, + "tests": [ + { + "name": "request succeeds after retry", + "status": "passed", + "duration": 100, + "retries": 1, + "flaky": true, + "attachments": [ + { + "name": "retry.log", + "contentType": "text/plain", + "path": "https://artifacts.example.com/run-42/retry.log", + "extra": { + "example.reporter": { + "localPath": "/work/results/retry.log", + "digest": { + "algorithm": "sha256", + "encoding": "hex", + "value": "385c7da7a539a88aafb70a49549d3642a4e52c0fb23cf58e80f1b5e82e69bc67" + } + } + } + } + ], + "retryAttempts": [ + { + "attempt": 1, + "status": "failed", + "duration": 200, + "message": "Request timed out", + "attachments": [ + { + "name": "initial.log", + "contentType": "text/plain", + "path": "https://artifacts.example.com/run-42/initial.log", + "extra": { + "example.reporter": { + "localPath": "/work/results/initial.log", + "digest": { + "algorithm": "sha256", + "encoding": "hex", + "value": "fe37d563cf87595eb141374a73f3f5e88cf7f097d56eebc2c34e766bdc66f7c7" + } + } + } + } + ] + } + ] + } + ] + } +} +``` diff --git a/tests/ctrf.test.json b/tests/ctrf.test.json index 41af69f..d41b390 100644 --- a/tests/ctrf.test.json +++ b/tests/ctrf.test.json @@ -208,6 +208,11 @@ "description": "Merged coordinated retry report", "valid": true, "dataPath": "../examples/coordinated-retry-merged.json" + }, + { + "description": "Durable attachment references with producer-defined local paths and digests", + "valid": true, + "dataPath": "../examples/portable-attachments.json" } ] } diff --git a/tests/normative/valid-documents.test.json b/tests/normative/valid-documents.test.json index 8c28c54..72b69d1 100644 --- a/tests/normative/valid-documents.test.json +++ b/tests/normative/valid-documents.test.json @@ -63,6 +63,11 @@ "description": "Merged coordinated retry report", "valid": true, "dataPath": "../../examples/coordinated-retry-merged.json" + }, + { + "description": "Durable attachment references with producer-defined local paths and digests", + "valid": true, + "dataPath": "../../examples/portable-attachments.json" } ] } From 37375a3ee707158c6ce922d48eba6244b38f30d9 Mon Sep 17 00:00:00 2001 From: Matthew Thomas <25988124+Ma11hewThomas@users.noreply.github.com> Date: Sun, 25 Oct 2026 17:38:29 +0000 Subject: [PATCH 2/2] Simplify attachment guidance and remove dedicated example --- examples/portable-attachments.json | 71 ------------------ schema/ctrf.schema.json | 8 +- spec/ctrf.md | 91 ++--------------------- tests/ctrf.test.json | 5 -- tests/normative/valid-documents.test.json | 5 -- 5 files changed, 9 insertions(+), 171 deletions(-) delete mode 100644 examples/portable-attachments.json diff --git a/examples/portable-attachments.json b/examples/portable-attachments.json deleted file mode 100644 index 1f3f2c9..0000000 --- a/examples/portable-attachments.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "reportFormat": "CTRF", - "specVersion": "0.2.0", - "results": { - "tool": { - "name": "example-runner" - }, - "summary": { - "tests": 1, - "passed": 1, - "failed": 0, - "skipped": 0, - "pending": 0, - "other": 0, - "flaky": 1, - "start": 1700000000000, - "stop": 1700000000300 - }, - "tests": [ - { - "name": "request succeeds after retry", - "status": "passed", - "duration": 100, - "retries": 1, - "flaky": true, - "attachments": [ - { - "name": "retry.log", - "contentType": "text/plain", - "path": "https://artifacts.example.com/run-42/retry.log", - "extra": { - "example.reporter": { - "localPath": "/work/results/retry.log", - "digest": { - "algorithm": "sha256", - "encoding": "hex", - "value": "385c7da7a539a88aafb70a49549d3642a4e52c0fb23cf58e80f1b5e82e69bc67" - } - } - } - } - ], - "retryAttempts": [ - { - "attempt": 1, - "status": "failed", - "duration": 200, - "message": "Request timed out", - "attachments": [ - { - "name": "initial.log", - "contentType": "text/plain", - "path": "https://artifacts.example.com/run-42/initial.log", - "extra": { - "example.reporter": { - "localPath": "/work/results/initial.log", - "digest": { - "algorithm": "sha256", - "encoding": "hex", - "value": "fe37d563cf87595eb141374a73f3f5e88cf7f097d56eebc2c34e766bdc66f7c7" - } - } - } - } - ] - } - ] - } - ] - } -} diff --git a/schema/ctrf.schema.json b/schema/ctrf.schema.json index 1195298..761d395 100644 --- a/schema/ctrf.schema.json +++ b/schema/ctrf.schema.json @@ -346,7 +346,7 @@ "required": [ "name", "contentType", "path" ], "properties": { "attachmentId": { - "description": "Unique identifier for this attachment reference instance, not a content digest. UUID recommended", + "description": "Unique identifier for this attachment reference instance. UUID recommended", "type": "string", "minLength": 1 }, @@ -359,7 +359,7 @@ "type": "string" }, "path": { - "description": "Opaque attachment reference (URL or file path). Prefer a durable reference when available; an original local path may be retained in producer-namespaced extra. Uploading is not required", + "description": "Opaque reference to the attachment (URL or file path)", "type": "string" }, "extra": { @@ -421,7 +421,7 @@ "required": [ "name", "contentType", "path" ], "properties": { "attachmentId": { - "description": "Unique identifier for this attachment reference instance, not a content digest. UUID recommended", + "description": "Unique identifier for this attachment reference instance. UUID recommended", "type": "string", "minLength": 1 }, @@ -434,7 +434,7 @@ "type": "string" }, "path": { - "description": "Opaque attachment reference (URL or file path). Prefer a durable reference when available; an original local path may be retained in producer-namespaced extra. Uploading is not required", + "description": "Opaque reference to the attachment (URL or file path)", "type": "string" }, "extra": { diff --git a/spec/ctrf.md b/spec/ctrf.md index 6ab7d67..bd0bbaf 100644 --- a/spec/ctrf.md +++ b/spec/ctrf.md @@ -1656,7 +1656,7 @@ An extensibility object containing arbitrary metadata. It MAY contain arbitrary fields. It is the ONLY permitted extension point directly under an attachment object. -Producer-specific original local paths and content-digest metadata MAY be carried in producer-namespaced objects here. CTRF does not standardize their keys or value structure. Producers carrying digest metadata SHOULD document its algorithm and encoding. See Appendix D.9 for an example on both the final result and a previous attempt. +Producer-specific original local paths and content-digest metadata MAY be carried in producer-namespaced objects here. CTRF does not standardize their keys or value structure. Producers carrying digest metadata SHOULD document its algorithm and encoding. --- @@ -2460,7 +2460,7 @@ to this specification. "required": [ "name", "contentType", "path" ], "properties": { "attachmentId": { - "description": "Unique identifier for this attachment reference instance, not a content digest. UUID recommended", + "description": "Unique identifier for this attachment reference instance. UUID recommended", "type": "string", "minLength": 1 }, @@ -2473,7 +2473,7 @@ to this specification. "type": "string" }, "path": { - "description": "Opaque attachment reference (URL or file path). Prefer a durable reference when available; an original local path may be retained in producer-namespaced extra. Uploading is not required", + "description": "Opaque reference to the attachment (URL or file path)", "type": "string" }, "extra": { @@ -2535,7 +2535,7 @@ to this specification. "required": [ "name", "contentType", "path" ], "properties": { "attachmentId": { - "description": "Unique identifier for this attachment reference instance, not a content digest. UUID recommended", + "description": "Unique identifier for this attachment reference instance. UUID recommended", "type": "string", "minLength": 1 }, @@ -2548,7 +2548,7 @@ to this specification. "type": "string" }, "path": { - "description": "Opaque attachment reference (URL or file path). Prefer a durable reference when available; an original local path may be retained in producer-namespaced extra. Uploading is not required", + "description": "Opaque reference to the attachment (URL or file path)", "type": "string" }, "extra": { @@ -3777,84 +3777,3 @@ Each block below matches its named standalone example. } } ``` - - -### D.9. Portable Attachment References - -The final result and previous attempt use durable artifact references in `path`, with their original local paths and digests under the producer-owned `example.reporter` namespace. The `localPath` and `digest` structures shown here are illustrative producer-defined metadata, not standardized CTRF fields. The producer documents SHA-256 with hexadecimal encoding for this example. - -This example matches `examples/portable-attachments.json`. - -```json title="Portable attachment references" -{ - "reportFormat": "CTRF", - "specVersion": "0.2.0", - "results": { - "tool": { - "name": "example-runner" - }, - "summary": { - "tests": 1, - "passed": 1, - "failed": 0, - "skipped": 0, - "pending": 0, - "other": 0, - "flaky": 1, - "start": 1700000000000, - "stop": 1700000000300 - }, - "tests": [ - { - "name": "request succeeds after retry", - "status": "passed", - "duration": 100, - "retries": 1, - "flaky": true, - "attachments": [ - { - "name": "retry.log", - "contentType": "text/plain", - "path": "https://artifacts.example.com/run-42/retry.log", - "extra": { - "example.reporter": { - "localPath": "/work/results/retry.log", - "digest": { - "algorithm": "sha256", - "encoding": "hex", - "value": "385c7da7a539a88aafb70a49549d3642a4e52c0fb23cf58e80f1b5e82e69bc67" - } - } - } - } - ], - "retryAttempts": [ - { - "attempt": 1, - "status": "failed", - "duration": 200, - "message": "Request timed out", - "attachments": [ - { - "name": "initial.log", - "contentType": "text/plain", - "path": "https://artifacts.example.com/run-42/initial.log", - "extra": { - "example.reporter": { - "localPath": "/work/results/initial.log", - "digest": { - "algorithm": "sha256", - "encoding": "hex", - "value": "fe37d563cf87595eb141374a73f3f5e88cf7f097d56eebc2c34e766bdc66f7c7" - } - } - } - } - ] - } - ] - } - ] - } -} -``` diff --git a/tests/ctrf.test.json b/tests/ctrf.test.json index d41b390..41af69f 100644 --- a/tests/ctrf.test.json +++ b/tests/ctrf.test.json @@ -208,11 +208,6 @@ "description": "Merged coordinated retry report", "valid": true, "dataPath": "../examples/coordinated-retry-merged.json" - }, - { - "description": "Durable attachment references with producer-defined local paths and digests", - "valid": true, - "dataPath": "../examples/portable-attachments.json" } ] } diff --git a/tests/normative/valid-documents.test.json b/tests/normative/valid-documents.test.json index 72b69d1..8c28c54 100644 --- a/tests/normative/valid-documents.test.json +++ b/tests/normative/valid-documents.test.json @@ -63,11 +63,6 @@ "description": "Merged coordinated retry report", "valid": true, "dataPath": "../../examples/coordinated-retry-merged.json" - }, - { - "description": "Durable attachment references with producer-defined local paths and digests", - "valid": true, - "dataPath": "../../examples/portable-attachments.json" } ] }