ontoref-code/ontology/schemas/positioning.ncl
2026-07-10 01:44:59 +01:00

236 lines
13 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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