diff --git a/.gitea/workflows/verify.yml b/.gitea/workflows/verify.yml new file mode 100644 index 0000000..b84a7cb --- /dev/null +++ b/.gitea/workflows/verify.yml @@ -0,0 +1,39 @@ +name: Verify + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +concurrency: + group: verify-${{ gitea.repository }}-${{ gitea.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 45 + env: + CI: "true" + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + - name: Select declared npm version + run: npm install --global npm@11.17.0 + - name: Install dependencies + run: npm ci + - name: Audit runtime dependencies + run: npm audit --omit=dev --audit-level=moderate + - name: Check, test, and build + run: npm run check + - name: Install browser engines + run: npx playwright install --with-deps chromium firefox webkit + - name: Browser tests + run: npm run test:browser diff --git a/CHANGELOG.md b/CHANGELOG.md index c7422bb..36644ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.2.0 - 2026-09-02 + +- Add shared evidence envelopes, a reusable format catalogue and bounded local + corpus runs for comparing measured round-trip behaviour across recipes. + ## 0.1.0 - 2026-09-01 - Add property-aware conversion graphs for data, image, audio/video, and diff --git a/README.md b/README.md index fe41eb0..5f35fff 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Explore conversion paths, state the expected preservation boundary, and measure what a representative round trip actually changes. Format Lab is a local-first module in the add·ideas Toolbox. -## v0.1.0 capabilities +## v0.2.0 capabilities - Browse directed format graphs for structured data, images, audio/video, and subtitles @@ -20,11 +20,14 @@ module in the add·ideas Toolbox. untested when it is outside the canonical model - Export the target data and a deterministic evidence report containing the catalog version, exact path, expectations, measurements, and notices +- Export a versioned portable evidence contract shared with other audit tools, + inspect a broad masked-offset file-signature catalog, and download a + deterministic edge-case corpus as individually named fixtures plus manifest ## Honest scope The image, audio/video, and subtitle graphs are planning contracts linked to the -specialized Toolbox apps; v0.1.0 does not duplicate their codec or renderer +specialized Toolbox apps; v0.2.0 does not duplicate their codec or renderer engines. Catalog statuses are conservative expectations, not measurements of a particular file or encoder. The executable data lab supports records only—not arbitrary JSON documents, XML mixed content/namespaces, or an external CSV type @@ -34,6 +37,10 @@ Inputs are capped at 2 MiB, 10,000 records, 200 fields per record, 500,000 top-level values, and 8 MiB output. CSV cells are strings and spreadsheet-formula prefixes are neutralized; the resulting semantic change is visible in the round-trip measurement. +Signature matches identify byte patterns and likely containers only; they do +not validate an entire file or establish that its content is safe. Parquet, +Arrow, YAML, AVIF/HEIC/TIFF/GIF, Ogg/M4A/MOV/AAC and SBV/SAMI appear in the +planning catalog without inventing executable conversion support. ## Development diff --git a/SOURCE.md b/SOURCE.md index 2a81e06..c64a79b 100644 --- a/SOURCE.md +++ b/SOURCE.md @@ -1,7 +1,7 @@ # Corresponding source -The corresponding source for Format Lab 0.1.0 is available at: +The corresponding source for Format Lab 0.2.0 is available at: -https://git.add-ideas.de/lotobo/format-lab/src/tag/v0.1.0 +https://git.add-ideas.de/lotobo/format-lab/src/tag/v0.2.0 Build with Node.js 22, npm 11, `npm ci`, and `npm run release:artifact`. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index b763bb7..70e1f6b 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -1,12 +1,12 @@ # Third-party notices -Format Lab 0.1.0 directly depends on these runtime packages: +Format Lab 0.2.0 directly depends on these runtime packages: | Package | Pinned version | Declared licence | | -------------------------------- | -------------: | ---------------- | -| `@add-ideas/toolbox-contract` | 0.2.3 | Apache-2.0 | -| `@add-ideas/toolbox-helpers` | 0.1.0 | GPL-3.0-or-later | -| `@add-ideas/toolbox-shell-react` | 0.2.3 | Apache-2.0 | +| `@add-ideas/toolbox-contract` | 0.3.0 | Apache-2.0 | +| `@add-ideas/toolbox-helpers` | 0.2.0 | GPL-3.0-or-later | +| `@add-ideas/toolbox-shell-react` | 0.3.0 | Apache-2.0 | | `react` | 19.2.8 | MIT | | `react-dom` | 19.2.8 | MIT | diff --git a/package-lock.json b/package-lock.json index 725cb29..e9e31eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,22 @@ { "name": "format-lab", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "format-lab", - "version": "0.1.0", + "version": "0.2.0", "license": "GPL-3.0-or-later", "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3", - "@add-ideas/toolbox-helpers": "0.1.0", - "@add-ideas/toolbox-shell-react": "0.2.3", + "@add-ideas/toolbox-contract": "0.3.0", + "@add-ideas/toolbox-helpers": "0.2.0", + "@add-ideas/toolbox-shell-react": "0.3.0", "react": "19.2.8", "react-dom": "19.2.8" }, "devDependencies": { - "@add-ideas/toolbox-testkit": "0.2.3", + "@add-ideas/toolbox-testkit": "0.3.0", "@eslint/js": "10.0.1", "@playwright/test": "1.62.1", "@testing-library/jest-dom": "6.9.1", @@ -42,24 +42,25 @@ } }, "node_modules/@add-ideas/toolbox-contract": { - "version": "0.2.3", - "license": "Apache-2.0", - "engines": { - "node": ">=20" - } + "version": "0.3.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-contract/-/0.3.0/toolbox-contract-0.3.0.tgz", + "integrity": "sha512-dKrK7BjOFwqJaBfJuhKxZKIld4sH0AKjEn6a0yLnbdMUFY+fFv4VSLGV2tNSBD016gumc2iNqOjUj/ld7x4rtA==", + "license": "Apache-2.0" }, "node_modules/@add-ideas/toolbox-helpers": { - "version": "0.1.0", + "version": "0.2.0", "license": "GPL-3.0-or-later", "engines": { "node": ">=22" } }, "node_modules/@add-ideas/toolbox-shell-react": { - "version": "0.2.3", + "version": "0.3.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-shell-react/-/0.3.0/toolbox-shell-react-0.3.0.tgz", + "integrity": "sha512-74p6JzAOG0YCAKdlc1hLofV4ZIko7vb448S75cIiM88PKm93EHl5VD7g8YVyfM56Ui97UY9dmy+Whiq4sGzpsg==", "license": "Apache-2.0", "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3" + "@add-ideas/toolbox-contract": "0.3.0" }, "peerDependencies": { "react": ">=18 <20", @@ -67,17 +68,16 @@ } }, "node_modules/@add-ideas/toolbox-testkit": { - "version": "0.2.3", + "version": "0.3.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-testkit/-/0.3.0/toolbox-testkit-0.3.0.tgz", + "integrity": "sha512-4Fk+oSvZFspOMIXr8Xy040nhAaBsIQAzsGyXWSpjn3+k3yBKq7nB1r5zCHhsXzfdLzvPDAx2KcmSNOhM330D9w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3" + "@add-ideas/toolbox-contract": "0.3.0" }, "bin": { "toolbox-check": "dist/cli.js" - }, - "engines": { - "node": ">=20" } }, "node_modules/@adobe/css-tools": { diff --git a/package.json b/package.json index f829928..292e749 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "format-lab", - "version": "0.1.0", + "version": "0.2.0", "description": "Explore conversion paths and measure format round-trip losses locally.", "license": "GPL-3.0-or-later", "author": "Albrecht Degering", @@ -39,14 +39,14 @@ "release:artifact": "npm run check && npm run test:browser && npm run package:release -- --force" }, "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3", - "@add-ideas/toolbox-helpers": "0.1.0", - "@add-ideas/toolbox-shell-react": "0.2.3", + "@add-ideas/toolbox-contract": "0.3.0", + "@add-ideas/toolbox-helpers": "0.2.0", + "@add-ideas/toolbox-shell-react": "0.3.0", "react": "19.2.8", "react-dom": "19.2.8" }, "devDependencies": { - "@add-ideas/toolbox-testkit": "0.2.3", + "@add-ideas/toolbox-testkit": "0.3.0", "@eslint/js": "10.0.1", "@playwright/test": "1.62.1", "@testing-library/jest-dom": "6.9.1", diff --git a/playwright.config.ts b/playwright.config.ts index 1997174..f388aab 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -15,7 +15,25 @@ export default defineConfig({ timeout: 180_000, }, projects: [ - { name: "chromium", use: { ...devices["Desktop Chrome"] } }, - { name: "firefox", use: { ...devices["Desktop Firefox"] } }, + { + name: "chromium", + testIgnore: /responsive\.spec\.ts/, + use: { ...devices["Desktop Chrome"] }, + }, + { + name: "firefox", + testIgnore: /responsive\.spec\.ts/, + use: { ...devices["Desktop Firefox"] }, + }, + { + name: "webkit", + testIgnore: /responsive\.spec\.ts/, + use: { ...devices["Desktop Safari"] }, + }, + { + name: "mobile-chromium", + testMatch: /responsive\.spec\.ts/, + use: { ...devices["Pixel 5"] }, + }, ], }); diff --git a/public/CHANGELOG.md b/public/CHANGELOG.md index c7422bb..36644ba 100644 --- a/public/CHANGELOG.md +++ b/public/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.2.0 - 2026-09-02 + +- Add shared evidence envelopes, a reusable format catalogue and bounded local + corpus runs for comparing measured round-trip behaviour across recipes. + ## 0.1.0 - 2026-09-01 - Add property-aware conversion graphs for data, image, audio/video, and diff --git a/public/LICENSES/npm-runtime-licenses.txt b/public/LICENSES/npm-runtime-licenses.txt index 45a7c95..4f412f1 100644 --- a/public/LICENSES/npm-runtime-licenses.txt +++ b/public/LICENSES/npm-runtime-licenses.txt @@ -1,5 +1,5 @@ ============================================================================== -@add-ideas/toolbox-contract@0.2.3 +@add-ideas/toolbox-contract@0.3.0 Declared licence: Apache-2.0 ============================================================================== --- LICENSE --- @@ -198,7 +198,7 @@ Declared licence: Apache-2.0 ============================================================================== -@add-ideas/toolbox-helpers@0.1.0 +@add-ideas/toolbox-helpers@0.2.0 Declared licence: GPL-3.0-or-later ============================================================================== --- LICENSE --- @@ -879,7 +879,7 @@ Public License instead of this License. But first, please read ============================================================================== -@add-ideas/toolbox-shell-react@0.2.3 +@add-ideas/toolbox-shell-react@0.3.0 Declared licence: Apache-2.0 ============================================================================== --- LICENSE --- diff --git a/public/README.md b/public/README.md index fe41eb0..5f35fff 100644 --- a/public/README.md +++ b/public/README.md @@ -4,7 +4,7 @@ Explore conversion paths, state the expected preservation boundary, and measure what a representative round trip actually changes. Format Lab is a local-first module in the add·ideas Toolbox. -## v0.1.0 capabilities +## v0.2.0 capabilities - Browse directed format graphs for structured data, images, audio/video, and subtitles @@ -20,11 +20,14 @@ module in the add·ideas Toolbox. untested when it is outside the canonical model - Export the target data and a deterministic evidence report containing the catalog version, exact path, expectations, measurements, and notices +- Export a versioned portable evidence contract shared with other audit tools, + inspect a broad masked-offset file-signature catalog, and download a + deterministic edge-case corpus as individually named fixtures plus manifest ## Honest scope The image, audio/video, and subtitle graphs are planning contracts linked to the -specialized Toolbox apps; v0.1.0 does not duplicate their codec or renderer +specialized Toolbox apps; v0.2.0 does not duplicate their codec or renderer engines. Catalog statuses are conservative expectations, not measurements of a particular file or encoder. The executable data lab supports records only—not arbitrary JSON documents, XML mixed content/namespaces, or an external CSV type @@ -34,6 +37,10 @@ Inputs are capped at 2 MiB, 10,000 records, 200 fields per record, 500,000 top-level values, and 8 MiB output. CSV cells are strings and spreadsheet-formula prefixes are neutralized; the resulting semantic change is visible in the round-trip measurement. +Signature matches identify byte patterns and likely containers only; they do +not validate an entire file or establish that its content is safe. Parquet, +Arrow, YAML, AVIF/HEIC/TIFF/GIF, Ogg/M4A/MOV/AAC and SBV/SAMI appear in the +planning catalog without inventing executable conversion support. ## Development diff --git a/public/SOURCE.md b/public/SOURCE.md index 2a81e06..c64a79b 100644 --- a/public/SOURCE.md +++ b/public/SOURCE.md @@ -1,7 +1,7 @@ # Corresponding source -The corresponding source for Format Lab 0.1.0 is available at: +The corresponding source for Format Lab 0.2.0 is available at: -https://git.add-ideas.de/lotobo/format-lab/src/tag/v0.1.0 +https://git.add-ideas.de/lotobo/format-lab/src/tag/v0.2.0 Build with Node.js 22, npm 11, `npm ci`, and `npm run release:artifact`. diff --git a/public/THIRD_PARTY_NOTICES.md b/public/THIRD_PARTY_NOTICES.md index b763bb7..70e1f6b 100644 --- a/public/THIRD_PARTY_NOTICES.md +++ b/public/THIRD_PARTY_NOTICES.md @@ -1,12 +1,12 @@ # Third-party notices -Format Lab 0.1.0 directly depends on these runtime packages: +Format Lab 0.2.0 directly depends on these runtime packages: | Package | Pinned version | Declared licence | | -------------------------------- | -------------: | ---------------- | -| `@add-ideas/toolbox-contract` | 0.2.3 | Apache-2.0 | -| `@add-ideas/toolbox-helpers` | 0.1.0 | GPL-3.0-or-later | -| `@add-ideas/toolbox-shell-react` | 0.2.3 | Apache-2.0 | +| `@add-ideas/toolbox-contract` | 0.3.0 | Apache-2.0 | +| `@add-ideas/toolbox-helpers` | 0.2.0 | GPL-3.0-or-later | +| `@add-ideas/toolbox-shell-react` | 0.3.0 | Apache-2.0 | | `react` | 19.2.8 | MIT | | `react-dom` | 19.2.8 | MIT | diff --git a/public/sw.js b/public/sw.js index 4b9d798..4dedbe3 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,5 +1,5 @@ const CACHE_PREFIX = "format-lab-shell-"; -const CACHE_NAME = CACHE_PREFIX + "0.1.0"; +const CACHE_NAME = CACHE_PREFIX + "0.2.0"; const CORE = ["./", "./manifest.webmanifest", "./favicon.svg"]; self.addEventListener("install", (event) => { event.waitUntil( diff --git a/public/toolbox-app.json b/public/toolbox-app.json index cb6cbce..9259793 100644 --- a/public/toolbox-app.json +++ b/public/toolbox-app.json @@ -3,7 +3,7 @@ "schemaVersion": 1, "id": "de.add-ideas.format-lab", "name": "Format Lab", - "version": "0.1.0", + "version": "0.2.0", "description": "Explore conversion paths and measure information loss locally.", "entry": "./", "icon": "./favicon.svg", @@ -28,6 +28,21 @@ "crossOriginIsolated": false, "topLevelContext": false }, + "io": { + "accepts": [ + { "mediaType": "application/json", "extensions": [".json"] }, + { "mediaType": "application/x-ndjson", "extensions": [".ndjson"] }, + { "mediaType": "text/csv", "extensions": [".csv"] }, + { "mediaType": "application/xml", "extensions": [".xml"] } + ], + "produces": [ + { "mediaType": "application/json", "extensions": [".json"] }, + { "mediaType": "application/x-ndjson", "extensions": [".ndjson"] }, + { "mediaType": "text/csv", "extensions": [".csv"] }, + { "mediaType": "application/xml", "extensions": [".xml"] } + ] + }, + "capabilities": { "required": [], "optional": [] }, "privacy": { "processing": "local", "fileUploads": true, diff --git a/src/components/Workbench.tsx b/src/components/Workbench.tsx index 3ab2540..aec98e1 100644 --- a/src/components/Workbench.tsx +++ b/src/components/Workbench.tsx @@ -1,7 +1,7 @@ import { useMemo, useState, type ChangeEvent } from "react"; import { - stableStringify, triggerBlobDownload, + triggerBlobDownloads, } from "@add-ideas/toolbox-helpers"; import { domains, @@ -19,6 +19,13 @@ import { type DataFormat, type RoundTripResult, } from "../lab/data"; +import { exportCorpusFiles } from "../lab/corpus"; +import { serializeEvidence } from "../lab/evidence"; +import { + formatSignatures, + identifyBySignature, + type FormatSignature, +} from "../lab/signatures"; const SAMPLE = `[ { @@ -159,6 +166,10 @@ export function Workbench() { "target", ); const [error, setError] = useState(); + const [signatureMatches, setSignatureMatches] = useState( + [], + ); + const [signatureName, setSignatureName] = useState("No file inspected"); const domainFormats = formats.filter((format) => format.domain === domain); const routes = useMemo( @@ -172,18 +183,7 @@ export function Workbench() { domainFormats.some((format) => format.id === edge.to), ); const report = useMemo( - () => - `${stableStringify( - { - catalogVersion: "format-catalog-v1", - route: record.route.formats, - expected: record.route.summary, - measured: record.result.measurements, - notes: record.result.notes, - }, - 2, - { maxTextChars: 2 * 1024 * 1024, maxDepth: 16, maxNodes: 20_000 }, - )}\n`, + () => serializeEvidence(record.result.evidence), [record], ); @@ -240,6 +240,24 @@ export function Workbench() { setError(undefined); }; + const inspectSignature = async (event: ChangeEvent) => { + const file = event.target.files?.[0]; + event.target.value = ""; + if (!file) return; + const bytes = new Uint8Array(await file.slice(0, 64).arrayBuffer()); + setSignatureName(file.name); + setSignatureMatches(identifyBySignature(bytes)); + }; + + const downloadCorpus = () => + triggerBlobDownloads( + exportCorpusFiles().map((file) => ({ + blob: new Blob([file.content], { type: file.mediaType }), + filename: file.path.replaceAll("/", "__"), + })), + { order: "filename", maximumFiles: 20 }, + ); + const measuredByProperty = Object.fromEntries( record.result.measurements.map((measurement) => [ measurement.property, @@ -408,6 +426,68 @@ export function Workbench() { +
+
+
+

+ Signature catalog and regression corpus +

+

+ {formatSignatures.length} bounded byte signatures identify likely + containers; they do not claim structural validity. {signatureName} + . +

+
+
+ + +
+
+

+ {signatureMatches.length + ? signatureMatches + .map( + (match) => + `${match.label} (${match.confidence}: ${match.note})`, + ) + .join(" · ") + : "No catalog signature matched the inspected prefix."} +

+
+ Browse all signature definitions +
+ + + + + + + + + + + {formatSignatures.map((item) => ( + + + + + + + ))} + +
FormatOffsetConfidenceBoundary
{item.label}{item.offset}{item.confidence}{item.note}
+
+
+
+
@@ -510,6 +590,16 @@ export function Workbench() { Download evidence report
+
+ Portable evidence JSON +