From 7fb83ad7931ace3f800836fd13f3af5cd935763e Mon Sep 17 00:00:00 2001 From: callumalpass Date: Wed, 29 Jul 2026 08:25:28 +1000 Subject: [PATCH] feat: add catalog presentation metadata --- catalog.yaml | 3 +- dist/artifacts/types/contact/1.md | 224 ++++++++++++++++++ dist/catalog.json | 93 +++++++- dist/packs/mdbase.jscontact/2.0.3.json | 49 ++++ dist/schemas/catalog.v2.schema.json | 193 +++++++++++++++ packs/mdbase.jscontact/2.0.2.pack.yaml | 1 + packs/mdbase.jscontact/2.0.3.pack.yaml | 31 +++ packs/mdbase.runtime.standard/0.2.0.pack.yaml | 14 +- schemas/catalog.v2.schema.json | 193 +++++++++++++++ scripts/build.mjs | 91 ++++++- scripts/sync-runtime-pack.mjs | 15 +- scripts/verify.mjs | 4 +- types/contact/1.md | 224 ++++++++++++++++++ 13 files changed, 1116 insertions(+), 19 deletions(-) create mode 100644 dist/artifacts/types/contact/1.md create mode 100644 dist/packs/mdbase.jscontact/2.0.3.json create mode 100644 dist/schemas/catalog.v2.schema.json create mode 100644 packs/mdbase.jscontact/2.0.3.pack.yaml create mode 100644 schemas/catalog.v2.schema.json create mode 100644 types/contact/1.md diff --git a/catalog.yaml b/catalog.yaml index 6e3f4c3..9f151f9 100644 --- a/catalog.yaml +++ b/catalog.yaml @@ -1,4 +1,4 @@ -catalog_version: 1 +catalog_version: 2 id: dev.mdbase.first-party name: mdbase contracts description: Reviewed first-party contracts and starter type packs for mdbase collections. @@ -6,4 +6,3 @@ homepage: https://mdbase.dev/contracts/ publisher: name: mdbase url: https://mdbase.dev/ - diff --git a/dist/artifacts/types/contact/1.md b/dist/artifacts/types/contact/1.md new file mode 100644 index 0000000..7cbc9fc --- /dev/null +++ b/dist/artifacts/types/contact/1.md @@ -0,0 +1,224 @@ +--- +kind: mdbase.type +name: contact +version: 1 +description: A fully expanded, editable Markdown-backed Contact using JSContact 2.0 +match: + where: + /@type: Card + version: "2.0" +schema: + dialect: json-schema-2020-12 + value: + $schema: https://json-schema.org/draft/2020-12/schema + title: Contact + description: A local starting shape for a person or organisation using IETF JSContact 2.0. + type: object + required: + - "@type" + - version + properties: + "@type": + const: Card + version: + const: "2.0" + uid: + type: string + minLength: 1 + kind: + enum: + - individual + - group + - org + - location + - device + - application + name: + type: object + properties: + "@type": + const: Name + components: + type: array + minItems: 1 + contains: + type: object + required: + - kind + properties: + kind: + not: + const: separator + items: + type: object + required: + - kind + - value + properties: + "@type": + const: NameComponent + kind: + enum: + - credential + - generation + - given + - given2 + - separator + - surname + - surname2 + - title + value: + type: string + additionalProperties: false + full: + type: string + minLength: 1 + isOrdered: + type: boolean + defaultSeparator: + type: string + anyOf: + - required: + - components + - required: + - full + additionalProperties: false + emails: + type: object + propertyNames: + type: string + minLength: 1 + additionalProperties: + type: object + required: + - address + properties: + "@type": + const: EmailAddress + address: + type: string + format: email + contexts: + type: object + additionalProperties: + const: true + pref: + type: integer + minimum: 1 + label: + type: string + additionalProperties: false + phones: + type: object + propertyNames: + type: string + minLength: 1 + additionalProperties: + type: object + required: + - number + properties: + "@type": + const: Phone + number: + type: string + minLength: 1 + features: + type: object + additionalProperties: + const: true + contexts: + type: object + additionalProperties: + const: true + pref: + type: integer + minimum: 1 + label: + type: string + additionalProperties: false + organizations: + type: object + propertyNames: + type: string + minLength: 1 + additionalProperties: + type: object + properties: + "@type": + const: Organization + name: + type: string + minLength: 1 + units: + type: array + minItems: 1 + items: + type: object + required: + - name + properties: + "@type": + const: OrgUnit + name: + type: string + minLength: 1 + additionalProperties: false + sortAs: + type: string + contexts: + type: object + additionalProperties: + const: true + anyOf: + - required: + - name + - required: + - units + additionalProperties: false + notes: + type: object + propertyNames: + type: string + minLength: 1 + additionalProperties: + type: object + required: + - note + properties: + "@type": + const: Note + note: + type: string + created: + type: string + format: date-time + additionalProperties: false + additionalProperties: false +collection: + display: + name_field: /name/full +implements: + - contract: mdbase.jscontact.card + version: 2.0.0 + fields: + /@type: /@type + version: version + uid: uid + kind: kind + name: name + emails: emails + phones: phones + organizations: organizations + notes: notes +--- + +# Contact + +This type begins as a fully expanded local copy of the JSContact core profile. +Nested fields are declared inline so the type editor can edit them directly. +Its fields belong to the collection and can be added, renamed, or reshaped. +Update the explicit contract mapping whenever a compatible field moves. + +The separately installed contract schema remains the portable boundary. Local +fields that are not mapped into that boundary remain collection-specific. diff --git a/dist/catalog.json b/dist/catalog.json index 2758817..9fec696 100644 --- a/dist/catalog.json +++ b/dist/catalog.json @@ -1,5 +1,5 @@ { - "catalog_version": 1, + "catalog_version": 2, "id": "dev.mdbase.first-party", "name": "mdbase contracts", "description": "Reviewed first-party contracts and starter type packs for mdbase collections.", @@ -193,11 +193,11 @@ "packs": [ { "id": "mdbase.jscontact", - "version": "2.0.2", - "name": "JSContact 2.0 starter", - "description": "The mdbase JSContact Card core contract and a fully expanded, editable Markdown implementation.", - "digest": "sha256:f6f5aa76850c10b83d42c0fffb7f76d3a67b531fb1de7bb5ae9a9b2c783687b0", - "provision": "./packs/mdbase.jscontact/2.0.2.json", + "version": "2.0.3", + "name": "Contact type pack", + "description": "The mdbase JSContact Card core contract and a fully expanded, editable Contact type.", + "digest": "sha256:e89693733a9d3d98bdd05fc040a08d7fb6f5eaea22089ed4ef8e0f1392e9c269", + "provision": "./packs/mdbase.jscontact/2.0.3.json", "provides": [ { "id": "mdbase.jscontact.card", @@ -205,7 +205,27 @@ } ], "resource_count": 3, - "featured": true + "display": { + "name": "Contact", + "summary": "Store people and organisations with names, email addresses, phone numbers, and notes.", + "category": "people", + "audience": "general", + "icon": "address-book", + "badges": [ + "JSContact 2.0" + ] + }, + "installation": { + "visibility": "default", + "recommendation": "user", + "primary_type": "contact", + "types": [ + { + "name": "contact", + "label": "Contact" + } + ] + } }, { "id": "mdbase.runtime.standard", @@ -257,7 +277,64 @@ } ], "resource_count": 43, - "featured": true + "display": { + "name": "Runtime standard library", + "summary": "Internal records and contracts for durable workflows, runs, timers, and diagnostics.", + "category": "infrastructure", + "audience": "infrastructure", + "icon": "terminal-window", + "badges": [ + "Runtime 0.2" + ] + }, + "installation": { + "visibility": "advanced", + "recommendation": "integration-managed", + "primary_type": null, + "caution": "Most collections do not need this pack. Install it only when a runtime integration asks you to.", + "types": [ + { + "name": "runtime_workflow", + "label": "Runtime workflow" + }, + { + "name": "runtime_policy", + "label": "Runtime policy" + }, + { + "name": "runtime_provider_registration", + "label": "Runtime provider registration" + }, + { + "name": "runtime_capability_grant", + "label": "Runtime capability grant" + }, + { + "name": "runtime_run", + "label": "Runtime run" + }, + { + "name": "runtime_action_attempt", + "label": "Runtime action attempt" + }, + { + "name": "runtime_checkpoint", + "label": "Runtime checkpoint" + }, + { + "name": "runtime_timer", + "label": "Runtime timer" + }, + { + "name": "runtime_diagnostic", + "label": "Runtime diagnostic" + }, + { + "name": "runtime_dead_letter", + "label": "Runtime dead letter" + } + ] + } } ] } diff --git a/dist/packs/mdbase.jscontact/2.0.3.json b/dist/packs/mdbase.jscontact/2.0.3.json new file mode 100644 index 0000000..7597779 --- /dev/null +++ b/dist/packs/mdbase.jscontact/2.0.3.json @@ -0,0 +1,49 @@ +{ + "manifest": { + "kind": "mdbase.type-pack", + "id": "mdbase.jscontact", + "version": "2.0.3", + "name": "Contact type pack", + "description": "The mdbase JSContact Card core contract and a fully expanded, editable Contact type.", + "resources": [ + { + "kind": "schema", + "source": "schemas/mdbase.jscontact.card/2.0.0.schema.json", + "target": "schemas/mdbase.jscontact.card/2.0.0.schema.json", + "digest": "sha256:e2831b1203303075767aefd5e4892c81793d4b85c45db706ef189aa6124f8e26" + }, + { + "kind": "contract", + "source": "contracts/mdbase.jscontact.card/2.0.0.md", + "target": "_contracts/mdbase.jscontact.card/2.0.0.md", + "digest": "sha256:c56c5acbf7992155d6890294842a261fe80a933ec75a226ea875b97ba57dcaa2" + }, + { + "kind": "type", + "source": "types/contact/1.md", + "target": "_types/contact.md", + "digest": "sha256:3a7723ea8ad3e2bc1b10a33c2c77c974557df2471a91dea2c0db2e8e8a54e2ef" + } + ] + }, + "resources": [ + { + "source": "schemas/mdbase.jscontact.card/2.0.0.schema.json", + "document": "{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://mdbase.dev/contracts/schemas/mdbase.jscontact.card/2.0.0.schema.json\",\n \"title\": \"JSContact Card 2.0 core profile\",\n \"description\": \"A strict mdbase core profile of an IETF JSContact 2.0 Card.\",\n \"type\": \"object\",\n \"required\": [\"@type\", \"version\"],\n \"properties\": {\n \"@type\": { \"const\": \"Card\" },\n \"version\": { \"const\": \"2.0\" },\n \"uid\": { \"type\": \"string\", \"minLength\": 1 },\n \"kind\": {\n \"enum\": [\"individual\", \"group\", \"org\", \"location\", \"device\", \"application\"]\n },\n \"name\": { \"$ref\": \"#/$defs/name\" },\n \"emails\": {\n \"type\": \"object\",\n \"propertyNames\": { \"$ref\": \"#/$defs/id\" },\n \"additionalProperties\": { \"$ref\": \"#/$defs/emailAddress\" }\n },\n \"phones\": {\n \"type\": \"object\",\n \"propertyNames\": { \"$ref\": \"#/$defs/id\" },\n \"additionalProperties\": { \"$ref\": \"#/$defs/phone\" }\n },\n \"organizations\": {\n \"type\": \"object\",\n \"propertyNames\": { \"$ref\": \"#/$defs/id\" },\n \"additionalProperties\": { \"$ref\": \"#/$defs/organization\" }\n },\n \"notes\": {\n \"type\": \"object\",\n \"propertyNames\": { \"$ref\": \"#/$defs/id\" },\n \"additionalProperties\": { \"$ref\": \"#/$defs/note\" }\n }\n },\n \"additionalProperties\": false,\n \"$defs\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"trueSet\": {\n \"type\": \"object\",\n \"additionalProperties\": { \"const\": true }\n },\n \"name\": {\n \"type\": \"object\",\n \"properties\": {\n \"@type\": { \"const\": \"Name\" },\n \"components\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"contains\": {\n \"type\": \"object\",\n \"required\": [\"kind\"],\n \"properties\": {\n \"kind\": { \"not\": { \"const\": \"separator\" } }\n }\n },\n \"items\": { \"$ref\": \"#/$defs/nameComponent\" }\n },\n \"full\": { \"type\": \"string\", \"minLength\": 1 },\n \"isOrdered\": { \"type\": \"boolean\" },\n \"defaultSeparator\": { \"type\": \"string\" }\n },\n \"anyOf\": [\n { \"required\": [\"components\"] },\n { \"required\": [\"full\"] }\n ],\n \"additionalProperties\": false\n },\n \"nameComponent\": {\n \"type\": \"object\",\n \"required\": [\"kind\", \"value\"],\n \"properties\": {\n \"@type\": { \"const\": \"NameComponent\" },\n \"kind\": {\n \"enum\": [\n \"credential\",\n \"generation\",\n \"given\",\n \"given2\",\n \"separator\",\n \"surname\",\n \"surname2\",\n \"title\"\n ]\n },\n \"value\": { \"type\": \"string\" }\n },\n \"additionalProperties\": false\n },\n \"emailAddress\": {\n \"type\": \"object\",\n \"required\": [\"address\"],\n \"properties\": {\n \"@type\": { \"const\": \"EmailAddress\" },\n \"address\": { \"type\": \"string\", \"format\": \"email\" },\n \"contexts\": { \"$ref\": \"#/$defs/trueSet\" },\n \"pref\": { \"type\": \"integer\", \"minimum\": 1 },\n \"label\": { \"type\": \"string\" }\n },\n \"additionalProperties\": false\n },\n \"phone\": {\n \"type\": \"object\",\n \"required\": [\"number\"],\n \"properties\": {\n \"@type\": { \"const\": \"Phone\" },\n \"number\": { \"type\": \"string\", \"minLength\": 1 },\n \"features\": { \"$ref\": \"#/$defs/trueSet\" },\n \"contexts\": { \"$ref\": \"#/$defs/trueSet\" },\n \"pref\": { \"type\": \"integer\", \"minimum\": 1 },\n \"label\": { \"type\": \"string\" }\n },\n \"additionalProperties\": false\n },\n \"organization\": {\n \"type\": \"object\",\n \"properties\": {\n \"@type\": { \"const\": \"Organization\" },\n \"name\": { \"type\": \"string\", \"minLength\": 1 },\n \"units\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": { \"$ref\": \"#/$defs/orgUnit\" }\n },\n \"sortAs\": { \"type\": \"string\" },\n \"contexts\": { \"$ref\": \"#/$defs/trueSet\" }\n },\n \"anyOf\": [\n { \"required\": [\"name\"] },\n { \"required\": [\"units\"] }\n ],\n \"additionalProperties\": false\n },\n \"orgUnit\": {\n \"type\": \"object\",\n \"required\": [\"name\"],\n \"properties\": {\n \"@type\": { \"const\": \"OrgUnit\" },\n \"name\": { \"type\": \"string\", \"minLength\": 1 }\n },\n \"additionalProperties\": false\n },\n \"note\": {\n \"type\": \"object\",\n \"required\": [\"note\"],\n \"properties\": {\n \"@type\": { \"const\": \"Note\" },\n \"note\": { \"type\": \"string\" },\n \"created\": { \"type\": \"string\", \"format\": \"date-time\" }\n },\n \"additionalProperties\": false\n }\n }\n}\n\n" + }, + { + "source": "contracts/mdbase.jscontact.card/2.0.0.md", + "document": "---\nkind: mdbase.contract\ncontract_type: record\nid: mdbase.jscontact.card\nversion: 2.0.0\nname: JSContact Card 2.0 core\ndescription: A strict, portable core profile of an IETF JSContact 2.0 Card.\nrecord_schema:\n dialect: json-schema-2020-12\n ref: ../../schemas/mdbase.jscontact.card/2.0.0.schema.json\nx-standard:\n name: JSContact\n version: \"2.0\"\n scope: Strict core profile covering the Card envelope, names, email addresses, phones, organizations, and notes.\n references:\n - https://www.rfc-editor.org/rfc/rfc9553.html\n - https://www.rfc-editor.org/rfc/rfc9982.html\n---\n\n# JSContact Card 2.0 core\n\nThis contract provides a deliberately bounded JSContact Card representation\nfor interoperable contact records. It follows JSContact 2.0, including the\noptional `uid` introduced by RFC 9982.\n\nThe profile accepts a strict subset of JSContact properties. It is not an\nofficial IETF JSON Schema and does not claim to validate every JSContact\nextension or conditional semantic rule.\n\n" + }, + { + "source": "types/contact/1.md", + "document": "---\nkind: mdbase.type\nname: contact\nversion: 1\ndescription: A fully expanded, editable Markdown-backed Contact using JSContact 2.0\nmatch:\n where:\n /@type: Card\n version: \"2.0\"\nschema:\n dialect: json-schema-2020-12\n value:\n $schema: https://json-schema.org/draft/2020-12/schema\n title: Contact\n description: A local starting shape for a person or organisation using IETF JSContact 2.0.\n type: object\n required:\n - \"@type\"\n - version\n properties:\n \"@type\":\n const: Card\n version:\n const: \"2.0\"\n uid:\n type: string\n minLength: 1\n kind:\n enum:\n - individual\n - group\n - org\n - location\n - device\n - application\n name:\n type: object\n properties:\n \"@type\":\n const: Name\n components:\n type: array\n minItems: 1\n contains:\n type: object\n required:\n - kind\n properties:\n kind:\n not:\n const: separator\n items:\n type: object\n required:\n - kind\n - value\n properties:\n \"@type\":\n const: NameComponent\n kind:\n enum:\n - credential\n - generation\n - given\n - given2\n - separator\n - surname\n - surname2\n - title\n value:\n type: string\n additionalProperties: false\n full:\n type: string\n minLength: 1\n isOrdered:\n type: boolean\n defaultSeparator:\n type: string\n anyOf:\n - required:\n - components\n - required:\n - full\n additionalProperties: false\n emails:\n type: object\n propertyNames:\n type: string\n minLength: 1\n additionalProperties:\n type: object\n required:\n - address\n properties:\n \"@type\":\n const: EmailAddress\n address:\n type: string\n format: email\n contexts:\n type: object\n additionalProperties:\n const: true\n pref:\n type: integer\n minimum: 1\n label:\n type: string\n additionalProperties: false\n phones:\n type: object\n propertyNames:\n type: string\n minLength: 1\n additionalProperties:\n type: object\n required:\n - number\n properties:\n \"@type\":\n const: Phone\n number:\n type: string\n minLength: 1\n features:\n type: object\n additionalProperties:\n const: true\n contexts:\n type: object\n additionalProperties:\n const: true\n pref:\n type: integer\n minimum: 1\n label:\n type: string\n additionalProperties: false\n organizations:\n type: object\n propertyNames:\n type: string\n minLength: 1\n additionalProperties:\n type: object\n properties:\n \"@type\":\n const: Organization\n name:\n type: string\n minLength: 1\n units:\n type: array\n minItems: 1\n items:\n type: object\n required:\n - name\n properties:\n \"@type\":\n const: OrgUnit\n name:\n type: string\n minLength: 1\n additionalProperties: false\n sortAs:\n type: string\n contexts:\n type: object\n additionalProperties:\n const: true\n anyOf:\n - required:\n - name\n - required:\n - units\n additionalProperties: false\n notes:\n type: object\n propertyNames:\n type: string\n minLength: 1\n additionalProperties:\n type: object\n required:\n - note\n properties:\n \"@type\":\n const: Note\n note:\n type: string\n created:\n type: string\n format: date-time\n additionalProperties: false\n additionalProperties: false\ncollection:\n display:\n name_field: /name/full\nimplements:\n - contract: mdbase.jscontact.card\n version: 2.0.0\n fields:\n /@type: /@type\n version: version\n uid: uid\n kind: kind\n name: name\n emails: emails\n phones: phones\n organizations: organizations\n notes: notes\n---\n\n# Contact\n\nThis type begins as a fully expanded local copy of the JSContact core profile.\nNested fields are declared inline so the type editor can edit them directly.\nIts fields belong to the collection and can be added, renamed, or reshaped.\nUpdate the explicit contract mapping whenever a compatible field moves.\n\nThe separately installed contract schema remains the portable boundary. Local\nfields that are not mapped into that boundary remain collection-specific.\n" + } + ], + "provides": [ + { + "id": "mdbase.jscontact.card", + "version": "2.0.0" + } + ] +} diff --git a/dist/schemas/catalog.v2.schema.json b/dist/schemas/catalog.v2.schema.json new file mode 100644 index 0000000..a60f9a3 --- /dev/null +++ b/dist/schemas/catalog.v2.schema.json @@ -0,0 +1,193 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://mdbase.dev/contracts/schemas/catalog.v2.schema.json", + "title": "mdbase contract catalog v2", + "type": "object", + "required": [ + "catalog_version", + "id", + "name", + "description", + "homepage", + "publisher", + "contracts", + "packs" + ], + "properties": { + "catalog_version": { "const": 2 }, + "id": { "$ref": "#/$defs/id" }, + "name": { "type": "string", "minLength": 1 }, + "description": { "type": "string", "minLength": 1 }, + "homepage": { "type": "string", "format": "uri" }, + "publisher": { + "type": "object", + "required": ["name", "url"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "url": { "type": "string", "format": "uri" } + }, + "additionalProperties": false + }, + "contracts": { + "type": "array", + "items": { "$ref": "#/$defs/contract" } + }, + "packs": { + "type": "array", + "items": { "$ref": "#/$defs/pack" } + } + }, + "additionalProperties": false, + "$defs": { + "id": { + "type": "string", + "minLength": 3, + "maxLength": 128, + "pattern": "^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)+$" + }, + "version": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$" + }, + "digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "relativeUrl": { + "type": "string", + "pattern": "^\\./(?!.*(?:^|/)\\.\\.(?:/|$)).+$" + }, + "contractRef": { + "type": "object", + "required": ["id", "version"], + "properties": { + "id": { "$ref": "#/$defs/id" }, + "version": { "$ref": "#/$defs/version" } + }, + "additionalProperties": false + }, + "standard": { + "type": "object", + "required": ["name", "version", "scope", "references"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "version": { "type": "string", "minLength": 1 }, + "scope": { "type": "string", "minLength": 1 }, + "references": { + "type": "array", + "minItems": 1, + "items": { "type": "string", "format": "uri" } + } + }, + "additionalProperties": false + }, + "contract": { + "type": "object", + "required": [ + "id", + "version", + "name", + "description", + "contract_type", + "digest", + "artifact", + "standards" + ], + "properties": { + "id": { "$ref": "#/$defs/id" }, + "version": { "$ref": "#/$defs/version" }, + "name": { "type": "string", "minLength": 1 }, + "description": { "type": "string", "minLength": 1 }, + "contract_type": { "enum": ["record", "event", "action"] }, + "digest": { "$ref": "#/$defs/digest" }, + "artifact": { "$ref": "#/$defs/relativeUrl" }, + "standards": { + "type": "array", + "items": { "$ref": "#/$defs/standard" } + } + }, + "additionalProperties": false + }, + "display": { + "type": "object", + "required": ["name", "summary", "category", "audience", "icon"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "summary": { "type": "string", "minLength": 1 }, + "category": { + "enum": ["people", "work", "research", "calendar", "infrastructure", "other"] + }, + "audience": { "enum": ["general", "developer", "infrastructure"] }, + "icon": { "type": "string", "minLength": 1 }, + "badges": { + "type": "array", + "items": { "type": "string", "minLength": 1 }, + "uniqueItems": true + } + }, + "additionalProperties": false + }, + "installedType": { + "type": "object", + "required": ["name", "label"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "label": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + }, + "installation": { + "type": "object", + "required": ["visibility", "recommendation", "primary_type", "types"], + "properties": { + "visibility": { "enum": ["default", "advanced", "hidden"] }, + "recommendation": { "enum": ["user", "optional", "integration-managed"] }, + "primary_type": { + "oneOf": [ + { "type": "string", "minLength": 1 }, + { "type": "null" } + ] + }, + "types": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/installedType" } + }, + "caution": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + }, + "pack": { + "type": "object", + "required": [ + "id", + "version", + "name", + "description", + "digest", + "provision", + "provides", + "resource_count", + "display", + "installation" + ], + "properties": { + "id": { "$ref": "#/$defs/id" }, + "version": { "$ref": "#/$defs/version" }, + "name": { "type": "string", "minLength": 1 }, + "description": { "type": "string", "minLength": 1 }, + "digest": { "$ref": "#/$defs/digest" }, + "provision": { "$ref": "#/$defs/relativeUrl" }, + "provides": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/contractRef" } + }, + "resource_count": { "type": "integer", "minimum": 1 }, + "display": { "$ref": "#/$defs/display" }, + "installation": { "$ref": "#/$defs/installation" } + }, + "additionalProperties": false + } + } +} diff --git a/packs/mdbase.jscontact/2.0.2.pack.yaml b/packs/mdbase.jscontact/2.0.2.pack.yaml index 197eced..f6d1567 100644 --- a/packs/mdbase.jscontact/2.0.2.pack.yaml +++ b/packs/mdbase.jscontact/2.0.2.pack.yaml @@ -4,6 +4,7 @@ version: 2.0.2 name: JSContact 2.0 starter description: The mdbase JSContact Card core contract and a fully expanded, editable Markdown implementation. featured: true +catalog: false expand_local_refs: true provides: - id: mdbase.jscontact.card diff --git a/packs/mdbase.jscontact/2.0.3.pack.yaml b/packs/mdbase.jscontact/2.0.3.pack.yaml new file mode 100644 index 0000000..4cb426e --- /dev/null +++ b/packs/mdbase.jscontact/2.0.3.pack.yaml @@ -0,0 +1,31 @@ +kind: mdbase.catalog-pack +id: mdbase.jscontact +version: 2.0.3 +name: Contact type pack +description: The mdbase JSContact Card core contract and a fully expanded, editable Contact type. +display: + name: Contact + summary: Store people and organisations with names, email addresses, phone numbers, and notes. + category: people + audience: general + icon: address-book + badges: + - JSContact 2.0 +installation: + visibility: default + recommendation: user + primary_type: contact +expand_local_refs: true +provides: + - id: mdbase.jscontact.card + version: 2.0.0 +resources: + - kind: schema + source: schemas/mdbase.jscontact.card/2.0.0.schema.json + target: schemas/mdbase.jscontact.card/2.0.0.schema.json + - kind: contract + source: contracts/mdbase.jscontact.card/2.0.0.md + target: _contracts/mdbase.jscontact.card/2.0.0.md + - kind: type + source: types/contact/1.md + target: _types/contact.md diff --git a/packs/mdbase.runtime.standard/0.2.0.pack.yaml b/packs/mdbase.runtime.standard/0.2.0.pack.yaml index ff29e8e..d29c234 100644 --- a/packs/mdbase.runtime.standard/0.2.0.pack.yaml +++ b/packs/mdbase.runtime.standard/0.2.0.pack.yaml @@ -3,7 +3,19 @@ id: mdbase.runtime.standard version: 0.2.0 name: mdbase durable runtime standard library description: Runtime record implementations and inspectable built-in event/action contracts. -featured: true +display: + name: Runtime standard library + summary: Internal records and contracts for durable workflows, runs, timers, and diagnostics. + category: infrastructure + audience: infrastructure + icon: terminal-window + badges: + - Runtime 0.2 +installation: + visibility: advanced + recommendation: integration-managed + primary_type: null + caution: Most collections do not need this pack. Install it only when a runtime integration asks you to. provides: - id: mdbase.runtime.action-attempt version: 1.0.0 diff --git a/schemas/catalog.v2.schema.json b/schemas/catalog.v2.schema.json new file mode 100644 index 0000000..a60f9a3 --- /dev/null +++ b/schemas/catalog.v2.schema.json @@ -0,0 +1,193 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://mdbase.dev/contracts/schemas/catalog.v2.schema.json", + "title": "mdbase contract catalog v2", + "type": "object", + "required": [ + "catalog_version", + "id", + "name", + "description", + "homepage", + "publisher", + "contracts", + "packs" + ], + "properties": { + "catalog_version": { "const": 2 }, + "id": { "$ref": "#/$defs/id" }, + "name": { "type": "string", "minLength": 1 }, + "description": { "type": "string", "minLength": 1 }, + "homepage": { "type": "string", "format": "uri" }, + "publisher": { + "type": "object", + "required": ["name", "url"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "url": { "type": "string", "format": "uri" } + }, + "additionalProperties": false + }, + "contracts": { + "type": "array", + "items": { "$ref": "#/$defs/contract" } + }, + "packs": { + "type": "array", + "items": { "$ref": "#/$defs/pack" } + } + }, + "additionalProperties": false, + "$defs": { + "id": { + "type": "string", + "minLength": 3, + "maxLength": 128, + "pattern": "^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)+$" + }, + "version": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$" + }, + "digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "relativeUrl": { + "type": "string", + "pattern": "^\\./(?!.*(?:^|/)\\.\\.(?:/|$)).+$" + }, + "contractRef": { + "type": "object", + "required": ["id", "version"], + "properties": { + "id": { "$ref": "#/$defs/id" }, + "version": { "$ref": "#/$defs/version" } + }, + "additionalProperties": false + }, + "standard": { + "type": "object", + "required": ["name", "version", "scope", "references"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "version": { "type": "string", "minLength": 1 }, + "scope": { "type": "string", "minLength": 1 }, + "references": { + "type": "array", + "minItems": 1, + "items": { "type": "string", "format": "uri" } + } + }, + "additionalProperties": false + }, + "contract": { + "type": "object", + "required": [ + "id", + "version", + "name", + "description", + "contract_type", + "digest", + "artifact", + "standards" + ], + "properties": { + "id": { "$ref": "#/$defs/id" }, + "version": { "$ref": "#/$defs/version" }, + "name": { "type": "string", "minLength": 1 }, + "description": { "type": "string", "minLength": 1 }, + "contract_type": { "enum": ["record", "event", "action"] }, + "digest": { "$ref": "#/$defs/digest" }, + "artifact": { "$ref": "#/$defs/relativeUrl" }, + "standards": { + "type": "array", + "items": { "$ref": "#/$defs/standard" } + } + }, + "additionalProperties": false + }, + "display": { + "type": "object", + "required": ["name", "summary", "category", "audience", "icon"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "summary": { "type": "string", "minLength": 1 }, + "category": { + "enum": ["people", "work", "research", "calendar", "infrastructure", "other"] + }, + "audience": { "enum": ["general", "developer", "infrastructure"] }, + "icon": { "type": "string", "minLength": 1 }, + "badges": { + "type": "array", + "items": { "type": "string", "minLength": 1 }, + "uniqueItems": true + } + }, + "additionalProperties": false + }, + "installedType": { + "type": "object", + "required": ["name", "label"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "label": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + }, + "installation": { + "type": "object", + "required": ["visibility", "recommendation", "primary_type", "types"], + "properties": { + "visibility": { "enum": ["default", "advanced", "hidden"] }, + "recommendation": { "enum": ["user", "optional", "integration-managed"] }, + "primary_type": { + "oneOf": [ + { "type": "string", "minLength": 1 }, + { "type": "null" } + ] + }, + "types": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/installedType" } + }, + "caution": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + }, + "pack": { + "type": "object", + "required": [ + "id", + "version", + "name", + "description", + "digest", + "provision", + "provides", + "resource_count", + "display", + "installation" + ], + "properties": { + "id": { "$ref": "#/$defs/id" }, + "version": { "$ref": "#/$defs/version" }, + "name": { "type": "string", "minLength": 1 }, + "description": { "type": "string", "minLength": 1 }, + "digest": { "$ref": "#/$defs/digest" }, + "provision": { "$ref": "#/$defs/relativeUrl" }, + "provides": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/contractRef" } + }, + "resource_count": { "type": "integer", "minimum": 1 }, + "display": { "$ref": "#/$defs/display" }, + "installation": { "$ref": "#/$defs/installation" } + }, + "additionalProperties": false + } + } +} diff --git a/scripts/build.mjs b/scripts/build.mjs index 28fa63f..19c85e1 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -37,6 +37,7 @@ for (const packFile of packFiles) { const resources = []; const manifestResources = []; + const installedTypes = []; for (const resource of definition.resources) { assertSafePath(resource.source, "resource source"); assertSafePath(resource.target, "resource target"); @@ -72,6 +73,11 @@ for (const packFile of packFiles) { } if (resource.kind === "type" && definition.catalog !== false) { validateEditableType(resource.source, document, definition.expand_local_refs === true); + const frontmatter = matter(document).data; + installedTypes.push({ + name: frontmatter.name, + label: humanizeTypeName(frontmatter.name), + }); } } @@ -93,6 +99,7 @@ for (const packFile of packFiles) { await writeFile(join(dist, provisionPath), provisionDocument); if (definition.catalog !== false) { + validateCatalogPresentation(definition, installedTypes, relative(root, packFile)); packs.push({ id: definition.id, version: definition.version, @@ -102,7 +109,11 @@ for (const packFile of packFiles) { provision: `./${provisionPath}`, provides: definition.provides, resource_count: definition.resources.length, - featured: definition.featured, + display: definition.display, + installation: { + ...definition.installation, + types: installedTypes, + }, }); } } @@ -113,10 +124,12 @@ const catalog = { packs: packs.sort(compareIdentity), }; await writeFile(join(dist, "catalog.json"), json(catalog)); -await cp( - join(root, "schemas", "catalog.v1.schema.json"), - join(dist, "schemas", "catalog.v1.schema.json"), -); +for (const version of [1, 2]) { + await cp( + join(root, "schemas", `catalog.v${version}.schema.json`), + join(dist, "schemas", `catalog.v${version}.schema.json`), + ); +} console.log( `Built ${catalog.contracts.length} contract and ${catalog.packs.length} pack into ${relative(root, dist)}.`, @@ -187,7 +200,6 @@ function validatePackDefinition(value, label) { fail(`${label} is missing ${key}.`); } } - if (typeof value.featured !== "boolean") fail(`${label} must declare featured.`); if (value.catalog !== undefined && typeof value.catalog !== "boolean") { fail(`${label} catalog must be a boolean.`); } @@ -215,6 +227,73 @@ function validatePackDefinition(value, label) { } } +function validateCatalogPresentation(definition, installedTypes, label) { + const display = definition.display; + if (!display || typeof display !== "object" || Array.isArray(display)) { + fail(`${label} must declare display metadata.`); + } + for (const key of ["name", "summary", "category", "audience", "icon"]) { + if (typeof display[key] !== "string" || display[key].length === 0) { + fail(`${label} display is missing ${key}.`); + } + } + if (!["people", "work", "research", "calendar", "infrastructure", "other"].includes( + display.category, + )) { + fail(`${label} display.category is invalid.`); + } + if (!["general", "developer", "infrastructure"].includes(display.audience)) { + fail(`${label} display.audience is invalid.`); + } + if ( + display.badges !== undefined + && ( + !Array.isArray(display.badges) + || display.badges.some((badge) => typeof badge !== "string" || badge.length === 0) + ) + ) { + fail(`${label} display.badges must contain non-empty strings.`); + } + + const installation = definition.installation; + if (!installation || typeof installation !== "object" || Array.isArray(installation)) { + fail(`${label} must declare installation metadata.`); + } + if (!["default", "advanced", "hidden"].includes(installation.visibility)) { + fail(`${label} installation.visibility is invalid.`); + } + if (!["user", "optional", "integration-managed"].includes(installation.recommendation)) { + fail(`${label} installation.recommendation is invalid.`); + } + if ( + installation.caution !== undefined + && (typeof installation.caution !== "string" || installation.caution.length === 0) + ) { + fail(`${label} installation.caution must be a non-empty string.`); + } + if ( + installation.primary_type !== null + && typeof installation.primary_type !== "string" + ) { + fail(`${label} installation.primary_type must be a type name or null.`); + } + if ( + typeof installation.primary_type === "string" + && !installedTypes.some(({ name }) => name === installation.primary_type) + ) { + fail( + `${label} installation.primary_type ${installation.primary_type} is not installed by the pack.`, + ); + } +} + +function humanizeTypeName(name) { + const parts = name.split(/[\s_-]+/u).filter(Boolean); + return parts + .map((part, index) => index === 0 ? `${part[0].toUpperCase()}${part.slice(1)}` : part) + .join(" "); +} + async function walk(directory) { const entries = await readdir(directory, { withFileTypes: true }); const paths = []; diff --git a/scripts/sync-runtime-pack.mjs b/scripts/sync-runtime-pack.mjs index eccfdc9..a2bf0e9 100644 --- a/scripts/sync-runtime-pack.mjs +++ b/scripts/sync-runtime-pack.mjs @@ -71,7 +71,20 @@ const definition = { version: manifest.version, name: manifest.name, description: manifest.description, - featured: true, + display: { + name: "Runtime standard library", + summary: "Internal records and contracts for durable workflows, runs, timers, and diagnostics.", + category: "infrastructure", + audience: "infrastructure", + icon: "terminal-window", + badges: ["Runtime 0.2"] + }, + installation: { + visibility: "advanced", + recommendation: "integration-managed", + primary_type: null, + caution: "Most collections do not need this pack. Install it only when a runtime integration asks you to." + }, // `provides` means the installed types implement these record contracts. // Event/action artifacts remain available for admission, while live // declarations say which sources/providers actually supply them. diff --git a/scripts/verify.mjs b/scripts/verify.mjs index 190cda8..6d31174 100644 --- a/scripts/verify.mjs +++ b/scripts/verify.mjs @@ -9,7 +9,9 @@ import addFormats from "ajv-formats"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const dist = join(root, "dist"); const catalog = json(await readFile(join(dist, "catalog.json"), "utf8")); -const catalogSchema = json(await readFile(join(root, "schemas", "catalog.v1.schema.json"), "utf8")); +const catalogSchema = json( + await readFile(join(root, `schemas/catalog.v${catalog.catalog_version}.schema.json`), "utf8"), +); const ajv = new Ajv2020({ allErrors: true, strict: true }); addFormats(ajv); diff --git a/types/contact/1.md b/types/contact/1.md new file mode 100644 index 0000000..7cbc9fc --- /dev/null +++ b/types/contact/1.md @@ -0,0 +1,224 @@ +--- +kind: mdbase.type +name: contact +version: 1 +description: A fully expanded, editable Markdown-backed Contact using JSContact 2.0 +match: + where: + /@type: Card + version: "2.0" +schema: + dialect: json-schema-2020-12 + value: + $schema: https://json-schema.org/draft/2020-12/schema + title: Contact + description: A local starting shape for a person or organisation using IETF JSContact 2.0. + type: object + required: + - "@type" + - version + properties: + "@type": + const: Card + version: + const: "2.0" + uid: + type: string + minLength: 1 + kind: + enum: + - individual + - group + - org + - location + - device + - application + name: + type: object + properties: + "@type": + const: Name + components: + type: array + minItems: 1 + contains: + type: object + required: + - kind + properties: + kind: + not: + const: separator + items: + type: object + required: + - kind + - value + properties: + "@type": + const: NameComponent + kind: + enum: + - credential + - generation + - given + - given2 + - separator + - surname + - surname2 + - title + value: + type: string + additionalProperties: false + full: + type: string + minLength: 1 + isOrdered: + type: boolean + defaultSeparator: + type: string + anyOf: + - required: + - components + - required: + - full + additionalProperties: false + emails: + type: object + propertyNames: + type: string + minLength: 1 + additionalProperties: + type: object + required: + - address + properties: + "@type": + const: EmailAddress + address: + type: string + format: email + contexts: + type: object + additionalProperties: + const: true + pref: + type: integer + minimum: 1 + label: + type: string + additionalProperties: false + phones: + type: object + propertyNames: + type: string + minLength: 1 + additionalProperties: + type: object + required: + - number + properties: + "@type": + const: Phone + number: + type: string + minLength: 1 + features: + type: object + additionalProperties: + const: true + contexts: + type: object + additionalProperties: + const: true + pref: + type: integer + minimum: 1 + label: + type: string + additionalProperties: false + organizations: + type: object + propertyNames: + type: string + minLength: 1 + additionalProperties: + type: object + properties: + "@type": + const: Organization + name: + type: string + minLength: 1 + units: + type: array + minItems: 1 + items: + type: object + required: + - name + properties: + "@type": + const: OrgUnit + name: + type: string + minLength: 1 + additionalProperties: false + sortAs: + type: string + contexts: + type: object + additionalProperties: + const: true + anyOf: + - required: + - name + - required: + - units + additionalProperties: false + notes: + type: object + propertyNames: + type: string + minLength: 1 + additionalProperties: + type: object + required: + - note + properties: + "@type": + const: Note + note: + type: string + created: + type: string + format: date-time + additionalProperties: false + additionalProperties: false +collection: + display: + name_field: /name/full +implements: + - contract: mdbase.jscontact.card + version: 2.0.0 + fields: + /@type: /@type + version: version + uid: uid + kind: kind + name: name + emails: emails + phones: phones + organizations: organizations + notes: notes +--- + +# Contact + +This type begins as a fully expanded local copy of the JSContact core profile. +Nested fields are declared inline so the type editor can edit them directly. +Its fields belong to the collection and can be added, renamed, or reshaped. +Update the explicit contract mapping whenever a compatible field moves. + +The separately installed contract schema remains the portable boundary. Local +fields that are not mapped into that boundary remain collection-specific.