From 702858a8696ac11a8084f69a9ff50a29c7f2423a Mon Sep 17 00:00:00 2001 From: Mathias Maisberger Date: Thu, 22 Jan 2026 00:21:13 +0200 Subject: [PATCH 1/7] Add BMML (Business Model Markup Language) schema Add positive and negative test files for BMML v2 format validation. BMML is a YAML-based markup language for describing business models, based on Alexander Osterwalder's Business Model Canvas framework. --- src/negative_test/bmml.yaml | 11 +++++++ src/test/bmml.yaml | 62 +++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 src/negative_test/bmml.yaml create mode 100644 src/test/bmml.yaml diff --git a/src/negative_test/bmml.yaml b/src/negative_test/bmml.yaml new file mode 100644 index 00000000000..30a8813f735 --- /dev/null +++ b/src/negative_test/bmml.yaml @@ -0,0 +1,11 @@ +# Invalid BMML: missing required 'portfolio' field in meta +version: "2.0" + +meta: + name: "Invalid Example" + stage: ideation + # portfolio is missing - should be required + +customer_segments: + - id: cs-test + name: Test Segment diff --git a/src/test/bmml.yaml b/src/test/bmml.yaml new file mode 100644 index 00000000000..ed573766296 --- /dev/null +++ b/src/test/bmml.yaml @@ -0,0 +1,62 @@ +version: "2.0" + +meta: + name: "Sample Business Model" + tagline: "A simple BMML example" + portfolio: explore + stage: ideation + +customer_segments: + - id: cs-professionals + name: Busy Professionals + description: Time-poor professionals seeking convenience + +value_propositions: + - id: vp-convenience + name: Convenient Solution + description: Easy-to-use product that saves time + +channels: + - id: ch-website + name: Website + for: + value_propositions: [vp-convenience] + customer_segments: [cs-professionals] + +customer_relationships: + - id: cr-self-service + name: Self-Service + for: + customer_segments: [cs-professionals] + +revenue_streams: + - id: rs-subscription + name: Monthly Subscription + from: + customer_segments: [cs-professionals] + for: + value_propositions: [vp-convenience] + +key_resources: + - id: kr-platform + name: Technology Platform + for: + value_propositions: [vp-convenience] + +key_activities: + - id: ka-development + name: Product Development + for: + value_propositions: [vp-convenience] + +key_partnerships: + - id: kp-vendors + name: Technology Vendors + for: + key_resources: [kr-platform] + +costs: + - id: cost-hosting + name: Hosting Costs + for: + key_resources: [kr-platform] From e192c2de472e11ef1152774fb3d420212b31ae5f Mon Sep 17 00:00:00 2001 From: Mathias Maisberger Date: Wed, 21 Jan 2026 22:22:46 +0000 Subject: [PATCH 2/7] Add BMML (Business Model Markup Language) schema BMML is a YAML-based markup language for describing business models, based on Alexander Osterwalder's Business Model Canvas framework. - Adds catalog entry for BMML schema - Schema URL points to GitHub Pages hosted version - FileMatch patterns: *.bmml, *.bmml.yaml, *.bmml.yml Project: https://github.com/hiasinho/bmml --- src/api/json/catalog.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/api/json/catalog.json b/src/api/json/catalog.json index 3cc81551380..7bbf2d3437b 100644 --- a/src/api/json/catalog.json +++ b/src/api/json/catalog.json @@ -9241,6 +9241,12 @@ "description": "Bento stream configuration file", "fileMatch": ["bento.json", "bento.yml", "bento.yaml"], "url": "https://raw.githubusercontent.com/warpstreamlabs/bento/refs/heads/main/resources/schemastore/bento.json" + }, + { + "name": "BMML", + "description": "Business Model Markup Language - a YAML format for describing business models based on Alexander Osterwalder's Business Model Canvas", + "fileMatch": ["*.bmml", "*.bmml.yaml", "*.bmml.yml"], + "url": "https://hiasinho.github.io/bmml/bmclang-v2.schema.json" } ] } From c00891654f5bef51ec305c2089d52e8ccd45d7df Mon Sep 17 00:00:00 2001 From: Mathias Maisberger Date: Thu, 22 Jan 2026 00:26:42 +0200 Subject: [PATCH 3/7] style: fix prettier formatting (single quotes) --- src/negative_test/bmml.yaml | 4 ++-- src/test/bmml.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/negative_test/bmml.yaml b/src/negative_test/bmml.yaml index 30a8813f735..280971b7316 100644 --- a/src/negative_test/bmml.yaml +++ b/src/negative_test/bmml.yaml @@ -1,8 +1,8 @@ # Invalid BMML: missing required 'portfolio' field in meta -version: "2.0" +version: '2.0' meta: - name: "Invalid Example" + name: 'Invalid Example' stage: ideation # portfolio is missing - should be required diff --git a/src/test/bmml.yaml b/src/test/bmml.yaml index ed573766296..0433dec288c 100644 --- a/src/test/bmml.yaml +++ b/src/test/bmml.yaml @@ -1,8 +1,8 @@ -version: "2.0" +version: '2.0' meta: - name: "Sample Business Model" - tagline: "A simple BMML example" + name: 'Sample Business Model' + tagline: 'A simple BMML example' portfolio: explore stage: ideation From 3c9e9132a950609f1e3b3ebd766589954b885c59 Mon Sep 17 00:00:00 2001 From: Mathias Maisberger Date: Tue, 17 Feb 2026 20:40:17 +0200 Subject: [PATCH 4/7] fix: close BMML catalog entry and move negative test to subdirectory - Add missing closing bracket and comma after BMML entry in catalog.json - Move src/negative_test/bmml.yaml to src/negative_test/bmml/bmml.yaml --- src/api/json/catalog.json | 2 ++ src/negative_test/{ => bmml}/bmml.yaml | 0 2 files changed, 2 insertions(+) rename src/negative_test/{ => bmml}/bmml.yaml (100%) diff --git a/src/api/json/catalog.json b/src/api/json/catalog.json index 9c095c4df18..12f88789990 100644 --- a/src/api/json/catalog.json +++ b/src/api/json/catalog.json @@ -9335,6 +9335,8 @@ "description": "Business Model Markup Language - a YAML format for describing business models based on Alexander Osterwalder's Business Model Canvas", "fileMatch": ["*.bmml", "*.bmml.yaml", "*.bmml.yml"], "url": "https://hiasinho.github.io/bmml/bmclang-v2.schema.json" + }, + { "name": "pgxgen", "description": "pgxgen configuration file", "fileMatch": ["pgxgen.yml", "pgxgen.yaml"], diff --git a/src/negative_test/bmml.yaml b/src/negative_test/bmml/bmml.yaml similarity index 100% rename from src/negative_test/bmml.yaml rename to src/negative_test/bmml/bmml.yaml From 7c3d52f7660b7ba11bf30825e46b02d68936eccc Mon Sep 17 00:00:00 2001 From: Mathias Maisberger Date: Tue, 17 Feb 2026 20:43:23 +0200 Subject: [PATCH 5/7] fix: move positive test file to subdirectory Move src/test/bmml.yaml to src/test/bmml/bmml.yaml (SchemaStore requires test files in subdirectories) --- src/test/{ => bmml}/bmml.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/test/{ => bmml}/bmml.yaml (100%) diff --git a/src/test/bmml.yaml b/src/test/bmml/bmml.yaml similarity index 100% rename from src/test/bmml.yaml rename to src/test/bmml/bmml.yaml From 9e42d05119a682b06b5859a5e6f7baea657e2b8b Mon Sep 17 00:00:00 2001 From: Mathias Maisberger Date: Tue, 17 Feb 2026 20:49:19 +0200 Subject: [PATCH 6/7] fix: add local schema, update catalog URL, and add schema pragmas - Add local copy of BMML schema to src/schemas/json/bmml.json - Point catalog.json URL to SchemaStore's own raw URL - Add yaml-language-server schema pragmas to test files --- src/api/json/catalog.json | 2 +- src/negative_test/bmml/bmml.yaml | 1 + src/schemas/json/bmml.json | 732 +++++++++++++++++++++++++++++++ src/test/bmml/bmml.yaml | 1 + 4 files changed, 735 insertions(+), 1 deletion(-) create mode 100644 src/schemas/json/bmml.json diff --git a/src/api/json/catalog.json b/src/api/json/catalog.json index 12f88789990..23136a847f5 100644 --- a/src/api/json/catalog.json +++ b/src/api/json/catalog.json @@ -9334,7 +9334,7 @@ "name": "BMML", "description": "Business Model Markup Language - a YAML format for describing business models based on Alexander Osterwalder's Business Model Canvas", "fileMatch": ["*.bmml", "*.bmml.yaml", "*.bmml.yml"], - "url": "https://hiasinho.github.io/bmml/bmclang-v2.schema.json" + "url": "https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/bmml.json" }, { "name": "pgxgen", diff --git a/src/negative_test/bmml/bmml.yaml b/src/negative_test/bmml/bmml.yaml index 280971b7316..cf0fba7f3ce 100644 --- a/src/negative_test/bmml/bmml.yaml +++ b/src/negative_test/bmml/bmml.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../schemas/json/bmml.json # Invalid BMML: missing required 'portfolio' field in meta version: '2.0' diff --git a/src/schemas/json/bmml.json b/src/schemas/json/bmml.json new file mode 100644 index 00000000000..3b7df9fe334 --- /dev/null +++ b/src/schemas/json/bmml.json @@ -0,0 +1,732 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://hiasinho.github.io/bmml/bmclang-v2.schema.json", + "title": "BMML Business Model v2", + "description": "A YAML-based format for describing business models, based on Alexander Osterwalder's work (v2 structure)", + "$comment": "DESIGN: v2 introduces three key principles: (1) SYMMETRY - Customer Profile lives in segments, Value Map lives in propositions, (2) CONSISTENCY - all relationships use for:/from: with typed sub-keys, (3) OPTIONALITY - VPC detail (profiles, value maps, fits) is optional; BMC works standalone. See specs/bmclang-v2-structure.md for rationale.", + "type": "object", + "required": ["version", "meta"], + "additionalProperties": false, + "properties": { + "version": { + "type": "string", + "description": "BMML format version", + "const": "2.0" + }, + "meta": { + "$ref": "#/$defs/Meta" + }, + "customer_segments": { + "type": "array", + "description": "Customer segments the business targets", + "items": { + "$ref": "#/$defs/CustomerSegment" + } + }, + "value_propositions": { + "type": "array", + "description": "Value propositions offered to customer segments", + "items": { + "$ref": "#/$defs/ValueProposition" + } + }, + "fits": { + "type": "array", + "$comment": "DESIGN: Fits are top-level (not nested under VP) because a fit connects two peers (VP and CS) - neither owns the relationship. One VP can fit multiple segments differently, and one segment can be served by multiple VPs.", + "description": "Connections between value propositions and customer segments (VPC detail)", + "items": { + "$ref": "#/$defs/Fit" + } + }, + "channels": { + "type": "array", + "description": "Channels to reach customer segments", + "items": { + "$ref": "#/$defs/Channel" + } + }, + "customer_relationships": { + "type": "array", + "description": "Types of relationships with customer segments", + "items": { + "$ref": "#/$defs/CustomerRelationship" + } + }, + "revenue_streams": { + "type": "array", + "description": "Revenue streams from customer segments", + "items": { + "$ref": "#/$defs/RevenueStream" + } + }, + "key_resources": { + "type": "array", + "description": "Key resources needed to deliver value propositions", + "items": { + "$ref": "#/$defs/KeyResource" + } + }, + "key_activities": { + "type": "array", + "description": "Key activities needed to deliver value propositions", + "items": { + "$ref": "#/$defs/KeyActivity" + } + }, + "key_partnerships": { + "type": "array", + "description": "Key partnerships that provide resources or activities", + "items": { + "$ref": "#/$defs/KeyPartnership" + } + }, + "costs": { + "type": "array", + "$comment": "DESIGN: v2 uses 'costs' array instead of v1's 'cost_structure' object with nested 'major_costs'. This aligns with how other infrastructure entities are defined (simple array of items with for: relations).", + "description": "Cost items (replaces v1 cost_structure)", + "items": { + "$ref": "#/$defs/Cost" + } + } + }, + "$defs": { + "Meta": { + "type": "object", + "description": "Business model metadata", + "required": ["name", "portfolio", "stage"], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Name of the business model" + }, + "tagline": { + "type": "string", + "description": "One-liner description" + }, + "created": { + "type": "string", + "format": "date", + "description": "Creation date (ISO 8601)" + }, + "updated": { + "type": "string", + "format": "date", + "description": "Last updated date (ISO 8601)" + }, + "portfolio": { + "type": "string", + "enum": ["explore", "exploit"], + "$comment": "DESIGN: From Osterwalder's 'The Invincible Company' - companies manage two portfolios: 'explore' (searching for new value with high uncertainty) and 'exploit' (managing existing business with low uncertainty).", + "description": "Portfolio position (Osterwalder's Invincible Company)" + }, + "stage": { + "type": "string", + "$comment": "DESIGN: Valid stages are constrained by portfolio. Explore: ideation→discovery→validation→acceleration. Exploit: improve→grow→sustain→retire. 'transfer' marks the shift from explore to exploit.", + "description": "Current stage within the portfolio" + }, + "derived_from": { + "type": "string", + "description": "Relative path to parent business model file" + } + }, + "allOf": [ + { + "if": { + "properties": { + "portfolio": { "const": "explore" } + } + }, + "then": { + "properties": { + "stage": { + "enum": ["ideation", "discovery", "validation", "acceleration", "transfer"] + } + } + } + }, + { + "if": { + "properties": { + "portfolio": { "const": "exploit" } + } + }, + "then": { + "properties": { + "stage": { + "enum": ["improve", "grow", "sustain", "retire", "transfer"] + } + } + } + } + ] + }, + "CustomerSegment": { + "type": "object", + "$comment": "DESIGN: Customer Profile (jobs/pains/gains) is nested here for SYMMETRY with Value Map in ValueProposition. This is v2's key structural change - profile lives with the segment it describes.", + "description": "A customer segment with optional profile (jobs, pains, gains)", + "required": ["id", "name"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^cs-[a-z0-9-]+$", + "description": "Unique identifier with cs- prefix" + }, + "name": { + "type": "string", + "description": "Name of the customer segment" + }, + "description": { + "type": "string", + "description": "Who they are" + }, + "jobs": { + "type": "array", + "description": "Jobs to be done (Customer Profile)", + "items": { + "$ref": "#/$defs/Job" + } + }, + "pains": { + "type": "array", + "description": "Customer pains (Customer Profile)", + "items": { + "$ref": "#/$defs/Pain" + } + }, + "gains": { + "type": "array", + "description": "Customer gains (Customer Profile)", + "items": { + "$ref": "#/$defs/Gain" + } + } + } + }, + "Job": { + "type": "object", + "$comment": "DESIGN: v2 removes 'type' and 'importance' fields from v1. Types can be added later as the format matures. This simplifies the initial structure.", + "description": "A job the customer is trying to accomplish", + "required": ["id", "description"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^job-[a-z0-9-]+$", + "description": "Unique identifier with job- prefix" + }, + "description": { + "type": "string", + "description": "What they're trying to accomplish" + } + } + }, + "Pain": { + "type": "object", + "description": "A customer pain point", + "required": ["id", "description"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^pain-[a-z0-9-]+$", + "description": "Unique identifier with pain- prefix" + }, + "description": { + "type": "string", + "description": "What frustrates them or blocks them" + } + } + }, + "Gain": { + "type": "object", + "description": "A desired customer gain", + "required": ["id", "description"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^gain-[a-z0-9-]+$", + "description": "Unique identifier with gain- prefix" + }, + "description": { + "type": "string", + "description": "What they want to achieve or experience" + } + } + }, + "ValueProposition": { + "type": "object", + "$comment": "DESIGN: Value Map (products/pain_relievers/gain_creators) is nested here for SYMMETRY with Customer Profile in CustomerSegment. In v1, pain_relievers and gain_creators lived in fits - v2 moves them here where they belong conceptually.", + "description": "A value proposition with optional value map (products, pain relievers, gain creators)", + "required": ["id", "name"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^vp-[a-z0-9-]+$", + "description": "Unique identifier with vp- prefix" + }, + "name": { + "type": "string", + "description": "Name of the value proposition" + }, + "description": { + "type": "string", + "description": "What you offer" + }, + "products_services": { + "type": "array", + "description": "Products and services that deliver this value (Value Map)", + "items": { + "$ref": "#/$defs/ProductService" + } + }, + "pain_relievers": { + "type": "array", + "description": "How this VP relieves customer pains (Value Map)", + "items": { + "$ref": "#/$defs/PainReliever" + } + }, + "gain_creators": { + "type": "array", + "description": "How this VP creates customer gains (Value Map)", + "items": { + "$ref": "#/$defs/GainCreator" + } + } + } + }, + "ProductService": { + "type": "object", + "description": "A product or service offering", + "required": ["id", "name"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^ps-[a-z0-9-]+$", + "description": "Unique identifier with ps- prefix" + }, + "name": { + "type": "string", + "description": "Name of the product or service" + } + } + }, + "PainReliever": { + "type": "object", + "$comment": "DESIGN: New in v2 - pr-* prefix enables type inference in fit mappings. A [pr-*, pain-*] tuple is inferred as pain relief without explicit type field.", + "description": "How a value proposition relieves a specific pain", + "required": ["id", "name"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^pr-[a-z0-9-]+$", + "description": "Unique identifier with pr- prefix" + }, + "name": { + "type": "string", + "description": "How it relieves pain" + } + } + }, + "GainCreator": { + "type": "object", + "$comment": "DESIGN: New in v2 - gc-* prefix enables type inference in fit mappings. A [gc-*, gain-*] tuple is inferred as gain creation without explicit type field.", + "description": "How a value proposition creates a specific gain", + "required": ["id", "name"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^gc-[a-z0-9-]+$", + "description": "Unique identifier with gc- prefix" + }, + "name": { + "type": "string", + "description": "How it creates gain" + } + } + }, + "ForRelation": { + "type": "object", + "$comment": "DESIGN: 'for:' means 'this entity serves/supports/targets these other entities'. Sub-keys match section names exactly (value_propositions not propositions) for self-documenting references and validation simplicity.", + "description": "Relationship target - which entities this serves/supports", + "additionalProperties": false, + "properties": { + "value_propositions": { + "type": "array", + "items": { + "type": "string", + "pattern": "^vp-[a-z0-9-]+$" + }, + "description": "Value propositions this relates to" + }, + "customer_segments": { + "type": "array", + "items": { + "type": "string", + "pattern": "^cs-[a-z0-9-]+$" + }, + "description": "Customer segments this relates to" + }, + "key_resources": { + "type": "array", + "items": { + "type": "string", + "pattern": "^kr-[a-z0-9-]+$" + }, + "description": "Key resources this relates to" + }, + "key_activities": { + "type": "array", + "items": { + "type": "string", + "pattern": "^ka-[a-z0-9-]+$" + }, + "description": "Key activities this relates to" + } + } + }, + "FromRelation": { + "type": "object", + "$comment": "DESIGN: 'from:' means 'this entity receives from/is sourced by these other entities'. Currently only used by revenue_streams to indicate who pays.", + "description": "Relationship source - which entities this comes from", + "additionalProperties": false, + "properties": { + "customer_segments": { + "type": "array", + "items": { + "type": "string", + "pattern": "^cs-[a-z0-9-]+$" + }, + "description": "Customer segments this comes from" + } + } + }, + "Fit": { + "type": "object", + "$comment": "DESIGN: Fit is a first-class entity (not nested) because it connects two peers. The 'for:' pattern requires both VP and CS refs. Mappings use tuples [reliever/creator, pain/gain] for conciseness - type is inferred from ID prefixes.", + "description": "A fit between value propositions and customer segments (VPC detail)", + "required": ["id", "for"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^fit-[a-z0-9-]+$", + "description": "Unique identifier with fit- prefix" + }, + "for": { + "type": "object", + "description": "Which VP(s) and CS(s) this fit connects", + "additionalProperties": false, + "properties": { + "value_propositions": { + "type": "array", + "items": { + "type": "string", + "pattern": "^vp-[a-z0-9-]+$" + }, + "minItems": 1, + "description": "Value propositions in this fit" + }, + "customer_segments": { + "type": "array", + "items": { + "type": "string", + "pattern": "^cs-[a-z0-9-]+$" + }, + "minItems": 1, + "description": "Customer segments in this fit" + } + }, + "required": ["value_propositions", "customer_segments"] + }, + "mappings": { + "type": "array", + "$comment": "DESIGN: Tuples replace v1's verbose objects. [pr-x, pain-y] = pain relief, [gc-x, gain-y] = gain creation. Type inference from prefixes eliminates explicit type fields.", + "description": "Tuple mappings: [reliever/creator, pain/gain]", + "items": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 2, + "maxItems": 2 + } + } + } + }, + "Channel": { + "type": "object", + "$comment": "DESIGN: Channels have a TERNARY relationship - they deliver VPs TO CSs. The 'for:' pattern with both sub-keys expresses this cleanly without creating join entities.", + "description": "A channel to reach customer segments with value propositions", + "required": ["id", "name"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^ch-[a-z0-9-]+$", + "description": "Unique identifier with ch- prefix" + }, + "name": { + "type": "string", + "description": "Name of the channel" + }, + "for": { + "type": "object", + "description": "Which VPs and CSs this channel serves", + "additionalProperties": false, + "properties": { + "value_propositions": { + "type": "array", + "items": { + "type": "string", + "pattern": "^vp-[a-z0-9-]+$" + }, + "description": "Value propositions delivered through this channel" + }, + "customer_segments": { + "type": "array", + "items": { + "type": "string", + "pattern": "^cs-[a-z0-9-]+$" + }, + "description": "Customer segments reached through this channel" + } + } + } + } + }, + "CustomerRelationship": { + "type": "object", + "description": "A type of relationship with customer segments", + "required": ["id", "name"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^cr-[a-z0-9-]+$", + "description": "Unique identifier with cr- prefix" + }, + "name": { + "type": "string", + "description": "Name/type of relationship" + }, + "for": { + "type": "object", + "description": "Which customer segments this relationship applies to", + "additionalProperties": false, + "properties": { + "customer_segments": { + "type": "array", + "items": { + "type": "string", + "pattern": "^cs-[a-z0-9-]+$" + }, + "description": "Customer segments with this relationship type" + } + } + } + } + }, + "RevenueStream": { + "type": "object", + "$comment": "DESIGN: Revenue has bidirectional relationships - 'from:' (who pays) and 'for:' (what they pay for). This is the only entity using both prepositions, expressing: CS pays FOR VP.", + "description": "A revenue stream from customer segments for value propositions", + "required": ["id", "name"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^rs-[a-z0-9-]+$", + "description": "Unique identifier with rs- prefix" + }, + "name": { + "type": "string", + "description": "Name of the revenue stream" + }, + "from": { + "type": "object", + "description": "Who pays (source of revenue)", + "additionalProperties": false, + "properties": { + "customer_segments": { + "type": "array", + "items": { + "type": "string", + "pattern": "^cs-[a-z0-9-]+$" + }, + "description": "Customer segments this revenue comes from" + } + } + }, + "for": { + "type": "object", + "description": "What they pay for", + "additionalProperties": false, + "properties": { + "value_propositions": { + "type": "array", + "items": { + "type": "string", + "pattern": "^vp-[a-z0-9-]+$" + }, + "description": "Value propositions this revenue is for" + } + } + } + } + }, + "KeyResource": { + "type": "object", + "description": "A key resource needed to deliver value", + "required": ["id", "name"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^kr-[a-z0-9-]+$", + "description": "Unique identifier with kr- prefix" + }, + "name": { + "type": "string", + "description": "Name of the resource" + }, + "for": { + "type": "object", + "description": "Which value propositions need this resource", + "additionalProperties": false, + "properties": { + "value_propositions": { + "type": "array", + "items": { + "type": "string", + "pattern": "^vp-[a-z0-9-]+$" + }, + "description": "Value propositions that need this resource" + } + } + } + } + }, + "KeyActivity": { + "type": "object", + "description": "A key activity needed to deliver value", + "required": ["id", "name"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^ka-[a-z0-9-]+$", + "description": "Unique identifier with ka- prefix" + }, + "name": { + "type": "string", + "description": "Name of the activity" + }, + "for": { + "type": "object", + "description": "Which value propositions require this activity", + "additionalProperties": false, + "properties": { + "value_propositions": { + "type": "array", + "items": { + "type": "string", + "pattern": "^vp-[a-z0-9-]+$" + }, + "description": "Value propositions that require this activity" + } + } + } + } + }, + "KeyPartnership": { + "type": "object", + "$comment": "DESIGN: Partners link to infrastructure (resources/activities) via 'for:', not to VPs directly. This reflects reality: partners provide capabilities that enable VPs, not the VPs themselves.", + "description": "A key partnership that provides resources or activities", + "required": ["id", "name"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^kp-[a-z0-9-]+$", + "description": "Unique identifier with kp- prefix" + }, + "name": { + "type": "string", + "description": "Name of the partner" + }, + "for": { + "type": "object", + "description": "Which resources/activities this partner provides", + "additionalProperties": false, + "properties": { + "key_resources": { + "type": "array", + "items": { + "type": "string", + "pattern": "^kr-[a-z0-9-]+$" + }, + "description": "Resources this partner provides" + }, + "key_activities": { + "type": "array", + "items": { + "type": "string", + "pattern": "^ka-[a-z0-9-]+$" + }, + "description": "Activities this partner performs" + } + } + } + } + }, + "Cost": { + "type": "object", + "$comment": "DESIGN: New in v2 with cost-* prefix. Costs link to infrastructure (resources/activities) like partnerships do. 'for:' here means 'incurred by' these resources/activities.", + "description": "A cost item linked to resources or activities", + "required": ["id", "name"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "pattern": "^cost-[a-z0-9-]+$", + "description": "Unique identifier with cost- prefix" + }, + "name": { + "type": "string", + "description": "Name of the cost" + }, + "for": { + "type": "object", + "description": "Which resources/activities incur this cost", + "additionalProperties": false, + "properties": { + "key_resources": { + "type": "array", + "items": { + "type": "string", + "pattern": "^kr-[a-z0-9-]+$" + }, + "description": "Resources that incur this cost" + }, + "key_activities": { + "type": "array", + "items": { + "type": "string", + "pattern": "^ka-[a-z0-9-]+$" + }, + "description": "Activities that incur this cost" + } + } + } + } + } + } +} diff --git a/src/test/bmml/bmml.yaml b/src/test/bmml/bmml.yaml index 0433dec288c..d1fc7432535 100644 --- a/src/test/bmml/bmml.yaml +++ b/src/test/bmml/bmml.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../schemas/json/bmml.json version: '2.0' meta: From 1099d09e4b2f351411791a80d20ed14933c63b85 Mon Sep 17 00:00:00 2001 From: Mathias Maisberger Date: Tue, 17 Feb 2026 20:51:23 +0200 Subject: [PATCH 7/7] style: format bmml.json with prettier --- src/schemas/json/bmml.json | 212 +++++++++++++++++++------------------ 1 file changed, 109 insertions(+), 103 deletions(-) diff --git a/src/schemas/json/bmml.json b/src/schemas/json/bmml.json index 3b7df9fe334..8eefbaca788 100644 --- a/src/schemas/json/bmml.json +++ b/src/schemas/json/bmml.json @@ -1,94 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://hiasinho.github.io/bmml/bmclang-v2.schema.json", - "title": "BMML Business Model v2", - "description": "A YAML-based format for describing business models, based on Alexander Osterwalder's work (v2 structure)", "$comment": "DESIGN: v2 introduces three key principles: (1) SYMMETRY - Customer Profile lives in segments, Value Map lives in propositions, (2) CONSISTENCY - all relationships use for:/from: with typed sub-keys, (3) OPTIONALITY - VPC detail (profiles, value maps, fits) is optional; BMC works standalone. See specs/bmclang-v2-structure.md for rationale.", - "type": "object", - "required": ["version", "meta"], - "additionalProperties": false, - "properties": { - "version": { - "type": "string", - "description": "BMML format version", - "const": "2.0" - }, - "meta": { - "$ref": "#/$defs/Meta" - }, - "customer_segments": { - "type": "array", - "description": "Customer segments the business targets", - "items": { - "$ref": "#/$defs/CustomerSegment" - } - }, - "value_propositions": { - "type": "array", - "description": "Value propositions offered to customer segments", - "items": { - "$ref": "#/$defs/ValueProposition" - } - }, - "fits": { - "type": "array", - "$comment": "DESIGN: Fits are top-level (not nested under VP) because a fit connects two peers (VP and CS) - neither owns the relationship. One VP can fit multiple segments differently, and one segment can be served by multiple VPs.", - "description": "Connections between value propositions and customer segments (VPC detail)", - "items": { - "$ref": "#/$defs/Fit" - } - }, - "channels": { - "type": "array", - "description": "Channels to reach customer segments", - "items": { - "$ref": "#/$defs/Channel" - } - }, - "customer_relationships": { - "type": "array", - "description": "Types of relationships with customer segments", - "items": { - "$ref": "#/$defs/CustomerRelationship" - } - }, - "revenue_streams": { - "type": "array", - "description": "Revenue streams from customer segments", - "items": { - "$ref": "#/$defs/RevenueStream" - } - }, - "key_resources": { - "type": "array", - "description": "Key resources needed to deliver value propositions", - "items": { - "$ref": "#/$defs/KeyResource" - } - }, - "key_activities": { - "type": "array", - "description": "Key activities needed to deliver value propositions", - "items": { - "$ref": "#/$defs/KeyActivity" - } - }, - "key_partnerships": { - "type": "array", - "description": "Key partnerships that provide resources or activities", - "items": { - "$ref": "#/$defs/KeyPartnership" - } - }, - "costs": { - "type": "array", - "$comment": "DESIGN: v2 uses 'costs' array instead of v1's 'cost_structure' object with nested 'major_costs'. This aligns with how other infrastructure entities are defined (simple array of items with for: relations).", - "description": "Cost items (replaces v1 cost_structure)", - "items": { - "$ref": "#/$defs/Cost" - } - } - }, "$defs": { "Meta": { "type": "object", @@ -115,14 +28,14 @@ "description": "Last updated date (ISO 8601)" }, "portfolio": { + "$comment": "DESIGN: From Osterwalder's 'The Invincible Company' - companies manage two portfolios: 'explore' (searching for new value with high uncertainty) and 'exploit' (managing existing business with low uncertainty).", "type": "string", "enum": ["explore", "exploit"], - "$comment": "DESIGN: From Osterwalder's 'The Invincible Company' - companies manage two portfolios: 'explore' (searching for new value with high uncertainty) and 'exploit' (managing existing business with low uncertainty).", "description": "Portfolio position (Osterwalder's Invincible Company)" }, "stage": { - "type": "string", "$comment": "DESIGN: Valid stages are constrained by portfolio. Explore: ideation→discovery→validation→acceleration. Exploit: improve→grow→sustain→retire. 'transfer' marks the shift from explore to exploit.", + "type": "string", "description": "Current stage within the portfolio" }, "derived_from": { @@ -140,7 +53,13 @@ "then": { "properties": { "stage": { - "enum": ["ideation", "discovery", "validation", "acceleration", "transfer"] + "enum": [ + "ideation", + "discovery", + "validation", + "acceleration", + "transfer" + ] } } } @@ -162,8 +81,8 @@ ] }, "CustomerSegment": { - "type": "object", "$comment": "DESIGN: Customer Profile (jobs/pains/gains) is nested here for SYMMETRY with Value Map in ValueProposition. This is v2's key structural change - profile lives with the segment it describes.", + "type": "object", "description": "A customer segment with optional profile (jobs, pains, gains)", "required": ["id", "name"], "additionalProperties": false, @@ -205,8 +124,8 @@ } }, "Job": { - "type": "object", "$comment": "DESIGN: v2 removes 'type' and 'importance' fields from v1. Types can be added later as the format matures. This simplifies the initial structure.", + "type": "object", "description": "A job the customer is trying to accomplish", "required": ["id", "description"], "additionalProperties": false, @@ -257,8 +176,8 @@ } }, "ValueProposition": { - "type": "object", "$comment": "DESIGN: Value Map (products/pain_relievers/gain_creators) is nested here for SYMMETRY with Customer Profile in CustomerSegment. In v1, pain_relievers and gain_creators lived in fits - v2 moves them here where they belong conceptually.", + "type": "object", "description": "A value proposition with optional value map (products, pain relievers, gain creators)", "required": ["id", "name"], "additionalProperties": false, @@ -317,8 +236,8 @@ } }, "PainReliever": { - "type": "object", "$comment": "DESIGN: New in v2 - pr-* prefix enables type inference in fit mappings. A [pr-*, pain-*] tuple is inferred as pain relief without explicit type field.", + "type": "object", "description": "How a value proposition relieves a specific pain", "required": ["id", "name"], "additionalProperties": false, @@ -335,8 +254,8 @@ } }, "GainCreator": { - "type": "object", "$comment": "DESIGN: New in v2 - gc-* prefix enables type inference in fit mappings. A [gc-*, gain-*] tuple is inferred as gain creation without explicit type field.", + "type": "object", "description": "How a value proposition creates a specific gain", "required": ["id", "name"], "additionalProperties": false, @@ -353,8 +272,8 @@ } }, "ForRelation": { - "type": "object", "$comment": "DESIGN: 'for:' means 'this entity serves/supports/targets these other entities'. Sub-keys match section names exactly (value_propositions not propositions) for self-documenting references and validation simplicity.", + "type": "object", "description": "Relationship target - which entities this serves/supports", "additionalProperties": false, "properties": { @@ -393,8 +312,8 @@ } }, "FromRelation": { - "type": "object", "$comment": "DESIGN: 'from:' means 'this entity receives from/is sourced by these other entities'. Currently only used by revenue_streams to indicate who pays.", + "type": "object", "description": "Relationship source - which entities this comes from", "additionalProperties": false, "properties": { @@ -409,8 +328,8 @@ } }, "Fit": { - "type": "object", "$comment": "DESIGN: Fit is a first-class entity (not nested) because it connects two peers. The 'for:' pattern requires both VP and CS refs. Mappings use tuples [reliever/creator, pain/gain] for conciseness - type is inferred from ID prefixes.", + "type": "object", "description": "A fit between value propositions and customer segments (VPC detail)", "required": ["id", "for"], "additionalProperties": false, @@ -447,8 +366,8 @@ "required": ["value_propositions", "customer_segments"] }, "mappings": { - "type": "array", "$comment": "DESIGN: Tuples replace v1's verbose objects. [pr-x, pain-y] = pain relief, [gc-x, gain-y] = gain creation. Type inference from prefixes eliminates explicit type fields.", + "type": "array", "description": "Tuple mappings: [reliever/creator, pain/gain]", "items": { "type": "array", @@ -462,8 +381,8 @@ } }, "Channel": { - "type": "object", "$comment": "DESIGN: Channels have a TERNARY relationship - they deliver VPs TO CSs. The 'for:' pattern with both sub-keys expresses this cleanly without creating join entities.", + "type": "object", "description": "A channel to reach customer segments with value propositions", "required": ["id", "name"], "additionalProperties": false, @@ -535,8 +454,8 @@ } }, "RevenueStream": { - "type": "object", "$comment": "DESIGN: Revenue has bidirectional relationships - 'from:' (who pays) and 'for:' (what they pay for). This is the only entity using both prepositions, expressing: CS pays FOR VP.", + "type": "object", "description": "A revenue stream from customer segments for value propositions", "required": ["id", "name"], "additionalProperties": false, @@ -647,8 +566,8 @@ } }, "KeyPartnership": { - "type": "object", "$comment": "DESIGN: Partners link to infrastructure (resources/activities) via 'for:', not to VPs directly. This reflects reality: partners provide capabilities that enable VPs, not the VPs themselves.", + "type": "object", "description": "A key partnership that provides resources or activities", "required": ["id", "name"], "additionalProperties": false, @@ -688,8 +607,8 @@ } }, "Cost": { - "type": "object", "$comment": "DESIGN: New in v2 with cost-* prefix. Costs link to infrastructure (resources/activities) like partnerships do. 'for:' here means 'incurred by' these resources/activities.", + "type": "object", "description": "A cost item linked to resources or activities", "required": ["id", "name"], "additionalProperties": false, @@ -728,5 +647,92 @@ } } } + }, + "title": "BMML Business Model v2", + "description": "A YAML-based format for describing business models, based on Alexander Osterwalder's work (v2 structure)", + "type": "object", + "required": ["version", "meta"], + "additionalProperties": false, + "properties": { + "version": { + "type": "string", + "description": "BMML format version", + "const": "2.0" + }, + "meta": { + "$ref": "#/$defs/Meta" + }, + "customer_segments": { + "type": "array", + "description": "Customer segments the business targets", + "items": { + "$ref": "#/$defs/CustomerSegment" + } + }, + "value_propositions": { + "type": "array", + "description": "Value propositions offered to customer segments", + "items": { + "$ref": "#/$defs/ValueProposition" + } + }, + "fits": { + "$comment": "DESIGN: Fits are top-level (not nested under VP) because a fit connects two peers (VP and CS) - neither owns the relationship. One VP can fit multiple segments differently, and one segment can be served by multiple VPs.", + "type": "array", + "description": "Connections between value propositions and customer segments (VPC detail)", + "items": { + "$ref": "#/$defs/Fit" + } + }, + "channels": { + "type": "array", + "description": "Channels to reach customer segments", + "items": { + "$ref": "#/$defs/Channel" + } + }, + "customer_relationships": { + "type": "array", + "description": "Types of relationships with customer segments", + "items": { + "$ref": "#/$defs/CustomerRelationship" + } + }, + "revenue_streams": { + "type": "array", + "description": "Revenue streams from customer segments", + "items": { + "$ref": "#/$defs/RevenueStream" + } + }, + "key_resources": { + "type": "array", + "description": "Key resources needed to deliver value propositions", + "items": { + "$ref": "#/$defs/KeyResource" + } + }, + "key_activities": { + "type": "array", + "description": "Key activities needed to deliver value propositions", + "items": { + "$ref": "#/$defs/KeyActivity" + } + }, + "key_partnerships": { + "type": "array", + "description": "Key partnerships that provide resources or activities", + "items": { + "$ref": "#/$defs/KeyPartnership" + } + }, + "costs": { + "$comment": "DESIGN: v2 uses 'costs' array instead of v1's 'cost_structure' object with nested 'major_costs'. This aligns with how other infrastructure entities are defined (simple array of items with for: relations).", + "type": "array", + "description": "Cost items (replaces v1 cost_structure)", + "items": { + "$ref": "#/$defs/Cost" + } + } } }