diff --git a/README.md b/README.md index d076513..161e22e 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,18 @@ -# MailSchema for Python +# mailschema -[![PyPI](https://img.shields.io/pypi/v/mailschema)](https://pypi.org/project/mailschema/) -[![CI](https://github.com/mailschema/python/actions/workflows/test.yml/badge.svg)](https://github.com/mailschema/python/actions/workflows/test.yml) - -The Mail Action Protocol 0.2 core artifacts, and Draft 2020-12 validation of MailSchema Registry contributions, with no network requests. - -[Specification](https://mailschema.org/specification) · [Registry](https://mailschema.org/registry) · [Tools](https://mailschema.org/tools) · [Source](https://github.com/mailschema/python) - -## Install +The [Mail Action Protocol](https://mailschema.org) 0.3 artifacts for Python, byte for byte as mailschema.org publishes them: the profile record, the JSON-LD context, and the core, type contract and implementation record schemas. The profile record binds the context and the first two schemas by SHA-256. The package has no dependencies. ```sh -pip install mailschema -``` - -Python 3.10 or newer is required. - -## MAP 0.2 core artifacts - -```python -from mailschema import MAP_PROFILE, get_contract_format_schema, get_forms_schema, get_map_context, get_map_schema +pip install mailschema==0.3.0 ``` -Each returns a fresh copy of the file the [profile record](https://mailschema.org/profiles/map/0.2.json) binds by SHA-256. Type contracts are not bundled: a client obtains them from the [Registry catalogue](https://mailschema.org/registry/catalog.json) by digest. Processing MAP messages is outside this package; see the [profile](https://mailschema.org/specification/profile). - -## Registry data - ```python -from mailschema import contribution_errors, validate_contribution +import json +import mailschema -errors = contribution_errors(candidate) -validate_contribution(candidate) # Raises ValueError for invalid input. -``` - -`validate_record`, `record_errors` and `get_record_schema` handle expanded Registry records. - -```sh -python -m mailschema check contribution.json -python -m mailschema check record.json --record -python -m mailschema schema --map +core_schema = json.loads(mailschema.artifact("core.schema.json")) ``` -## Trust boundary +`mailschema.ARTIFACTS` lists the bundled files and `mailschema.PROFILE` names the profile. To parse and check descriptions and contracts, use the JavaScript or Ruby package, or implement the [specification](https://mailschema.org/specification/core); schema validation alone does not establish a valid description. -A valid document is structured input. Validation does not authenticate a service, grant authority or establish product conformance. The CLI reads one local file of at most 256 KiB and does not upload or modify it. Package versions and MAP profile versions advance independently. MIT licensed. +Source: [mailschema/python](https://github.com/mailschema/python). License: MIT. diff --git a/UPSTREAM.md b/UPSTREAM.md index 151765f..f944298 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -1,13 +1,13 @@ # Release provenance -Version `0.2.1` derives from [`mailschema/mailschema@db99c15`](https://github.com/mailschema/mailschema/commit/db99c1582f68b3cd3e80d5fe6c029c8535bf470c). +Version `0.3.0` derives from [`mailschema/mailschema@2c26469`](https://github.com/mailschema/mailschema/commit/2c26469209070f04ff9f8ec29e1387b8699a46a0). Its sources, tests and bundled files are prepared from that commit by `npm run packages:prepare`. -The bundled files are exact projections of that source commit and do not independently define MAP: +The bundled MAP 0.3 artifacts are exact copies of that commit's files and do not independently define MAP: -- MAP 0.2 core schema: `732d60fcb758d945dc15c57d885c9d1c0c76423a7439096d9755e8ec9830836a` -- MAP 0.2 JSON-LD context: `dfed0189524d7d16d31ed3d8af97e9a0c458200c4c034b413f9d14fa35b60257` -- Type contract format: `e030007cc034662c9d9d448a8a035aff3106cfb96fb60e6aa9f92b99b25df0b9` -- Form fields block: `3a976b93a057380e3630ca05e77e221a43a98559fb7fda8066299ec4bf2a8f9e` -- Contribution schema: `a8a241b4837971a6251bcc135c704283536f4fb825fbbaedaf1598c6e688d564` +- profile (`mailschema/profile.json`): `d9481627f855c127a4f5d6ddb7f3b699f66f6475bc8672d609f5b499f83d1a0f` +- context (`mailschema/context.jsonld`): `a51490d1fe8af695c14ea508aad88fd0429d18564791b6b52f3ab07d5c51aab3` +- core-schema (`mailschema/core.schema.json`): `0ad09a6955a47e7a2c1c1eec0747a3e0b15c08a79860e90ce4e65da11983aeb3` +- contract-schema (`mailschema/contract.schema.json`): `86bf057529c4fd2c7f0a880825774afeed31e9f7ba1d99760858a585fe28d18d` +- implementation-schema (`mailschema/implementation.schema.json`): `c62acc3ebb9f384c8fba7be353ab1905b65883ac2b16b147fd98485eec643396` -The canonical artifacts live in the main MailSchema repository. This repository owns the Python package surface and its release history. +The canonical artifacts live in the main MailSchema repository. This repository owns the Python package and its release history. diff --git a/pyproject.toml b/pyproject.toml index b384ec7..db48fee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,14 +4,14 @@ build-backend = "hatchling.build" [project] name = "mailschema" -version = "0.2.1" -description = "Mail Action Protocol 0.2 core artifacts and MailSchema Registry validation" +version = "0.3.0" +description = "Mail Action Protocol 0.3 artifacts: the profile record, context and schemas" readme = "README.md" license = "MIT" license-files = ["LICENSE"] requires-python = ">=3.10" authors = [{ name = "MailSchema contributors" }] -dependencies = ["jsonschema[format]>=4.23,<5"] +dependencies = [] classifiers = [ "Development Status :: 3 - Alpha", "Programming Language :: Python :: 3", @@ -23,8 +23,5 @@ Homepage = "https://mailschema.org/tools" Repository = "https://github.com/mailschema/python" Issues = "https://github.com/mailschema/python/issues" -[project.scripts] -mailschema = "mailschema.__main__:main" - [tool.hatch.build.targets.wheel] packages = ["src/mailschema"] diff --git a/src/mailschema/__init__.py b/src/mailschema/__init__.py index c94ae59..d98ca36 100644 --- a/src/mailschema/__init__.py +++ b/src/mailschema/__init__.py @@ -1,93 +1,31 @@ -"""The MAP 0.2 core artifacts and JSON Schema validation for MailSchema Registry contributions.""" +"""Mail Action Protocol 0.3 artifacts, byte for byte as mailschema.org publishes them. -import json -from importlib.resources import files -from typing import Any - -from jsonschema import Draft202012Validator, FormatChecker - -__version__ = "0.2.1" - -MAP_PROFILE = "https://mailschema.org/profiles/map/0.2" -"""The MAP profile whose core artifacts this package carries.""" - - -def _bundled(name: str) -> dict[str, Any]: - return json.loads(files(__package__).joinpath(name).read_text(encoding="utf-8")) - - -def get_map_schema() -> dict[str, Any]: - """Return a fresh copy of the MAP 0.2 core schema, as the profile record binds it by SHA-256.""" - return _bundled("map-0.2.schema.json") - - -def get_map_context() -> dict[str, Any]: - """Return a fresh copy of the MAP 0.2 JSON-LD context.""" - return _bundled("map-0.2.jsonld") - - -def get_contract_format_schema() -> dict[str, Any]: - """Return a fresh copy of the type contract format every MAP 0.2 contract follows.""" - return _bundled("type-contract-0.2.schema.json") +The profile record, the JSON-LD context, and the core, type contract and implementation record +schemas. The profile record binds the context and the first two schemas by SHA-256. +""" +from importlib.resources import files -def get_forms_schema() -> dict[str, Any]: - """Return a fresh copy of the form fields block contracts pin.""" - return _bundled("forms-0.1.schema.json") - - -def get_contribution_schema() -> dict[str, Any]: - """Return a fresh copy of the bundled Registry contribution schema.""" - return _bundled("contribution.schema.json") - - -def get_record_schema() -> dict[str, Any]: - """Return the schema for expanded Registry records.""" - schema = get_contribution_schema() - return {"$schema": schema["$schema"], "$defs": schema["$defs"], "$ref": "#/$defs/record"} - - -_schema = get_contribution_schema() -_validator = Draft202012Validator(_schema, format_checker=FormatChecker()) -_variants = { - variant["properties"]["kind"]["const"]: Draft202012Validator( - {"$schema": _schema["$schema"], "$defs": _schema["$defs"], **variant}, - format_checker=FormatChecker(), - ) - for variant in _schema["oneOf"] -} -_record_validator = Draft202012Validator(get_record_schema(), format_checker=FormatChecker()) - - -def _errors(validator: Draft202012Validator, value: Any) -> list[str]: - result = [] - for error in validator.iter_errors(value): - path = "/" + "/".join(str(part).replace("~", "~0").replace("/", "~1") for part in error.absolute_path) - result.append(f"{path} {error.message}") - return result - - -def contribution_errors(value: Any) -> list[str]: - """Return structural errors; an empty list means the schema checks passed.""" - kind = value.get("kind") if isinstance(value, dict) else None - validator = _variants.get(kind, _validator) if isinstance(kind, str) else _validator - return _errors(validator, value) - +__version__ = "0.3.0" -def record_errors(value: Any) -> list[str]: - """Return structural errors for a Registry record.""" - return _errors(_record_validator, value) +PROFILE = "https://mailschema.org/profiles/map/0.3" +"""The MAP profile these artifacts define.""" +CONTEXT = "https://mailschema.org/contexts/map-0.3.jsonld" +"""The JSON-LD context every MAP 0.3 description names.""" -def validate_contribution(value: Any) -> None: - """Raise ValueError if a contribution does not match the schema.""" - errors = contribution_errors(value) - if errors: - raise ValueError("Invalid contribution:\n" + "\n".join(errors)) +ARTIFACTS = ( + "profile.json", + "context.jsonld", + "core.schema.json", + "contract.schema.json", + "implementation.schema.json", +) +"""The bundled files, by name.""" -def validate_record(value: Any) -> None: - """Raise ValueError if a Registry record does not match the schema.""" - errors = record_errors(value) - if errors: - raise ValueError("Invalid type record:\n" + "\n".join(errors)) +def artifact(name: str) -> bytes: + """Return the exact bytes of a bundled artifact.""" + if name not in ARTIFACTS: + raise KeyError(f"mailschema: unknown artifact {name!r}") + return files(__package__).joinpath(name).read_bytes() diff --git a/src/mailschema/__main__.py b/src/mailschema/__main__.py deleted file mode 100644 index 0bbd28d..0000000 --- a/src/mailschema/__main__.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Local Registry checker; python -m mailschema or mailschema.""" - -import argparse -import json -import sys - -from . import ( - get_contract_format_schema, - get_contribution_schema, - get_forms_schema, - get_map_context, - get_map_schema, - get_record_schema, - validate_contribution, - validate_record, -) - -SCHEMAS = { - "record": get_record_schema, - "map": get_map_schema, - "context": get_map_context, - "contract_format": get_contract_format_schema, - "forms": get_forms_schema, -} - - -def main() -> int: - parser = argparse.ArgumentParser(description="Check MailSchema Registry files and print bundled schemas locally.") - commands = parser.add_subparsers(dest="command", required=True) - check = commands.add_parser("check", help="Check a local contribution or record; no upload or mutation.") - check.add_argument("file") - check.add_argument("--record", action="store_true") - schema = commands.add_parser("schema", help="Print a bundled schema: the contribution schema by default.") - chosen = schema.add_mutually_exclusive_group() - for name in SCHEMAS: - chosen.add_argument(f"--{name.replace('_', '-')}", dest=name, action="store_true") - args = parser.parse_args() - try: - if args.command == "schema": - selected = next((SCHEMAS[name] for name in SCHEMAS if getattr(args, name)), get_contribution_schema) - print(json.dumps(selected(), indent=2)) - else: - with open(args.file, "rb") as file: - raw = file.read(256 * 1024 + 1) - if len(raw) > 256 * 1024: - raise ValueError("JSON file exceeds 256 KiB.") - value = json.loads(raw) - (validate_record if args.record else validate_contribution)(value) - print(f"Valid MailSchema {'type record' if args.record else 'contribution'}.") - return 0 - except (OSError, ValueError) as error: - print(str(error), file=sys.stderr) - return 1 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/src/mailschema/context.jsonld b/src/mailschema/context.jsonld new file mode 100644 index 0000000..28c9635 --- /dev/null +++ b/src/mailschema/context.jsonld @@ -0,0 +1,50 @@ +{ + "@context": { + "@version": 1.1, + "@protected": true, + "@vocab": "https://mailschema.org/vocab/map/0.3#", + "MailAction": "https://mailschema.org/vocab/map/0.3#MailAction", + "profile": { + "@id": "profile", + "@type": "@id" + }, + "type": { + "@id": "type", + "@type": "@json" + }, + "service": { + "@id": "service", + "@type": "@json" + }, + "subject": { + "@id": "subject", + "@type": "@json" + }, + "terms": { + "@id": "terms", + "@type": "@json" + }, + "details": { + "@id": "details", + "@type": "@json" + }, + "operations": { + "@id": "operations", + "@type": "@json" + }, + "human": { + "@id": "human", + "@type": "@json" + }, + "recipient": "recipient", + "inLanguage": "inLanguage", + "issuedAt": { + "@id": "issuedAt", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "expiresAt": { + "@id": "expiresAt", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + } + } +} diff --git a/src/mailschema/contract.schema.json b/src/mailschema/contract.schema.json new file mode 100644 index 0000000..b050127 --- /dev/null +++ b/src/mailschema/contract.schema.json @@ -0,0 +1,206 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://mailschema.org/artifacts/map-0.3/schemas/contract.schema.json", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "version", + "profile", + "name", + "summary", + "requirements", + "detailsSchema", + "operations" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "format": "uri", + "pattern": "^https://", + "maxLength": 2048 + }, + "version": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "profile": { + "const": "https://mailschema.org/profiles/map/0.3" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "summary": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "requirements": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "minItems": 1, + "maxItems": 64 + }, + "detailsSchema": { + "type": "object" + }, + "operations": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "name", + "semantics", + "effects", + "bindings", + "actors", + "exactTerms", + "inputSchema", + "outcomes" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "pattern": "^[a-z][a-z0-9-]{0,63}$" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "semantics": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "effects": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "format": "uri", + "pattern": "^https://" + }, + "minItems": 1, + "maxItems": 32, + "uniqueItems": true + }, + "bindings": { + "type": "array", + "items": { + "enum": [ + "credential", + "capability" + ] + }, + "minItems": 1, + "maxItems": 2, + "uniqueItems": true + }, + "actors": { + "type": "array", + "items": { + "enum": [ + "human", + "agent" + ] + }, + "minItems": 1, + "maxItems": 2, + "uniqueItems": true + }, + "exactTerms": { + "type": "boolean" + }, + "inputSchema": { + "type": [ + "object", + "null" + ] + }, + "outcomes": { + "type": "object", + "minProperties": 1, + "propertyNames": { + "pattern": "^[a-z][a-z0-9-]{0,63}$" + }, + "additionalProperties": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + }, + "capability": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "maxLifetimeSeconds" + ], + "properties": { + "kind": { + "enum": [ + "refusal", + "protective-report", + "address-confirmation" + ] + }, + "maxLifetimeSeconds": { + "type": "integer", + "minimum": 1, + "maximum": 604800 + } + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "bindings": { + "contains": { + "const": "capability" + }, + "type": "array" + } + } + }, + "then": { + "required": [ + "capability" + ], + "properties": { + "inputSchema": { + "type": "null" + }, + "exactTerms": { + "const": true + } + } + }, + "else": { + "not": { + "required": [ + "capability" + ] + } + } + } + ] + }, + "minItems": 1, + "maxItems": 32 + } + } +} diff --git a/src/mailschema/contribution.schema.json b/src/mailschema/contribution.schema.json deleted file mode 100644 index 6b7742a..0000000 --- a/src/mailschema/contribution.schema.json +++ /dev/null @@ -1,874 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "MailSchema contribution format", - "description": "Repository contribution data. This is not a MAP message or wire representation.", - "$defs": { - "definition": { - "type": "object", - "additionalProperties": false, - "properties": { - "slug": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "name": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "summary": { - "type": "string", - "minLength": 1, - "maxLength": 500, - "pattern": "\\S" - }, - "category": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "status": { - "enum": ["Draft", "Proposal", "Reuse assessment"] - }, - "version": { - "anyOf": [ - { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:-[a-zA-Z0-9.-]+)?$", - "maxLength": 80 - }, - { - "type": "null" - } - ] - }, - "profile": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "overview": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "target": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "operations": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "name": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "description": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - } - }, - "required": ["id", "name", "description"] - }, - "minItems": 1, - "maxItems": 100 - }, - "inputs": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "results": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "permissions": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "humanRoute": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "example": { - "type": "object", - "additionalProperties": false, - "properties": { - "title": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "steps": { - "type": "array", - "items": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "minItems": 1, - "maxItems": 100 - }, - "exception": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - } - }, - "required": ["title", "steps", "exception"] - }, - "references": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "title": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "href": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - }, - "description": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - } - }, - "required": ["title", "href", "description"] - }, - "minItems": 1, - "maxItems": 100 - }, - "openQuestions": { - "type": "array", - "items": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "minItems": 0, - "maxItems": 100 - }, - "maintainers": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "name": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "url": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - } - }, - "required": ["name"] - }, - "minItems": 1, - "maxItems": 100 - }, - "definition": { - "type": "object", - "additionalProperties": false, - "properties": { - "label": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "href": { - "type": "string", - "pattern": "^/specification/(?:[a-z0-9-]+/)*$", - "maxLength": 300 - } - }, - "required": ["label", "href"] - } - }, - "required": [ - "slug", - "name", - "summary", - "category", - "status", - "version", - "profile", - "overview", - "target", - "operations", - "inputs", - "results", - "permissions", - "humanRoute", - "example", - "references", - "openQuestions", - "maintainers" - ], - "allOf": [ - { - "if": { - "properties": { - "status": { - "const": "Draft" - } - }, - "required": ["status"] - }, - "then": { - "required": ["definition"], - "properties": { - "version": { - "type": "string" - } - } - } - } - ] - }, - "record": { - "type": "object", - "additionalProperties": false, - "properties": { - "slug": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "name": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "summary": { - "type": "string", - "minLength": 1, - "maxLength": 500, - "pattern": "\\S" - }, - "category": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "status": { - "enum": ["Draft", "Proposal", "Reuse assessment"] - }, - "version": { - "anyOf": [ - { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:-[a-zA-Z0-9.-]+)?$", - "maxLength": 80 - }, - { - "type": "null" - } - ] - }, - "profile": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "overview": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "target": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "operations": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "name": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "description": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - } - }, - "required": ["id", "name", "description"] - }, - "minItems": 1, - "maxItems": 100 - }, - "inputs": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "results": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "permissions": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "humanRoute": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "example": { - "type": "object", - "additionalProperties": false, - "properties": { - "title": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "steps": { - "type": "array", - "items": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "minItems": 1, - "maxItems": 100 - }, - "exception": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - } - }, - "required": ["title", "steps", "exception"] - }, - "references": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "title": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "href": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - }, - "description": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - } - }, - "required": ["title", "href", "description"] - }, - "minItems": 1, - "maxItems": 100 - }, - "openQuestions": { - "type": "array", - "items": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "minItems": 0, - "maxItems": 100 - }, - "maintainers": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "url": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - } - }, - "required": ["name"] - }, - "minItems": 1, - "maxItems": 100 - }, - "definition": { - "type": "object", - "additionalProperties": false, - "properties": { - "label": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "href": { - "type": "string", - "pattern": "^/specification/(?:[a-z0-9-]+/)*$", - "maxLength": 300 - } - }, - "required": ["label", "href"] - }, - "origin": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "contributors": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "url": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - } - }, - "required": ["name"] - }, - "minItems": 1, - "maxItems": 100 - }, - "history": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "label": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "description": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "contributionId": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - } - }, - "required": ["label", "description"] - }, - "minItems": 1, - "maxItems": 100 - } - }, - "required": [ - "slug", - "name", - "summary", - "category", - "status", - "version", - "profile", - "overview", - "target", - "operations", - "inputs", - "results", - "permissions", - "humanRoute", - "example", - "references", - "openQuestions", - "maintainers", - "origin", - "contributors", - "history" - ], - "allOf": [ - { - "if": { - "properties": { - "status": { - "const": "Draft" - } - }, - "required": ["status"] - }, - "then": { - "required": ["definition"], - "properties": { - "version": { - "type": "string" - } - } - } - } - ] - } - }, - "oneOf": [ - { - "type": "object", - "additionalProperties": false, - "properties": { - "format": { - "const": "mailschema-contribution/1" - }, - "id": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "contributor": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "url": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - } - }, - "required": ["name"] - }, - "source": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - }, - "summary": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "kind": { - "const": "new-type" - }, - "record": { - "$ref": "#/$defs/definition" - } - }, - "required": ["format", "id", "contributor", "summary", "kind", "record"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "format": { - "const": "mailschema-contribution/1" - }, - "id": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "contributor": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "url": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - } - }, - "required": ["name"] - }, - "source": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - }, - "summary": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "kind": { - "const": "amendment" - }, - "baseDigest": { - "type": "string", - "pattern": "^[a-f0-9]{64}$" - }, - "record": { - "$ref": "#/$defs/definition" - } - }, - "required": ["format", "id", "contributor", "summary", "kind", "baseDigest", "record"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "format": { - "const": "mailschema-contribution/1" - }, - "id": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "contributor": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "url": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - } - }, - "required": ["name"] - }, - "source": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - }, - "summary": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "kind": { - "const": "implementation" - }, - "type": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "typeVersion": { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:-[a-zA-Z0-9.-]+)?$", - "maxLength": 80 - }, - "typeDigest": { - "type": "string", - "pattern": "^[a-f0-9]{64}$" - }, - "profile": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "product": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "\\S" - }, - "url": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - } - }, - "required": ["name"] - }, - "operations": { - "type": "array", - "items": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "minItems": 1, - "maxItems": 100 - }, - "evidence": { - "type": "object", - "additionalProperties": false, - "properties": { - "kind": { - "enum": ["declaration", "test-report"] - }, - "url": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - }, - "reproduction": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - }, - "results": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - } - }, - "required": ["kind"], - "allOf": [ - { - "if": { - "properties": { - "kind": { - "const": "test-report" - } - }, - "required": ["kind"] - }, - "then": { - "required": ["url", "reproduction", "results"] - } - } - ] - } - }, - "required": [ - "format", - "id", - "contributor", - "summary", - "kind", - "type", - "typeVersion", - "typeDigest", - "profile", - "product", - "operations", - "evidence" - ] - } - ] -} diff --git a/src/mailschema/core.schema.json b/src/mailschema/core.schema.json new file mode 100644 index 0000000..b0ebbfc --- /dev/null +++ b/src/mailschema/core.schema.json @@ -0,0 +1,210 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://mailschema.org/artifacts/map-0.3/schemas/core.schema.json", + "title": "MAP 0.3 description shape; semantic checks in Core are additionally required", + "type": "object", + "additionalProperties": false, + "required": [ + "@context", + "@type", + "@id", + "profile", + "type", + "issuedAt", + "expiresAt", + "inLanguage", + "service", + "recipient", + "subject", + "terms", + "details", + "operations", + "human" + ], + "properties": { + "@context": { + "const": "https://mailschema.org/contexts/map-0.3.jsonld" + }, + "@type": { + "const": "MailAction" + }, + "@id": { + "type": "string", + "minLength": 1, + "format": "uri", + "maxLength": 2048 + }, + "profile": { + "const": "https://mailschema.org/profiles/map/0.3" + }, + "type": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "version", + "contractDigest" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "format": "uri", + "pattern": "^https://", + "maxLength": 2048 + }, + "version": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "contractDigest": { + "type": "string", + "minLength": 1, + "pattern": "^sha-256:[0-9a-f]{64}$" + } + } + }, + "issuedAt": { + "type": "string", + "minLength": 1, + "format": "date-time", + "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])T([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\dZ$" + }, + "expiresAt": { + "type": "string", + "minLength": 1, + "format": "date-time", + "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])T([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\dZ$" + }, + "inLanguage": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "service": { + "type": "object", + "additionalProperties": false, + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "format": "uri", + "pattern": "^https://(?![0-9.]+(:[0-9]+)?$)[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(:[0-9]{1,5})?$", + "maxLength": 2048 + }, + "tenant": { + "type": "string", + "minLength": 1, + "format": "uri", + "maxLength": 2048 + } + } + }, + "recipient": { + "type": "string", + "minLength": 1, + "maxLength": 320 + }, + "subject": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "title" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "format": "uri", + "maxLength": 2048 + }, + "title": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + } + }, + "terms": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "version" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "format": "uri", + "maxLength": 2048 + }, + "version": { + "type": "string", + "minLength": 1, + "pattern": "^[ -~]+$", + "maxLength": 1024 + } + } + }, + "details": { + "type": "object" + }, + "operations": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "pattern": "^[a-z][a-z0-9-]{0,63}$" + }, + "capability": { + "type": "object", + "additionalProperties": false, + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string", + "minLength": 1, + "format": "uri", + "pattern": "^https://[^/?#@]+([/?][^#]*)?$", + "maxLength": 4096 + } + } + } + } + }, + "minItems": 1, + "maxItems": 32 + }, + "human": { + "type": "object", + "additionalProperties": false, + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string", + "minLength": 1, + "format": "uri", + "pattern": "^https://[^/?#@]+([/?][^#]*)?$", + "maxLength": 4096 + } + } + } + } +} diff --git a/src/mailschema/forms-0.1.schema.json b/src/mailschema/forms-0.1.schema.json deleted file mode 100644 index 88f680b..0000000 --- a/src/mailschema/forms-0.1.schema.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://mailschema.org/schemas/forms-0.1.schema.json", - "title": "MailSchema form fields 0.1", - "description": "Shared, immutable blocks for types that collect values: a closed subset of JSON Schema based on Model Context Protocol elicitation, with HTML autofill field names.", - "$defs": { - "fieldName": { - "type": "string", - "pattern": "^[a-z][A-Za-z0-9]{0,63}$" - }, - "text": { - "type": "string", - "minLength": 1, - "maxLength": 2000, - "pattern": "[^\\t\\n\\u000b\\f\\r \\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]" - }, - "autocomplete": { - "type": "string", - "maxLength": 200, - "pattern": "^(?:section-[a-z0-9-]+ )?(?:(?:shipping|billing) )?(?:(?:home|work|mobile|fax|pager) )?[a-z][a-z0-9-]*$", - "not": { - "pattern": "(?:^| )(?:current-password|new-password|one-time-code|cc-[a-z-]+)$" - } - }, - "choice": { - "type": "object", - "additionalProperties": false, - "properties": { - "const": { - "type": "string", - "minLength": 1, - "maxLength": 200 - }, - "title": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "[^\\t\\n\\u000b\\f\\r \\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]" - } - }, - "required": ["const", "title"] - }, - "choices": { - "type": "array", - "items": { - "$ref": "#/$defs/choice" - }, - "minItems": 1, - "maxItems": 100 - }, - "textField": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "const": "string" - }, - "title": { - "$ref": "#/$defs/text" - }, - "description": { - "$ref": "#/$defs/text" - }, - "minLength": { - "type": "integer", - "minimum": 0, - "maximum": 12000 - }, - "maxLength": { - "type": "integer", - "minimum": 1, - "maximum": 12000 - }, - "format": { - "enum": ["email", "uri", "date", "date-time"] - }, - "default": { - "type": "string", - "maxLength": 12000 - }, - "autocomplete": { - "$ref": "#/$defs/autocomplete" - } - }, - "required": ["type", "title"] - }, - "numberField": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "enum": ["number", "integer"] - }, - "title": { - "$ref": "#/$defs/text" - }, - "description": { - "$ref": "#/$defs/text" - }, - "minimum": { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - }, - "maximum": { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - }, - "default": { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - }, - "autocomplete": { - "$ref": "#/$defs/autocomplete" - } - }, - "required": ["type", "title"] - }, - "booleanField": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "const": "boolean" - }, - "title": { - "$ref": "#/$defs/text" - }, - "description": { - "$ref": "#/$defs/text" - }, - "default": { - "type": "boolean" - } - }, - "required": ["type", "title"] - }, - "choiceField": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "const": "string" - }, - "title": { - "$ref": "#/$defs/text" - }, - "description": { - "$ref": "#/$defs/text" - }, - "oneOf": { - "$ref": "#/$defs/choices" - }, - "default": { - "type": "string", - "maxLength": 200 - }, - "autocomplete": { - "$ref": "#/$defs/autocomplete" - } - }, - "required": ["type", "title", "oneOf"] - }, - "choicesField": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "const": "array" - }, - "title": { - "$ref": "#/$defs/text" - }, - "description": { - "$ref": "#/$defs/text" - }, - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "anyOf": { - "$ref": "#/$defs/choices" - } - }, - "required": ["anyOf"] - }, - "minItems": { - "type": "integer", - "minimum": 0, - "maximum": 100 - }, - "maxItems": { - "type": "integer", - "minimum": 1, - "maximum": 100 - }, - "uniqueItems": { - "const": true - }, - "default": { - "type": "array", - "items": { - "type": "string", - "maxLength": 200 - }, - "maxItems": 100, - "uniqueItems": true - } - }, - "required": ["type", "title", "items", "uniqueItems"] - }, - "fields": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "const": "object" - }, - "properties": { - "type": "object", - "minProperties": 1, - "maxProperties": 50, - "propertyNames": { - "$ref": "#/$defs/fieldName" - }, - "additionalProperties": { - "oneOf": [ - { - "$ref": "#/$defs/textField" - }, - { - "$ref": "#/$defs/numberField" - }, - { - "$ref": "#/$defs/booleanField" - }, - { - "$ref": "#/$defs/choiceField" - }, - { - "$ref": "#/$defs/choicesField" - } - ] - } - }, - "required": { - "type": "array", - "items": { - "$ref": "#/$defs/fieldName" - }, - "uniqueItems": true, - "maxItems": 50 - } - }, - "required": ["type", "properties"] - }, - "choiceFields": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "const": "object" - }, - "properties": { - "type": "object", - "minProperties": 1, - "maxProperties": 50, - "propertyNames": { - "$ref": "#/$defs/fieldName" - }, - "additionalProperties": { - "oneOf": [ - { - "$ref": "#/$defs/booleanField" - }, - { - "$ref": "#/$defs/choiceField" - }, - { - "$ref": "#/$defs/choicesField" - } - ] - } - }, - "required": { - "type": "array", - "items": { - "$ref": "#/$defs/fieldName" - }, - "uniqueItems": true, - "maxItems": 50 - } - }, - "required": ["type", "properties"] - }, - "fieldValues": { - "type": "object", - "maxProperties": 50, - "propertyNames": { - "$ref": "#/$defs/fieldName" - }, - "additionalProperties": { - "anyOf": [ - { - "type": "string", - "maxLength": 12000 - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "array", - "items": { - "type": "string", - "maxLength": 200 - }, - "maxItems": 100, - "uniqueItems": true - } - ] - } - } - } -} diff --git a/src/mailschema/implementation.schema.json b/src/mailschema/implementation.schema.json new file mode 100644 index 0000000..f6d2310 --- /dev/null +++ b/src/mailschema/implementation.schema.json @@ -0,0 +1,173 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://mailschema.org/artifacts/map-0.3/schemas/implementation.schema.json", + "title": "MAP implementation record", + "type": "object", + "additionalProperties": false, + "required": [ + "service", + "maintainer", + "type", + "operations", + "binding", + "status", + "documentation", + "evidence" + ], + "dependentRequired": { + "artifact": ["format"], + "format": ["artifact"] + }, + "properties": { + "service": { + "type": "string", + "minLength": 1, + "format": "uri", + "pattern": "^https://" + }, + "maintainer": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "url" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "url": { + "type": "string", + "minLength": 1, + "format": "uri", + "pattern": "^https://" + } + } + }, + "type": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "version", + "contractDigest" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "format": "uri", + "pattern": "^https://" + }, + "version": { + "type": "string", + "minLength": 1 + }, + "contractDigest": { + "type": "string", + "pattern": "^sha-256:[0-9a-f]{64}$" + } + } + }, + "operations": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "binding": { + "type": "string", + "format": "uri", + "pattern": "^https://", + "description": "Identifier of the binding specification or documented service binding. It is not a request destination. Clients must explicitly support it." + }, + "artifact": { + "type": "object", + "additionalProperties": false, + "required": [ + "url", + "digest", + "digestMode" + ], + "properties": { + "url": { + "type": "string", + "minLength": 1, + "format": "uri", + "pattern": "^https://" + }, + "digest": { + "type": "string", + "pattern": "^sha-256:[0-9a-f]{64}$" + }, + "digestMode": { + "enum": [ + "canonical-json", + "bytes" + ], + "description": "canonical-json hashes the RFC 8785 representation; bytes hashes the exact downloaded artifact. Both use SHA-256." + } + } + }, + "format": { + "type": "string", + "minLength": 1 + }, + "status": { + "enum": [ + "Draft", + "Experimental", + "Stable", + "Deprecated", + "Superseded" + ] + }, + "documentation": { + "type": "string", + "minLength": 1, + "format": "uri", + "pattern": "^https://" + }, + "evidence": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "url", + "summary" + ], + "properties": { + "kind": { + "enum": [ + "declaration", + "test-report" + ] + }, + "url": { + "type": "string", + "minLength": 1, + "format": "uri", + "pattern": "^https://" + }, + "summary": { + "type": "string", + "minLength": 1 + } + } + } + }, + "successor": { + "type": "string", + "minLength": 1, + "format": "uri", + "pattern": "^https://" + } + } +} diff --git a/src/mailschema/map-0.2.jsonld b/src/mailschema/map-0.2.jsonld deleted file mode 100644 index c97aca3..0000000 --- a/src/mailschema/map-0.2.jsonld +++ /dev/null @@ -1,91 +0,0 @@ -{ - "@context": { - "@version": 1.1, - "map": "https://mailschema.org/ns/map#", - "schema": "https://schema.org/", - "MailAction": "map:MailAction", - "profile": { - "@id": "map:profile", - "@type": "@id" - }, - "type": { - "@id": "map:interactionType", - "@context": { - "id": { - "@id": "map:id", - "@type": "@id" - } - } - }, - "version": "schema:version", - "contractDigest": "map:contractDigest", - "describedAt": { - "@id": "schema:dateCreated", - "@type": "http://www.w3.org/2001/XMLSchema#dateTime" - }, - "expiresAt": { - "@id": "schema:expires", - "@type": "http://www.w3.org/2001/XMLSchema#dateTime" - }, - "service": { - "@id": "schema:provider", - "@context": { - "id": { - "@id": "map:id", - "@type": "@id" - } - } - }, - "name": "schema:name", - "authority": "map:authority", - "onBehalfOf": { - "@id": "map:onBehalfOf", - "@context": { - "id": { - "@id": "map:id", - "@type": "@id" - } - } - }, - "resource": { - "@id": "map:resource", - "@type": "@id" - }, - "execution": "map:execution", - "url": { - "@id": "schema:url", - "@type": "@id" - }, - "resultUrlTemplate": "map:resultUrlTemplate", - "resultRetentionSeconds": "map:resultRetentionSeconds", - "humanUrl": { - "@id": "map:humanUrl", - "@type": "@id" - }, - "target": { - "@id": "schema:object", - "@context": { - "id": { - "@id": "map:id", - "@type": "@id" - } - } - }, - "recipient": "schema:recipient", - "details": { - "@id": "map:details", - "@type": "@json" - }, - "revision": "schema:version", - "title": "schema:name", - "digest": "map:digest", - "operations": { - "@id": "schema:potentialAction", - "@container": "@set", - "@context": { - "id": "map:operationId" - } - }, - "description": "schema:description" - } -} diff --git a/src/mailschema/map-0.2.schema.json b/src/mailschema/map-0.2.schema.json deleted file mode 100644 index a87b566..0000000 --- a/src/mailschema/map-0.2.schema.json +++ /dev/null @@ -1,1033 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://mailschema.org/schemas/map-0.2.schema.json", - "title": "Mail Action Protocol 0.2", - "description": "Description, request, result and problem documents for MAP 0.2: a type-agnostic core whose types are defined by digest-bound contracts, with credential or possession authority over HTTPS.", - "oneOf": [ - { - "$ref": "#/$defs/description" - }, - { - "$ref": "#/$defs/request" - }, - { - "$ref": "#/$defs/result" - }, - { - "$ref": "#/$defs/problem" - } - ], - "$defs": { - "profile": { - "const": "https://mailschema.org/profiles/map/0.2" - }, - "nonBlank": { - "type": "string", - "pattern": "[^\\t\\n\\u000b\\f\\r \\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]" - }, - "dateTime": { - "type": "string", - "pattern": "^(?:[0-9]{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12][0-9]|3[01])|(?:0[469]|11)-(?:0[1-9]|[12][0-9]|30)|02-(?:0[1-9]|1[0-9]|2[0-8]))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:[02468][048]|[13579][26])00)-02-29)T(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](?:\\.[0-9]{1,3})?(?:Z|[+-](?:[01][0-9]|2[0-3]):[0-5][0-9])$" - }, - "date": { - "type": "string", - "pattern": "^(?:[0-9]{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12][0-9]|3[01])|(?:0[469]|11)-(?:0[1-9]|[12][0-9]|30)|02-(?:0[1-9]|1[0-9]|2[0-8]))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:[02468][048]|[13579][26])00)-02-29)$" - }, - "identifier": { - "type": "string", - "pattern": "^[A-Za-z][A-Za-z0-9+.-]*:(?:[A-Za-z0-9._~:/?#@!$&'()*+,;=-]|%[0-9A-Fa-f]{2})+$", - "maxLength": 2048 - }, - "httpsIdentifier": { - "type": "string", - "pattern": "^https://(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)*[A-Za-z](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?::(?:[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?(?:[/?#](?:[A-Za-z0-9._~:/?#@!$&'()*+,;=-]|%[0-9A-Fa-f]{2})*)?$", - "maxLength": 2048 - }, - "httpsTarget": { - "type": "string", - "pattern": "^https://(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)*[A-Za-z](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?::(?:[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?(?:[/?](?:[A-Za-z0-9._~:/?@!$&'()*+,;=-]|%[0-9A-Fa-f]{2})*)?$", - "maxLength": 2048 - }, - "httpsOrigin": { - "type": "string", - "pattern": "^https://(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)*[A-Za-z](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?::(?:[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?$", - "maxLength": 300 - }, - "urlTemplate": { - "type": "string", - "pattern": "^https://(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)*[A-Za-z](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?::(?:[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?[/?](?:[A-Za-z0-9._~:/?@!$&'()*+,;=-]|%[0-9A-Fa-f]{2})*\\{requestId\\}(?:[A-Za-z0-9._~:/?#@!$&'()*+,;=-]|%[0-9A-Fa-f]{2})*$", - "maxLength": 2048 - }, - "address": { - "type": "string", - "pattern": "^(?:[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]|[^\\u0000-\\u00a0\\u1680\\u2000-\\u200f\\u2028-\\u202f\\u205f-\\u206f\\u3000\\ufeff])+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$", - "maxLength": 320 - }, - "uuidUrn": { - "type": "string", - "pattern": "^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" - }, - "digest": { - "type": "string", - "pattern": "^sha-256:[a-f0-9]{64}$" - }, - "contractDigest": { - "type": "string", - "pattern": "^sha-256:[a-f0-9]{64}$" - }, - "typeReference": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "$ref": "#/$defs/identifier" - }, - "version": { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:-[a-zA-Z0-9.-]+)?$", - "maxLength": 80 - }, - "contractDigest": { - "$ref": "#/$defs/contractDigest" - } - }, - "required": ["id", "version", "contractDigest"] - }, - "token": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "target": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "$ref": "#/$defs/identifier" - }, - "revision": { - "type": "string", - "minLength": 1, - "maxLength": 240 - }, - "title": { - "type": "string", - "minLength": 1, - "maxLength": 500 - }, - "digest": { - "$ref": "#/$defs/digest" - } - }, - "required": ["id", "revision", "digest"] - }, - "operation": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "$ref": "#/$defs/token" - }, - "name": { - "type": "string", - "minLength": 1, - "maxLength": 240 - }, - "description": { - "type": "string", - "minLength": 1, - "maxLength": 2000 - } - }, - "required": ["id", "name", "description"] - }, - "description": { - "type": "object", - "additionalProperties": false, - "properties": { - "@context": { - "const": "https://mailschema.org/contexts/map-0.2.jsonld" - }, - "@type": { - "const": "MailAction" - }, - "@id": { - "$ref": "#/$defs/uuidUrn" - }, - "profile": { - "$ref": "#/$defs/profile" - }, - "type": { - "$ref": "#/$defs/typeReference" - }, - "describedAt": { - "$ref": "#/$defs/dateTime" - }, - "expiresAt": { - "$ref": "#/$defs/dateTime" - }, - "service": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "$ref": "#/$defs/httpsIdentifier" - }, - "name": { - "type": "string", - "minLength": 1, - "maxLength": 240 - }, - "authority": { - "enum": ["credential", "possession"] - }, - "onBehalfOf": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "$ref": "#/$defs/identifier" - }, - "name": { - "type": "string", - "minLength": 1, - "maxLength": 240 - } - }, - "required": ["id", "name"] - }, - "resource": { - "$ref": "#/$defs/httpsTarget" - }, - "execution": { - "type": "object", - "additionalProperties": false, - "properties": { - "url": { - "$ref": "#/$defs/httpsTarget" - }, - "resultUrlTemplate": { - "$ref": "#/$defs/urlTemplate" - }, - "resultRetentionSeconds": { - "type": "integer", - "minimum": 300, - "maximum": 31536000 - } - }, - "required": ["url", "resultUrlTemplate", "resultRetentionSeconds"] - }, - "humanUrl": { - "$ref": "#/$defs/httpsIdentifier" - } - }, - "required": ["id", "name", "authority", "execution", "humanUrl"] - }, - "recipient": { - "$ref": "#/$defs/address" - }, - "target": { - "$ref": "#/$defs/target" - }, - "details": { - "type": "object", - "maxProperties": 100, - "propertyNames": { - "$ref": "#/$defs/detailsKey" - }, - "additionalProperties": { - "$ref": "#/$defs/detailsValue" - } - }, - "operations": { - "type": "array", - "items": { - "$ref": "#/$defs/operation" - }, - "minItems": 1, - "maxItems": 100 - } - }, - "required": [ - "@context", - "@type", - "@id", - "profile", - "type", - "describedAt", - "expiresAt", - "service", - "target", - "operations" - ], - "allOf": [ - { - "if": { - "type": "object", - "properties": { - "service": { - "type": "object", - "properties": { - "authority": { - "const": "credential" - } - } - } - } - }, - "then": { - "type": "object", - "properties": { - "service": { - "type": "object", - "required": ["resource"] - } - }, - "not": { - "required": ["recipient"] - } - }, - "else": { - "type": "object", - "properties": { - "service": { - "type": "object", - "properties": { - "resource": false, - "onBehalfOf": false - } - } - }, - "required": ["recipient"] - } - } - ] - }, - "request": { - "type": "object", - "additionalProperties": false, - "properties": { - "kind": { - "const": "MapRequest" - }, - "profile": { - "$ref": "#/$defs/profile" - }, - "requestId": { - "$ref": "#/$defs/uuidUrn" - }, - "interactionId": { - "$ref": "#/$defs/uuidUrn" - }, - "descriptionDigest": { - "$ref": "#/$defs/digest" - }, - "type": { - "$ref": "#/$defs/typeReference" - }, - "operation": { - "$ref": "#/$defs/token" - }, - "input": { - "type": "object", - "maxProperties": 100 - } - }, - "required": [ - "kind", - "profile", - "requestId", - "interactionId", - "descriptionDigest", - "type", - "operation", - "input" - ] - }, - "result": { - "type": "object", - "additionalProperties": false, - "properties": { - "kind": { - "const": "MapResult" - }, - "profile": { - "$ref": "#/$defs/profile" - }, - "requestId": { - "$ref": "#/$defs/uuidUrn" - }, - "interactionId": { - "$ref": "#/$defs/uuidUrn" - }, - "descriptionDigest": { - "$ref": "#/$defs/digest" - }, - "type": { - "$ref": "#/$defs/typeReference" - }, - "operation": { - "$ref": "#/$defs/token" - }, - "state": { - "enum": ["accepted", "completed", "failed", "pending", "approval-required"] - }, - "target": { - "$ref": "#/$defs/target" - }, - "recordedAt": { - "$ref": "#/$defs/dateTime" - }, - "resultUrl": { - "$ref": "#/$defs/httpsIdentifier" - }, - "actor": { - "type": "string", - "minLength": 1, - "maxLength": 240 - }, - "approvalUrl": { - "$ref": "#/$defs/httpsIdentifier" - }, - "reason": { - "$ref": "#/$defs/token" - }, - "output": { - "type": "object", - "maxProperties": 100 - } - }, - "required": [ - "kind", - "profile", - "requestId", - "interactionId", - "descriptionDigest", - "type", - "operation", - "state", - "target", - "recordedAt", - "resultUrl", - "output" - ], - "allOf": [ - { - "if": { - "type": "object", - "properties": { - "state": { - "const": "approval-required" - } - } - }, - "then": { - "type": "object", - "required": ["approvalUrl"] - }, - "else": { - "type": "object", - "not": { - "required": ["approvalUrl"] - } - } - }, - { - "if": { - "type": "object", - "properties": { - "state": { - "const": "failed" - } - } - }, - "then": { - "type": "object", - "required": ["reason"] - }, - "else": { - "type": "object", - "not": { - "required": ["reason"] - } - } - } - ] - }, - "problem": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "enum": [ - "https://mailschema.org/problems/refused", - "https://mailschema.org/problems/authentication-required", - "https://mailschema.org/problems/result-not-found", - "https://mailschema.org/problems/stale-target", - "https://mailschema.org/problems/expired-interaction", - "https://mailschema.org/problems/invalid-request", - "https://mailschema.org/problems/unsupported-type", - "https://mailschema.org/problems/unsupported-operation", - "https://mailschema.org/problems/idempotency-conflict", - "https://mailschema.org/problems/request-in-progress", - "https://mailschema.org/problems/already-decided" - ] - }, - "title": { - "type": "string", - "minLength": 1, - "maxLength": 240 - }, - "status": { - "type": "integer", - "minimum": 400, - "maximum": 599 - }, - "detail": { - "type": "string", - "minLength": 1, - "maxLength": 4000 - }, - "instance": { - "$ref": "#/$defs/identifier" - }, - "profile": { - "$ref": "#/$defs/profile" - }, - "requestId": { - "$ref": "#/$defs/uuidUrn" - }, - "interactionId": { - "$ref": "#/$defs/uuidUrn" - }, - "code": { - "enum": [ - "refused", - "result-not-found", - "stale-target", - "expired-interaction", - "invalid-request", - "unsupported-type", - "unsupported-operation", - "idempotency-conflict", - "request-in-progress", - "already-decided" - ] - }, - "target": { - "$ref": "#/$defs/target" - }, - "errors": { - "type": "array", - "minItems": 1, - "maxItems": 100, - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "detail": { - "type": "string", - "minLength": 1, - "maxLength": 2000 - }, - "pointer": { - "type": "string", - "pattern": "^(?:/(?:[^~/]|~[01])*)*$", - "maxLength": 1000 - } - }, - "required": ["detail", "pointer"] - } - } - }, - "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": { - "type": { - "enum": [ - "https://mailschema.org/problems/result-not-found", - "https://mailschema.org/problems/stale-target", - "https://mailschema.org/problems/expired-interaction", - "https://mailschema.org/problems/unsupported-type", - "https://mailschema.org/problems/unsupported-operation", - "https://mailschema.org/problems/idempotency-conflict", - "https://mailschema.org/problems/request-in-progress", - "https://mailschema.org/problems/already-decided" - ] - } - }, - "required": ["type"] - }, - "then": { - "required": ["code"] - } - }, - { - "if": { - "required": ["errors"] - }, - "then": { - "properties": { - "code": { - "const": "invalid-request" - } - }, - "required": ["code"] - } - }, - { - "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 - } - } - } - }, - { - "if": { - "properties": { - "code": { - "const": "refused" - } - }, - "required": ["code"] - }, - "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 - } - } - } - }, - { - "if": { - "properties": { - "code": { - "const": "stale-target" - } - }, - "required": ["code"] - }, - "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": "request-in-progress" - } - }, - "required": ["code"] - }, - "then": { - "properties": { - "type": { - "const": "https://mailschema.org/problems/request-in-progress" - }, - "status": { - "const": 409 - } - } - } - }, - { - "if": { - "properties": { - "code": { - "const": "already-decided" - } - }, - "required": ["code"] - }, - "then": { - "properties": { - "type": { - "const": "https://mailschema.org/problems/already-decided" - }, - "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": "unsupported-type" - } - }, - "required": ["code"] - }, - "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": { - "type": { - "const": "https://mailschema.org/problems/invalid-request" - } - }, - "required": ["type"] - }, - "then": { - "properties": { - "status": { - "const": 400 - } - } - } - }, - { - "if": { - "properties": { - "type": { - "const": "https://mailschema.org/problems/authentication-required" - } - }, - "required": ["type"] - }, - "then": { - "properties": { - "status": { - "const": 401 - } - } - } - }, - { - "if": { - "properties": { - "type": { - "const": "https://mailschema.org/problems/refused" - } - }, - "required": ["type"] - }, - "then": { - "properties": { - "status": { - "const": 403 - } - } - } - }, - { - "if": { - "properties": { - "type": { - "const": "https://mailschema.org/problems/result-not-found" - } - }, - "required": ["type"] - }, - "then": { - "properties": { - "status": { - "const": 404 - } - } - } - }, - { - "if": { - "properties": { - "type": { - "const": "https://mailschema.org/problems/stale-target" - } - }, - "required": ["type"] - }, - "then": { - "properties": { - "status": { - "const": 409 - } - } - } - }, - { - "if": { - "properties": { - "type": { - "const": "https://mailschema.org/problems/idempotency-conflict" - } - }, - "required": ["type"] - }, - "then": { - "properties": { - "status": { - "const": 409 - } - } - } - }, - { - "if": { - "properties": { - "type": { - "const": "https://mailschema.org/problems/request-in-progress" - } - }, - "required": ["type"] - }, - "then": { - "properties": { - "status": { - "const": 409 - } - } - } - }, - { - "if": { - "properties": { - "type": { - "const": "https://mailschema.org/problems/already-decided" - } - }, - "required": ["type"] - }, - "then": { - "properties": { - "status": { - "const": 409 - } - } - } - }, - { - "if": { - "properties": { - "type": { - "const": "https://mailschema.org/problems/expired-interaction" - } - }, - "required": ["type"] - }, - "then": { - "properties": { - "status": { - "const": 410 - } - } - } - }, - { - "if": { - "properties": { - "type": { - "const": "https://mailschema.org/problems/unsupported-type" - } - }, - "required": ["type"] - }, - "then": { - "properties": { - "status": { - "const": 422 - } - } - } - }, - { - "if": { - "properties": { - "type": { - "const": "https://mailschema.org/problems/unsupported-operation" - } - }, - "required": ["type"] - }, - "then": { - "properties": { - "status": { - "const": 422 - } - } - } - } - ] - }, - "detailsKey": { - "type": "string", - "pattern": "^[A-Za-z][A-Za-z0-9_]{0,63}$" - }, - "detailsValue": { - "anyOf": [ - { - "type": "string", - "maxLength": 12000 - }, - { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - }, - { - "type": "boolean" - }, - { - "type": "null" - }, - { - "type": "array", - "maxItems": 100, - "items": { - "$ref": "#/$defs/detailsValue" - } - }, - { - "type": "object", - "maxProperties": 100, - "propertyNames": { - "$ref": "#/$defs/detailsKey" - }, - "additionalProperties": { - "$ref": "#/$defs/detailsValue" - } - } - ] - } - } -} diff --git a/src/mailschema/profile.json b/src/mailschema/profile.json new file mode 100644 index 0000000..ff28124 --- /dev/null +++ b/src/mailschema/profile.json @@ -0,0 +1,27 @@ +{ + "id": "https://mailschema.org/profiles/map/0.3", + "name": "Mail Action Protocol 0.3", + "status": "working-draft", + "description": "An action description carried in Structured Email. A trusted service binding maps each decision to the service's existing operations, and the service enforces permission and exact terms. Types are defined by digest-bound contracts.", + "context": "https://mailschema.org/contexts/map-0.3.jsonld", + "schema": "https://mailschema.org/artifacts/map-0.3/schemas/core.schema.json", + "contractFormat": "https://mailschema.org/artifacts/map-0.3/schemas/contract.schema.json", + "specification": "https://mailschema.org/specification/core", + "artifacts": { + "context": { + "url": "https://mailschema.org/contexts/map-0.3.jsonld", + "sha256": "a51490d1fe8af695c14ea508aad88fd0429d18564791b6b52f3ab07d5c51aab3" + }, + "schema": { + "url": "https://mailschema.org/artifacts/map-0.3/schemas/core.schema.json", + "sha256": "0ad09a6955a47e7a2c1c1eec0747a3e0b15c08a79860e90ce4e65da11983aeb3" + }, + "contractFormat": { + "url": "https://mailschema.org/artifacts/map-0.3/schemas/contract.schema.json", + "sha256": "86bf057529c4fd2c7f0a880825774afeed31e9f7ba1d99760858a585fe28d18d" + }, + "conformance": { + "url": "https://github.com/mailschema/mailschema/blob/main/conformance/map-0.3/README.md" + } + } +} diff --git a/src/mailschema/type-contract-0.2.schema.json b/src/mailschema/type-contract-0.2.schema.json deleted file mode 100644 index 1fe14b2..0000000 --- a/src/mailschema/type-contract-0.2.schema.json +++ /dev/null @@ -1,179 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://mailschema.org/schemas/type-contract-0.2.schema.json", - "title": "MailSchema type contract for MAP 0.2", - "description": "A versioned interaction contract: the details, inputs, results, authority, repeatability and consequences of each operation, with every schema it depends on pinned by digest.", - "type": "object", - "additionalProperties": false, - "properties": { - "kind": { - "const": "MapTypeContract" - }, - "id": { - "type": "string", - "pattern": "^https://mailschema\\.org/types/[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 2048 - }, - "version": { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:-[a-zA-Z0-9.-]+)?$", - "maxLength": 80 - }, - "profile": { - "const": "https://mailschema.org/profiles/map/0.2" - }, - "target": { - "$ref": "#/$defs/prose" - }, - "dependencies": { - "type": "array", - "minItems": 1, - "maxItems": 20, - "items": { - "$ref": "#/$defs/schemaReference" - } - }, - "detailsSchema": { - "type": "object" - }, - "requestSchema": { - "$ref": "#/$defs/schemaReference" - }, - "operations": { - "type": "array", - "minItems": 1, - "maxItems": 100, - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "$ref": "#/$defs/token" - }, - "effect": { - "$ref": "#/$defs/prose" - }, - "authority": { - "type": "array", - "items": { - "enum": ["credential", "possession"] - }, - "minItems": 1, - "maxItems": 2, - "uniqueItems": true - }, - "consequences": { - "type": "array", - "items": { - "enum": [ - "refusal", - "protection", - "record", - "disclosure", - "commitment", - "authorization", - "assertion" - ] - }, - "minItems": 1, - "maxItems": 7, - "uniqueItems": true - }, - "repeatable": { - "type": "boolean" - }, - "fieldBindings": { - "type": "array", - "minItems": 1, - "maxItems": 10, - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "input": { - "$ref": "#/$defs/pointer" - }, - "fields": { - "$ref": "#/$defs/pointer" - } - }, - "required": ["input", "fields"] - } - }, - "results": { - "type": "array", - "minItems": 1, - "maxItems": 5, - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "state": { - "enum": ["accepted", "completed", "failed", "pending", "approval-required"] - }, - "reasons": { - "type": "array", - "items": { - "$ref": "#/$defs/token" - }, - "minItems": 1, - "maxItems": 20, - "uniqueItems": true - }, - "outputSchema": { - "type": "object" - } - }, - "required": ["state", "outputSchema"] - } - } - }, - "required": ["id", "effect", "authority", "consequences", "results"] - } - } - }, - "required": [ - "kind", - "id", - "version", - "profile", - "target", - "dependencies", - "requestSchema", - "operations" - ], - "$defs": { - "prose": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "[^\\t\\n\\u000b\\f\\r \\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]" - }, - "token": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "pointer": { - "type": "string", - "pattern": "^(?:/(?:[^~/]|~[01])*)+$", - "maxLength": 1000 - }, - "schemaReference": { - "type": "object", - "additionalProperties": false, - "properties": { - "url": { - "type": "string", - "pattern": "^https://mailschema\\.org/schemas/[a-z0-9.-]+\\.json$", - "maxLength": 2048 - }, - "canonicalDigest": { - "type": "string", - "pattern": "^sha-256:[a-f0-9]{64}$" - } - }, - "required": ["url", "canonicalDigest"] - } - } -} diff --git a/tests/content-review.json b/tests/content-review.json deleted file mode 100644 index f01a863..0000000 --- a/tests/content-review.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "slug": "content-review", - "name": "Content Review", - "summary": "Request changes or decide approval for a specific revision of content.", - "category": "Review", - "status": "Draft", - "version": "0.2", - "origin": "MailSchema draft", - "profile": "https://mailschema.org/profiles/map/0.1", - "overview": "A service sends content for review. The recipient can submit feedback or decide whether to approve the revision they received. A proposed approval may require an authorized human decision and ends as approved, declined, stale or expired. Editing the draft and sending or publishing it remain separate service operations.", - "target": "Service-owned content identified by its exact revision.", - "operations": [ - { - "id": "request-changes", - "name": "Request changes", - "description": "Record feedback on the identified revision." - }, - { - "id": "approve", - "name": "Approve", - "description": "Propose or record approval of that revision." - } - ], - "inputs": "The target revision and, for a change request, review feedback.", - "results": "Feedback recorded; approval recorded; or a proposed approval closed as declined, stale or expired. Every result identifies the request and exact revision.", - "permissions": "The service checks permission when the request is proposed and again when a human decides it. An authorized teammate may decide for the same service tenant. An unauthorized attempt does not change the proposed request. Editing and any later sending or publication require their own permissions.", - "humanRoute": "A review page showing the same content and revision.", - "example": { - "title": "Review a campaign draft", - "steps": [ - "The service emails a review request for revision 3 of a campaign.", - "A reviewer asks for a timezone to be added to the opening paragraph.", - "The service records the feedback. An editor prepares revision 4.", - "The reviewer approves revision 4 and the service records that decision." - ], - "exception": "An approval request for revision 3 is refused once that revision is stale. Approval of revision 4 does not send the campaign." - }, - "references": [ - { - "title": "Content Review 0.2 contract", - "href": "https://mailschema.org/contracts/content-review-0.2.json", - "description": "Normative canonical type contract used by MAP wire messages." - }, - { - "title": "Schema.org Actions", - "href": "https://schema.org/docs/actions.html", - "description": "Candidate vocabulary for describing available operations and their routes." - }, - { - "title": "Schema.org ReviewAction", - "href": "https://schema.org/ReviewAction", - "description": "Describes an opinion about an object. It is not equivalent to the revision approval in Content Review." - } - ], - "openQuestions": [ - "Which independent services and clients can reproduce the 0.2 conformance cases?", - "Which content digest representations need bindings beyond SHA-256?" - ], - "history": [ - { - "label": "0.2 · Working draft", - "description": "Declares completed and terminal failed outcomes for supervised approval, including declined, stale and expired decisions. Human decisions are authorized independently of the proposing client." - }, - { - "label": "0.1 · Working draft", - "description": "The MAP 0.1 profile defines review feedback, revision approval, exact target binding, retries and recovery." - } - ], - "definition": { - "label": "Read the Content Review definition", - "href": "/specification/content-review/" - }, - "contributors": [ - { - "name": "MailSchema" - } - ], - "maintainers": [ - { - "name": "MailSchema" - } - ] -} diff --git a/tests/new-type.json b/tests/new-type.json deleted file mode 100644 index 5121822..0000000 --- a/tests/new-type.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "format": "mailschema-contribution/1", - "contributor": { - "name": "Example Document Service", - "url": "https://example.com" - }, - "id": "example-document-receipt", - "kind": "new-type", - "summary": "Illustrative new-type contribution. Replace this example with your own workflow and attribution.", - "record": { - "slug": "document-receipt", - "name": "Document Receipt", - "summary": "Acknowledge receipt of a specified document.", - "category": "Documents", - "status": "Proposal", - "version": null, - "profile": "Under consideration", - "overview": "A service delivers a document and asks the recipient to acknowledge that they received it.", - "target": "The delivered document and its revision.", - "operations": [ - { - "id": "acknowledge-receipt", - "name": "Acknowledge receipt", - "description": "Record receipt of the identified document." - } - ], - "inputs": "The delivery reference and document revision.", - "results": "Receipt recorded, or an explanation of why the acknowledgement was not recorded.", - "permissions": "The service checks that the caller may acknowledge receipt for the intended recipient.", - "humanRoute": "A document page with the same acknowledgement operation.", - "example": { - "title": "Acknowledge a revised delivery note", - "steps": [ - "The service delivers revision 2 of a delivery note.", - "The recipient acknowledges receipt.", - "The service records receipt of that revision." - ], - "exception": "Receipt does not indicate agreement with the document or approval of a separate operation." - }, - "references": [ - { - "title": "Schema.org Actions", - "href": "https://schema.org/docs/actions.html", - "description": "Candidate vocabulary to assess when describing the acknowledgement operation." - } - ], - "openQuestions": ["Document revision references and retry behaviour."], - "maintainers": [ - { - "name": "Example Document Service", - "url": "https://example.com" - } - ] - } -} diff --git a/tests/test_package.py b/tests/test_package.py index 10d5a0a..9a313ef 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -1,56 +1,30 @@ -import copy +import hashlib import json import unittest -from pathlib import Path -from mailschema import ( - MAP_PROFILE, - contribution_errors, - get_contract_format_schema, - get_contribution_schema, - get_forms_schema, - get_map_context, - get_map_schema, - get_record_schema, - validate_contribution, - validate_record, -) - - -class PackageTests(unittest.TestCase): - def setUp(self): - # The release preparation script supplies the same fixtures as the site. - self.fixture = json.loads(Path("tests/new-type.json").read_text()) - self.record = json.loads(Path("tests/content-review.json").read_text()) - - def test_map_core_artifacts(self): - self.assertEqual(MAP_PROFILE, "https://mailschema.org/profiles/map/0.2") - self.assertEqual(get_map_schema()["$id"], "https://mailschema.org/schemas/map-0.2.schema.json") - self.assertEqual(get_map_context()["@context"]["MailAction"], "map:MailAction") - self.assertEqual( - get_contract_format_schema()["$id"], "https://mailschema.org/schemas/type-contract-0.2.schema.json" - ) - self.assertEqual(get_forms_schema()["$id"], "https://mailschema.org/schemas/forms-0.1.schema.json") - - def test_valid_contribution_and_record(self): - validate_contribution(self.fixture) - validate_record(self.record) - - def test_invalid_inputs_are_rejected(self): - for value in [None, [], 1, {"kind": []}, {"kind": "unknown"}, {**self.fixture, "verified": True}]: - self.assertTrue(contribution_errors(value)) - invalid = copy.deepcopy(self.fixture) - invalid["contributor"]["url"] = "javascript:alert(1)" - with self.assertRaises(ValueError): - validate_contribution(invalid) - with self.assertRaises(ValueError): - validate_record({**self.record, "version": None}) - - def test_schema_copies_and_record_reference(self): - schema = get_contribution_schema() - schema["title"] = "mutated" - self.assertNotEqual(get_contribution_schema()["title"], "mutated") - self.assertEqual(get_record_schema()["$ref"], "#/$defs/record") +import mailschema + + +class ArtifactTest(unittest.TestCase): + def test_profile_record_binds_the_bundled_bytes(self): + record = json.loads(mailschema.artifact("profile.json")) + self.assertEqual(record["id"], mailschema.PROFILE) + self.assertEqual(record["context"], mailschema.CONTEXT) + for key, name in { + "context": "context.jsonld", + "schema": "core.schema.json", + "contractFormat": "contract.schema.json", + }.items(): + digest = hashlib.sha256(mailschema.artifact(name)).hexdigest() + self.assertEqual(digest, record["artifacts"][key]["sha256"], name) + + def test_every_artifact_is_json(self): + for name in mailschema.ARTIFACTS: + self.assertIsInstance(json.loads(mailschema.artifact(name)), dict, name) + + def test_refuses_an_unknown_artifact(self): + with self.assertRaises(KeyError): + mailschema.artifact("../pyproject.toml") if __name__ == "__main__":