From 122bd655f89351c9df958290d2dc52e69c0e20fe Mon Sep 17 00:00:00 2001 From: Kam Date: Thu, 24 Sep 2026 16:30:41 +1000 Subject: [PATCH] fix(map): expose the canonical type contract --- README.md | 9 +- UPSTREAM.md | 2 +- build.mjs | 1 + dist/content-review-0.1.contract.json | 58 +++++++++++ dist/content-review-0.1.schema.json | 7 +- dist/index.d.ts | 2 +- dist/index.js | 2 +- dist/map-0.1.schema.json | 142 ++++++++++++++++++++------ dist/map.d.ts | 1 + dist/map.js | 4 + package.json | 1 + src/content-review-0.1.contract.json | 58 +++++++++++ src/content-review-0.1.schema.json | 7 +- src/index.ts | 1 + src/map-0.1.schema.json | 142 ++++++++++++++++++++------ src/map.ts | 5 + test/map-description.json | 2 +- test/map-request.json | 3 +- test/map-result.json | 2 +- test/package.test.mjs | 3 + 20 files changed, 368 insertions(+), 84 deletions(-) create mode 100644 dist/content-review-0.1.contract.json create mode 100644 src/content-review-0.1.contract.json diff --git a/README.md b/README.md index 7042ef4..935aa86 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,17 @@ Node.js 22 or newer is required. The package is ESM and includes TypeScript decl ## Validate MAP documents ```js -import { assertMapDocument, assertContentReviewRequest, getMapSchema } from 'mailschema'; +import { + assertMapDocument, + assertContentReviewRequest, + getContentReviewContract, + getMapSchema, +} from 'mailschema'; assertMapDocument(description); assertContentReviewRequest(request); const mapSchema = getMapSchema(); +const contentReviewContract = getContentReviewContract(); ``` The CLI performs the same checks against local JSON files: @@ -56,6 +62,7 @@ Raw Draft 2020-12 schemas are exported as: - `mailschema/map-0.1.schema.json` - `mailschema/content-review-0.1.schema.json` +- `mailschema/content-review-0.1.contract.json` - `mailschema/contribution.schema.json` ## Trust boundary diff --git a/UPSTREAM.md b/UPSTREAM.md index d807a12..cf6c69c 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -4,4 +4,4 @@ Version `0.1.3` was prepared from [`mailschema/mailschema@eddc745`](https://gith The included contribution schema has SHA-256 digest `a8a241b4837971a6251bcc135c704283536f4fb825fbbaedaf1598c6e688d564`. The canonical schemas live in the main MailSchema repository; this repository owns the JavaScript and TypeScript package surface and its release history. -The unreleased MAP contract projection is derived from [`mailschema/mailschema@ab91b37`](https://github.com/mailschema/mailschema/commit/ab91b37d29dbf95a2e208a34a144bf5f7c26d0a6). Its MAP schema digest is `d908a960f53fade9ab47876bf69b1b3d037adeb7096091e22ca71fbc3909429c`; its Content Review schema digest is `dba2a2f1e57f843f43080f5a40ced2d9c84e7c7036ec306c371a82d39f6d6c9f`. These files are projections of that source commit and do not independently define MAP. +The unreleased MAP contract projection is derived from [`mailschema/mailschema@e2f2cc6`](https://github.com/mailschema/mailschema/commit/e2f2cc6983173e5aba1da27f1f8bff37336efd77). Its MAP schema digest is `c39befeecf8643e8beda1f53a8d1411482b68ddf14974f3806cfa95776103f85`; its Content Review schema digest is `10e2263c820ba3a6ef0381c377078e2c1f324c3b142a6f8caf92b298cc66c692`; and its Content Review contract byte digest is `754ab7245ddb09ff4da000fccc60d746724379d4146ea227973e5d87f105f800`. These files are exact projections of that source commit and do not independently define MAP. diff --git a/build.mjs b/build.mjs index 321b720..c6405ba 100644 --- a/build.mjs +++ b/build.mjs @@ -14,6 +14,7 @@ for (const name of [ 'contribution.schema.json', 'map-0.1.schema.json', 'content-review-0.1.schema.json', + 'content-review-0.1.contract.json', ]) { await copyFile(resolve(root, 'src', name), resolve(root, 'dist', name)); } diff --git a/dist/content-review-0.1.contract.json b/dist/content-review-0.1.contract.json new file mode 100644 index 0000000..922baef --- /dev/null +++ b/dist/content-review-0.1.contract.json @@ -0,0 +1,58 @@ +{ + "kind": "MapTypeContract", + "id": "https://mailschema.org/types/content-review", + "version": "0.1", + "profile": "https://mailschema.org/profiles/map/0.1", + "target": "Service-owned content at the exact revision and digest named in the request.", + "requestSchema": { + "url": "https://mailschema.org/schemas/content-review-0.1.schema.json", + "canonicalDigest": "sha-256:cdad1149ab193d1bf71cb9463e3c4b7331ed56b0123e225adf68f70a2022b29d" + }, + "operations": [ + { + "id": "request-changes", + "effect": "Record review feedback on the named revision.", + "results": [ + { + "state": "accepted", + "outputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "feedbackRecorded": { "const": true } + }, + "required": ["feedbackRecorded"] + } + } + ] + }, + { + "id": "approve", + "effect": "Record approval of the named revision.", + "results": [ + { + "state": "approval-required", + "outputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "approvalUrl": { "type": "string", "format": "uri", "pattern": "^https://" } + }, + "required": ["approvalUrl"] + } + }, + { + "state": "completed", + "outputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "decision": { "const": "approved" } + }, + "required": ["decision"] + } + } + ] + } + ] +} diff --git a/dist/content-review-0.1.schema.json b/dist/content-review-0.1.schema.json index 61c1f22..57fc90f 100644 --- a/dist/content-review-0.1.schema.json +++ b/dist/content-review-0.1.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://mailschema.org/schemas/content-review-0.1.schema.json", - "title": "Content Review 0.1 MAP request", + "title": "Content Review 0.1 MAP request binding", "allOf": [ { "$ref": "https://mailschema.org/schemas/map-0.1.schema.json#/$defs/request" }, { @@ -11,10 +11,7 @@ "type": "object", "properties": { "id": { "const": "https://mailschema.org/types/content-review" }, - "version": { "const": "0.1" }, - "recordDigest": { - "const": "sha-256:0e6365df1bf904f2475f972ec66a5561edc0bfaae69fd7a1b41f53b40bf8ac1c" - } + "version": { "const": "0.1" } } } } diff --git a/dist/index.d.ts b/dist/index.d.ts index 48e711c..ddfd4c0 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -2,7 +2,7 @@ export { contributionErrors, assertContribution, assertTypeRecord, referenceErro export type { CatalogView } from './validation.js'; export { typeStages } from './model.js'; export type { Party, TypeDefinition, TypeRecord, Contribution, Implementation } from './model.js'; -export { mapErrors, assertMapDocument, contentReviewRequestErrors, assertContentReviewRequest, getMapSchema, getContentReviewSchema, } from './map.js'; +export { mapErrors, assertMapDocument, contentReviewRequestErrors, assertContentReviewRequest, getMapSchema, getContentReviewSchema, getContentReviewContract, } from './map.js'; /** Return an independent copy of the contribution JSON Schema (Draft 2020-12). */ export declare function getContributionSchema(): Record; /** Return the schema for an expanded Registry record, including attribution and history. */ diff --git a/dist/index.js b/dist/index.js index fda929d..6e2dd04 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2,7 +2,7 @@ import schema from './contribution.schema.json' with { type: 'json' }; export { contributionErrors, assertContribution, assertTypeRecord, referenceErrors, } from './validation.js'; export { typeStages } from './model.js'; -export { mapErrors, assertMapDocument, contentReviewRequestErrors, assertContentReviewRequest, getMapSchema, getContentReviewSchema, } from './map.js'; +export { mapErrors, assertMapDocument, contentReviewRequestErrors, assertContentReviewRequest, getMapSchema, getContentReviewSchema, getContentReviewContract, } from './map.js'; /** Return an independent copy of the contribution JSON Schema (Draft 2020-12). */ export function getContributionSchema() { return structuredClone(schema); diff --git a/dist/map-0.1.schema.json b/dist/map-0.1.schema.json index 2cc1dfe..352cd5d 100644 --- a/dist/map-0.1.schema.json +++ b/dist/map-0.1.schema.json @@ -32,7 +32,7 @@ "type": "string", "pattern": "^sha-256:[a-f0-9]{64}$" }, - "recordDigest": { + "contractDigest": { "type": "string", "pattern": "^sha-256:[a-f0-9]{64}$" }, @@ -46,9 +46,9 @@ "pattern": "^[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:-[a-zA-Z0-9.-]+)?$", "maxLength": 80 }, - "recordDigest": { "$ref": "#/$defs/recordDigest" } + "contractDigest": { "$ref": "#/$defs/contractDigest" } }, - "required": ["id", "version", "recordDigest"] + "required": ["id", "version", "contractDigest"] }, "target": { "type": "object", @@ -172,7 +172,6 @@ "profile": { "$ref": "#/$defs/profile" }, "requestId": { "$ref": "#/$defs/uuidUrn" }, "interactionId": { "$ref": "#/$defs/uuidUrn" }, - "requestedAt": { "type": "string", "format": "date-time" }, "type": { "$ref": "#/$defs/typeReference" }, "operation": { "type": "string", @@ -187,7 +186,6 @@ "profile", "requestId", "interactionId", - "requestedAt", "type", "operation", "target", @@ -273,61 +271,139 @@ }, "target": { "$ref": "#/$defs/target" } }, - "required": [ - "type", - "title", - "status", - "detail", - "instance", - "profile", - "requestId", - "interactionId", - "code" - ], + "required": ["type", "title", "status", "detail"], + "dependentRequired": { + "instance": ["profile", "requestId", "code"], + "profile": ["instance", "requestId", "code"], + "requestId": ["instance", "profile", "code"], + "interactionId": ["instance", "profile", "requestId", "code"], + "code": ["instance", "profile", "requestId"] + }, "allOf": [ + { + "if": { + "properties": { "code": { "not": { "const": "result-not-found" } } }, + "required": ["code"] + }, + "then": { "required": ["interactionId"] } + }, { "if": { "properties": { "code": { "const": "invalid-request" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/invalid-request" }, "status": { "const": 400 } } } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/invalid-request" }, + "status": { "const": 400 } + } + } }, { - "if": { "properties": { "code": { "const": "authentication-required" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/authentication-required" }, "status": { "const": 401 } } } + "if": { + "properties": { "code": { "const": "authentication-required" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/authentication-required" }, + "status": { "const": 401 } + } + } }, { "if": { "properties": { "code": { "const": "refused" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/refused" }, "status": { "const": 403 } } } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/refused" }, + "status": { "const": 403 } + } + } }, { "if": { "properties": { "code": { "const": "result-not-found" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/result-not-found" }, "status": { "const": 404 } } } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/result-not-found" }, + "status": { "const": 404 } + } + } }, { "if": { "properties": { "code": { "const": "stale-target" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/stale-target" }, "status": { "const": 409 } }, "required": ["target"] } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/stale-target" }, + "status": { "const": 409 } + }, + "required": ["target"] + } }, { - "if": { "properties": { "code": { "const": "idempotency-conflict" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/idempotency-conflict" }, "status": { "const": 409 } } } + "if": { + "properties": { "code": { "const": "idempotency-conflict" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/idempotency-conflict" }, + "status": { "const": 409 } + } + } }, { - "if": { "properties": { "code": { "const": "request-in-progress" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/request-in-progress" }, "status": { "const": 409 } } } + "if": { + "properties": { "code": { "const": "request-in-progress" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/request-in-progress" }, + "status": { "const": 409 } + } + } }, { - "if": { "properties": { "code": { "const": "expired-interaction" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/expired-interaction" }, "status": { "const": 410 } } } + "if": { + "properties": { "code": { "const": "expired-interaction" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/expired-interaction" }, + "status": { "const": 410 } + } + } }, { - "if": { "properties": { "code": { "const": "unsupported-profile" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/unsupported-profile" }, "status": { "const": 422 } } } + "if": { + "properties": { "code": { "const": "unsupported-profile" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/unsupported-profile" }, + "status": { "const": 422 } + } + } }, { "if": { "properties": { "code": { "const": "unsupported-type" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/unsupported-type" }, "status": { "const": 422 } } } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/unsupported-type" }, + "status": { "const": 422 } + } + } }, { - "if": { "properties": { "code": { "const": "unsupported-operation" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/unsupported-operation" }, "status": { "const": 422 } } } + "if": { + "properties": { "code": { "const": "unsupported-operation" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/unsupported-operation" }, + "status": { "const": 422 } + } + } } ] } diff --git a/dist/map.d.ts b/dist/map.d.ts index f707e90..fabec40 100644 --- a/dist/map.d.ts +++ b/dist/map.d.ts @@ -4,3 +4,4 @@ export declare function contentReviewRequestErrors(value: unknown): string[]; export declare function assertContentReviewRequest(value: unknown): void; export declare function getMapSchema(): Record; export declare function getContentReviewSchema(): Record; +export declare function getContentReviewContract(): Record; diff --git a/dist/map.js b/dist/map.js index 9762ca2..ca13f84 100644 --- a/dist/map.js +++ b/dist/map.js @@ -2,6 +2,7 @@ import Ajv2020 from 'ajv/dist/2020.js'; import addFormats from 'ajv-formats'; import mapSchema from './map-0.1.schema.json' with { type: 'json' }; import contentReviewSchema from './content-review-0.1.schema.json' with { type: 'json' }; +import contentReviewContract from './content-review-0.1.contract.json' with { type: 'json' }; const ajv = new Ajv2020({ allErrors: true, strict: true, strictRequired: false }); addFormats(ajv); const mapDocument = ajv.compile(mapSchema); @@ -33,3 +34,6 @@ export function getMapSchema() { export function getContentReviewSchema() { return structuredClone(contentReviewSchema); } +export function getContentReviewContract() { + return structuredClone(contentReviewContract); +} diff --git a/package.json b/package.json index dcc1bb1..b4371fe 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "./contribution.schema.json": "./dist/contribution.schema.json", "./map-0.1.schema.json": "./dist/map-0.1.schema.json", "./content-review-0.1.schema.json": "./dist/content-review-0.1.schema.json", + "./content-review-0.1.contract.json": "./dist/content-review-0.1.contract.json", "./package.json": "./package.json" }, "types": "./dist/index.d.ts", diff --git a/src/content-review-0.1.contract.json b/src/content-review-0.1.contract.json new file mode 100644 index 0000000..922baef --- /dev/null +++ b/src/content-review-0.1.contract.json @@ -0,0 +1,58 @@ +{ + "kind": "MapTypeContract", + "id": "https://mailschema.org/types/content-review", + "version": "0.1", + "profile": "https://mailschema.org/profiles/map/0.1", + "target": "Service-owned content at the exact revision and digest named in the request.", + "requestSchema": { + "url": "https://mailschema.org/schemas/content-review-0.1.schema.json", + "canonicalDigest": "sha-256:cdad1149ab193d1bf71cb9463e3c4b7331ed56b0123e225adf68f70a2022b29d" + }, + "operations": [ + { + "id": "request-changes", + "effect": "Record review feedback on the named revision.", + "results": [ + { + "state": "accepted", + "outputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "feedbackRecorded": { "const": true } + }, + "required": ["feedbackRecorded"] + } + } + ] + }, + { + "id": "approve", + "effect": "Record approval of the named revision.", + "results": [ + { + "state": "approval-required", + "outputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "approvalUrl": { "type": "string", "format": "uri", "pattern": "^https://" } + }, + "required": ["approvalUrl"] + } + }, + { + "state": "completed", + "outputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "decision": { "const": "approved" } + }, + "required": ["decision"] + } + } + ] + } + ] +} diff --git a/src/content-review-0.1.schema.json b/src/content-review-0.1.schema.json index 61c1f22..57fc90f 100644 --- a/src/content-review-0.1.schema.json +++ b/src/content-review-0.1.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://mailschema.org/schemas/content-review-0.1.schema.json", - "title": "Content Review 0.1 MAP request", + "title": "Content Review 0.1 MAP request binding", "allOf": [ { "$ref": "https://mailschema.org/schemas/map-0.1.schema.json#/$defs/request" }, { @@ -11,10 +11,7 @@ "type": "object", "properties": { "id": { "const": "https://mailschema.org/types/content-review" }, - "version": { "const": "0.1" }, - "recordDigest": { - "const": "sha-256:0e6365df1bf904f2475f972ec66a5561edc0bfaae69fd7a1b41f53b40bf8ac1c" - } + "version": { "const": "0.1" } } } } diff --git a/src/index.ts b/src/index.ts index 39c4c22..38efb96 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ export { assertContentReviewRequest, getMapSchema, getContentReviewSchema, + getContentReviewContract, } from './map.js'; /** Return an independent copy of the contribution JSON Schema (Draft 2020-12). */ diff --git a/src/map-0.1.schema.json b/src/map-0.1.schema.json index 2cc1dfe..352cd5d 100644 --- a/src/map-0.1.schema.json +++ b/src/map-0.1.schema.json @@ -32,7 +32,7 @@ "type": "string", "pattern": "^sha-256:[a-f0-9]{64}$" }, - "recordDigest": { + "contractDigest": { "type": "string", "pattern": "^sha-256:[a-f0-9]{64}$" }, @@ -46,9 +46,9 @@ "pattern": "^[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:-[a-zA-Z0-9.-]+)?$", "maxLength": 80 }, - "recordDigest": { "$ref": "#/$defs/recordDigest" } + "contractDigest": { "$ref": "#/$defs/contractDigest" } }, - "required": ["id", "version", "recordDigest"] + "required": ["id", "version", "contractDigest"] }, "target": { "type": "object", @@ -172,7 +172,6 @@ "profile": { "$ref": "#/$defs/profile" }, "requestId": { "$ref": "#/$defs/uuidUrn" }, "interactionId": { "$ref": "#/$defs/uuidUrn" }, - "requestedAt": { "type": "string", "format": "date-time" }, "type": { "$ref": "#/$defs/typeReference" }, "operation": { "type": "string", @@ -187,7 +186,6 @@ "profile", "requestId", "interactionId", - "requestedAt", "type", "operation", "target", @@ -273,61 +271,139 @@ }, "target": { "$ref": "#/$defs/target" } }, - "required": [ - "type", - "title", - "status", - "detail", - "instance", - "profile", - "requestId", - "interactionId", - "code" - ], + "required": ["type", "title", "status", "detail"], + "dependentRequired": { + "instance": ["profile", "requestId", "code"], + "profile": ["instance", "requestId", "code"], + "requestId": ["instance", "profile", "code"], + "interactionId": ["instance", "profile", "requestId", "code"], + "code": ["instance", "profile", "requestId"] + }, "allOf": [ + { + "if": { + "properties": { "code": { "not": { "const": "result-not-found" } } }, + "required": ["code"] + }, + "then": { "required": ["interactionId"] } + }, { "if": { "properties": { "code": { "const": "invalid-request" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/invalid-request" }, "status": { "const": 400 } } } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/invalid-request" }, + "status": { "const": 400 } + } + } }, { - "if": { "properties": { "code": { "const": "authentication-required" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/authentication-required" }, "status": { "const": 401 } } } + "if": { + "properties": { "code": { "const": "authentication-required" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/authentication-required" }, + "status": { "const": 401 } + } + } }, { "if": { "properties": { "code": { "const": "refused" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/refused" }, "status": { "const": 403 } } } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/refused" }, + "status": { "const": 403 } + } + } }, { "if": { "properties": { "code": { "const": "result-not-found" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/result-not-found" }, "status": { "const": 404 } } } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/result-not-found" }, + "status": { "const": 404 } + } + } }, { "if": { "properties": { "code": { "const": "stale-target" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/stale-target" }, "status": { "const": 409 } }, "required": ["target"] } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/stale-target" }, + "status": { "const": 409 } + }, + "required": ["target"] + } }, { - "if": { "properties": { "code": { "const": "idempotency-conflict" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/idempotency-conflict" }, "status": { "const": 409 } } } + "if": { + "properties": { "code": { "const": "idempotency-conflict" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/idempotency-conflict" }, + "status": { "const": 409 } + } + } }, { - "if": { "properties": { "code": { "const": "request-in-progress" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/request-in-progress" }, "status": { "const": 409 } } } + "if": { + "properties": { "code": { "const": "request-in-progress" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/request-in-progress" }, + "status": { "const": 409 } + } + } }, { - "if": { "properties": { "code": { "const": "expired-interaction" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/expired-interaction" }, "status": { "const": 410 } } } + "if": { + "properties": { "code": { "const": "expired-interaction" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/expired-interaction" }, + "status": { "const": 410 } + } + } }, { - "if": { "properties": { "code": { "const": "unsupported-profile" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/unsupported-profile" }, "status": { "const": 422 } } } + "if": { + "properties": { "code": { "const": "unsupported-profile" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/unsupported-profile" }, + "status": { "const": 422 } + } + } }, { "if": { "properties": { "code": { "const": "unsupported-type" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/unsupported-type" }, "status": { "const": 422 } } } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/unsupported-type" }, + "status": { "const": 422 } + } + } }, { - "if": { "properties": { "code": { "const": "unsupported-operation" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/unsupported-operation" }, "status": { "const": 422 } } } + "if": { + "properties": { "code": { "const": "unsupported-operation" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/unsupported-operation" }, + "status": { "const": 422 } + } + } } ] } diff --git a/src/map.ts b/src/map.ts index 6a65c22..9df3d83 100644 --- a/src/map.ts +++ b/src/map.ts @@ -2,6 +2,7 @@ import Ajv2020 from 'ajv/dist/2020.js'; import addFormats from 'ajv-formats'; import mapSchema from './map-0.1.schema.json' with { type: 'json' }; import contentReviewSchema from './content-review-0.1.schema.json' with { type: 'json' }; +import contentReviewContract from './content-review-0.1.contract.json' with { type: 'json' }; const ajv = new Ajv2020({ allErrors: true, strict: true, strictRequired: false }); addFormats(ajv); @@ -41,3 +42,7 @@ export function getMapSchema(): Record { export function getContentReviewSchema(): Record { return structuredClone(contentReviewSchema); } + +export function getContentReviewContract(): Record { + return structuredClone(contentReviewContract); +} diff --git a/test/map-description.json b/test/map-description.json index f7f727b..0f00029 100644 --- a/test/map-description.json +++ b/test/map-description.json @@ -6,7 +6,7 @@ "type": { "id": "https://mailschema.org/types/content-review", "version": "0.1", - "recordDigest": "sha-256:0e6365df1bf904f2475f972ec66a5561edc0bfaae69fd7a1b41f53b40bf8ac1c" + "contractDigest": "sha-256:deba8fc588c83c7cdf9d4e47d23b114861aea35c3daaf71854dc6c626c8a1d05" }, "describedAt": "2026-09-23T01:00:00Z", "expiresAt": "2026-09-30T01:00:00Z", diff --git a/test/map-request.json b/test/map-request.json index 26ad157..26c367a 100644 --- a/test/map-request.json +++ b/test/map-request.json @@ -3,11 +3,10 @@ "profile": "https://mailschema.org/profiles/map/0.1", "requestId": "urn:uuid:018f47a2-b4d3-7c02-b491-7bdf2eaac67c", "interactionId": "urn:uuid:018f47a2-5d7c-7b11-9a3d-4d2160b85b10", - "requestedAt": "2026-09-23T01:06:00Z", "type": { "id": "https://mailschema.org/types/content-review", "version": "0.1", - "recordDigest": "sha-256:0e6365df1bf904f2475f972ec66a5561edc0bfaae69fd7a1b41f53b40bf8ac1c" + "contractDigest": "sha-256:deba8fc588c83c7cdf9d4e47d23b114861aea35c3daaf71854dc6c626c8a1d05" }, "operation": "approve", "target": { diff --git a/test/map-result.json b/test/map-result.json index 27fa0c4..f8721c9 100644 --- a/test/map-result.json +++ b/test/map-result.json @@ -6,7 +6,7 @@ "type": { "id": "https://mailschema.org/types/content-review", "version": "0.1", - "recordDigest": "sha-256:0e6365df1bf904f2475f972ec66a5561edc0bfaae69fd7a1b41f53b40bf8ac1c" + "contractDigest": "sha-256:deba8fc588c83c7cdf9d4e47d23b114861aea35c3daaf71854dc6c626c8a1d05" }, "operation": "approve", "state": "completed", diff --git a/test/package.test.mjs b/test/package.test.mjs index 9ab3ded..e2ee750 100644 --- a/test/package.test.mjs +++ b/test/package.test.mjs @@ -6,6 +6,7 @@ import { assertContentReviewRequest, assertMapDocument, getContributionSchema, + getContentReviewContract, getMapSchema, } from '../dist/index.js'; @@ -13,6 +14,7 @@ const load = async (name) => JSON.parse(await readFile(new URL(name, import.meta test('exposes the canonical schema identifiers', () => { assert.equal(getMapSchema().$id, 'https://mailschema.org/schemas/map-0.1.schema.json'); + assert.equal(getContentReviewContract().id, 'https://mailschema.org/types/content-review'); assert.equal(getContributionSchema().$schema, 'https://json-schema.org/draft/2020-12/schema'); }); @@ -29,6 +31,7 @@ test('preserves canonical schema bytes during the build', async () => { 'contribution.schema.json', 'map-0.1.schema.json', 'content-review-0.1.schema.json', + 'content-review-0.1.contract.json', ]) { const source = await readFile(new URL(`../src/${name}`, import.meta.url)); const built = await readFile(new URL(`../dist/${name}`, import.meta.url));