fluid-telegram/presence/telegram.schema.yaml

131 lines
4.8 KiB
YAML
Raw Normal View History

# Declared presence — schema for a campaign's Telegram surface.
#
# The instance lives in the campaign repo (pr-hall-of-helix/presence/telegram.yaml).
# This file is the contract it is validated against, and lives here because the
# provisioner is here. See docs/provisioning.md.
$schema: "https://json-schema.org/draft/2020-12/schema"
# No $id: this schema is identified by its path in the repo. A relative $id is
# not a resolvable URI and breaks "$ref" resolution in ordinary validators.
title: "Telegram declared presence"
type: object
required: [presence]
additionalProperties: false
properties:
presence:
type: object
required: [schema_version, campaign, interface, bot, channels]
additionalProperties: false
properties:
schema_version: { const: "0.1" }
campaign:
type: string
description: >
Campaign slug. Names the resolved-state file and the OpenBao subtree,
so it must be stable for the life of the presence.
pattern: "^[a-z0-9][a-z0-9-]*$"
interface:
type: string
description: The FLUID interface that will publish through this presence.
const: "helix-forge-telegram-publishing"
bot:
type: object
required: [name, about, description]
additionalProperties: false
properties:
name:
type: string
maxLength: 64
description: Display name. What a reader encountering it cold sees first.
username_preference:
type: array
description: >
Candidate usernames in order. BotFather requires a globally unique
name ending in "bot" or "_bot", and the first choice is often taken.
Declaring fallbacks keeps apply from stopping on a collision; the
one actually issued is recorded in the resolved state.
items: { type: string, pattern: "^[A-Za-z][A-Za-z0-9_]{3,30}([Bb]ot|_bot)$" }
minItems: 1
about:
type: string
maxLength: 120
description: BotFather /setabouttext — shown on the profile before a chat starts.
description:
type: string
maxLength: 512
description: BotFather /setdescription — shown in the empty chat.
avatar:
type: string
description: >
Repo-relative path to a square image in the campaign repo.
Provisioning is content-addressed on its digest, so replacing the
file is what triggers an update.
channels:
type: object
required: [test, public]
additionalProperties: false
description: >
Exactly two. The test channel is created first and is where every
verification runs; nothing reaches the public channel until a person
has looked at a rendering in the test channel (Canon PUB-01).
properties:
test: { $ref: "#/$defs/channel" }
public: { $ref: "#/$defs/channel" }
linked_discussion_group:
type: boolean
default: false
description: >
Whether the public channel has a linked group for comments. Off by
default: comments are an inbound surface, and this interface is
outbound only. Turning it on is a scope change, not a setting.
$defs:
channel:
type: object
required: [title, visibility]
additionalProperties: false
# A private channel has no username to claim. Declaring one is a mistake
# about what is being created, so it fails here rather than being ignored
# by apply -- a silently dropped field is how a spec stops describing
# what actually exists.
allOf:
- if:
properties: { visibility: { const: private } }
required: [visibility]
then:
not: { required: [username_preference] }
- if:
properties: { visibility: { const: public } }
required: [visibility]
then:
required: [username_preference]
properties:
title:
type: string
maxLength: 128
description:
type: string
maxLength: 255
visibility:
enum: [private, public]
username_preference:
type: array
description: Public channels only. Ordered candidates; a private channel must not declare one.
items: { type: string, pattern: "^[A-Za-z][A-Za-z0-9_]{4,31}$" }
admin_rights:
type: array
description: >
Rights granted to the bot. The provisioner clamps this: post_messages
is the only permitted value, and any other entry fails validation
rather than being silently dropped. Declared explicitly so that a
reader of the spec can see the boundary rather than infer it.
items: { const: post_messages }
default: [post_messages]
maxItems: 1