# Positioning schema — marketing as queryable protocol surface. # # Three artefact kinds: # - Audience : a named segment with its primary pain # - ValueProp : a claim aimed at one or more audiences, backed by ADRs # - Campaign : a GTM moment bundling value-props for specific channels # # Each artefact lives in its own .ncl file under `.ontoref/positioning/`. # Narrative copy (the prose that sells) is held in markdown files referenced # via `narrative_path` — NCL holds the verifiable skeleton, markdown holds # the messaging body. The two split lets the protocol validate claims # (audience present, evidence_adrs non-empty when Live, launch_criteria pass) # without trying to be a copywriter. let status_type = [| 'Draft, 'Validated, 'Live, 'Retired |] in # Audience validation state — an audience is a Hypothesis until a Proof # references it (coherence rule 2). Defaults to 'Hypothesis so existing # audience files remain valid without edits. let audience_status_type = [| 'Hypothesis, 'Validated |] in # Launch readiness check — structurally identical to the ConstraintCheck # type in `.ontoref/adrs/adr-schema.ncl`. Duplicated intentionally as a # sketch trade-off: a future lift-out into `ontology/schemas/check.ncl` # (shared by ADR and positioning layers) is tracked in ADR-035's negative # consequences. Until then, validators in both layers MUST keep these # variants in sync. let check_tag_type = [| 'Grep, 'NuCmd, 'ApiCall, 'FileExists, |] in let launch_check_type = { tag | check_tag_type, .. } in # Audience — one file per segment. let audience_type = { id | String, # kebab-case, e.g. "platform-engineer" name | String, # human-readable display name description | String, # who they are (one paragraph) primary_pain | String, # the pain we name and claim to solve context | String | default = "", # constraints, daily tools, team shape channels | Array String | default = [], # where they consume content (blog, conf, hn, mastodon) evidence | Array String | default = [], # observed signals this audience exists & cares linked_nodes | Array String | default = [], # ontology node ids that this audience engages status | audience_status_type | default = 'Hypothesis, # Validated requires a Proof (rule 2) } in # Value proposition — one file per claim. let value_prop_type = { id | String, # e.g. "vp-001-structure-that-remembers" claim | String, # the nuclear one-liner audience_ids | Array String, # references audience_type.id (>=1 required, contract enforced) differentiator_ids | Array String | default = [], # QUÉ×QUIÉN seam: differentiators this claim binds (>=1 when Live) problem_solved | String, # the problem this claim addresses alternatives | Array String | default = [], # competition / status-quo options the user might consider unique_angle | String, # why no one else solves it this way evidence_adrs | Array String | default = [], # ADR ids that materially back this claim (required when status='Live) proof_points | Array String | default = [], # benchmarks, demos, public metrics narrative_path | String | optional, # path to markdown body (relative to project root) launch_criteria | Array launch_check_type | default = [], status | status_type, retired_at | String | optional, # ISO date (required when status='Retired) superseded_by | String | optional, # value-prop id that replaces this one } in # Campaign — a GTM moment bundling value-props for specific channels. let campaign_type = { id | String, # e.g. "launch-mcp-surface" title | String, audience_ids | Array String, # who this campaign targets value_prop_ids | Array String, # value-props in scope mechanism_ids | Array String | default = [], # difusión mechanisms this campaign coordinates goal | String, # what we're trying to achieve channels | Array String | default = [], # where this campaign runs launch_criteria | Array launch_check_type | default = [], narrative_path | String | optional, status | status_type, starts_at | String | optional, # ISO date ends_at | String | optional, # ISO date } in # ── QUÉ axis ──────────────────────────────────────────────────────────────── # Differentiator — one file per area of differential value. Admissible only # when anchored: it must name the competitor categories that do NOT do it # (`vs`) and trace to ontology nodes + Accepted ADRs. Cross-field invariants # live in `make_differentiator` (defaults). let differentiator_type = { id | String, # e.g. "diff-witness-seam" area | String, # short area label value_claim | String, # one-line difference in VALUE, not a feature vs | Array String, # competitor categories that do not do this competitor_ids | Array String | default = [], # optional refs to competitor_type.id linked_nodes | Array String | default = [], # ontology node ids (>=1, contract enforced) evidence_adrs | Array String | default = [], # ADR ids (>=1 Accepted, contract enforced) status | status_type, retired_at | String | optional, } in # Competitor — one file per competitor or category on the QUÉ axis. Supports # the differentiators by recording what the competitor does and how ontoref # differs. let competitor_type = { id | String, name | String, category | String, # "KG consultancy", "developer portal", ... what_they_do | String, differs_by | Array String | default = [], # how ontoref differs (prose or differentiator ids) url | String | optional, linked_nodes | Array String | default = [], } in # ── CÓMO axis ─────────────────────────────────────────────────────────────── # DifusionMechanism — one file per vehicle of difusión. Difusión is the # category; a Campaign is one time-boxed coordination within it. A mechanism is # persistent (Web, DocsSite, McpServer, Repo) or an event (Content, Talk, # Event). Admissible only with >=1 channel and >=1 thing it difunde — enforced # by make_difusion_mechanism. let mechanism_kind_type = [| 'Web, 'DocsSite, 'McpServer, 'Repo, 'Content, 'Talk, 'Event |] in let difusion_mechanism_type = { id | String, kind | mechanism_kind_type, name | String, description | String | default = "", persistent | Bool, # persistent surface vs one-off / event channels | Array String | default = [], # where it runs (>=1, contract enforced) value_prop_ids | Array String | default = [], differentiator_ids | Array String | default = [], launch_criteria | Array launch_check_type | default = [], status | status_type, retired_at | String | optional, } in # ── VALIDACIÓN — the seam ──────────────────────────────────────────────────── # Proof — the costura (seam) of the positioning layer. A Proof is the # convergence of the three axes (a differentiator, an audience, a difusión # mechanism) signed by a real outcome — "where everything converges really". # It references the other three; nothing references it. `converges` must carry # >=1 id on each axis (enforced by make_proof). let proof_converges_type = { differentiator_ids | Array String, audience_ids | Array String, mechanism_ids | Array String, } in let proof_kind_type = [| 'Dogfood, 'PersonalInstance, 'ReferenceAdopter, 'Benchmark |] in let proof_type = { id | String, title | String, kind | proof_kind_type, converges | proof_converges_type, outcome | String, # the real, measurable result evidence | Array String | default = [], # links / paths / metric strings evidence_adrs | Array String | default = [], launch_criteria | Array launch_check_type | default = [], status | status_type, retired_at | String | optional, } in # ── VIABILIDAD — the second seam ────────────────────────────────────────────── # ViabilityPath — the sustainability costura, parallel to Proof. Where Proof # answers "is the claim real?" (validation seam over the three axes), a # ViabilityPath answers "does the claim sustain its maker?" (sustainability seam # over the same axes). It is NOT a fourth orthogonal axis — compensation is not # a facet of a claim but a convergence outcome: an audience (PARA QUIÉN) reached # by a difusión mechanism (CÓMO) monetizing a value-prop (QUÉ×QUIÉN) returns a # sustaining flow to the maintainer. Self-similar to witness-as-axis-seam and to # Proof: it references the three axes via `converges`; nothing references it. # # `directness` maps to the two poles of the core tension openness-vs-sustainability: # 'Indirect — the Yin/open pole: visibility, funnel, reputation (the open and # personal surface widens the funnel whose paid tail sustains). # 'Direct — the Yang/governance pole: metered payment for the witness/audit # surface corporate audiences pay for. # # A ViabilityPath is 'Draft (a hypothesis about how positioning could sustain the # project) until a real, measured compensation outcome promotes it — exactly as an # Audience is 'Hypothesis until a Proof validates it. The gate is one-directional # (realized status ⇒ realized_outcome present), never a Hard biconditional on the # Spiral question (ondaod forbidden pattern). let directness_type = [| 'Direct, 'Indirect |] in let flow_kind_type = [| 'HostedService, # daemon / witness / audit surface run for a fee 'Support, # paid support, SLA, priority response 'Consulting, # integration / adoption / advisory services 'DualLicense, # commercial license of an otherwise-open surface 'Sponsorship, # grants, sponsors, patronage of the open protocol 'Product, # a consumer artefact (e.g. a native shell) adopted or paid for 'Funnel, # indirect: the open/personal surface feeds a paid tail 'Reputation, # indirect: talks/demos/content convert to opportunities |] in let viability_converges_type = { audience_ids | Array String, # PARA QUIÉN the flow draws from (>=1 enforced) mechanism_ids | Array String, # CÓMO they are reached — difusión (>=1 enforced) value_prop_ids | Array String, # QUÉ×QUIÉN claim monetized (>=1 enforced) } in let viability_path_type = { id | String, # e.g. "via-001-governance-audit-tail" title | String, directness | directness_type, # maps to openness-vs-sustainability poles flow_kind | flow_kind_type, converges | viability_converges_type, # the triple-axis convergence this flow rests on hypothesis | String, # the sustaining mechanism stated as a claim realized_outcome | String | optional, # the REAL measured compensation (required when status='Validated|'Live) linked_nodes | Array String | default = [], # ontology node ids (>=1 enforced — must engage the viability tension) evidence_adrs | Array String | default = [], # ADR ids backing the path (required when status='Live) launch_criteria | Array launch_check_type | default = [], status | status_type, # Draft until a real outcome → Validated/Live retired_at | String | optional, } in { PositioningStatus = status_type, AudienceStatus = audience_status_type, MechanismKind = mechanism_kind_type, ProofKind = proof_kind_type, Directness = directness_type, FlowKind = flow_kind_type, LaunchCheckTag = check_tag_type, LaunchCheck = launch_check_type, Audience = audience_type, ValueProp = value_prop_type, Campaign = campaign_type, Differentiator = differentiator_type, Competitor = competitor_type, DifusionMechanism = difusion_mechanism_type, Proof = proof_type, ViabilityPath = viability_path_type, }