ontoref-code/ontology/schemas/positioning.ncl

237 lines
13 KiB
Text
Raw Permalink Normal View History

# 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,
}