# SPDX-License-Identifier: LicenseRef-NativeMindNONC
# Native OSC address space of the media server (02e §8.3; ports per master §9b / RC1). Structure:
# tools/schemas/osc.schema.json; checked by tools/osc/check.mjs (`make osc`): every mapsTo names an OpenAPI
# operationId and the address scope equals that operation's x-scope.
version: 1
kind: native
prefix: /freedome/v1
transports:
  udp: { in: 7000, out: 7001, protocol: OSC 1.0, note: replies go to the sender address or to the subscriber's replyPort }
  tcp: { port: 7000, framing: SLIP (RFC 1055), protocol: OSC 1.1 }
  mdns: _osc._udp (TXT prefix=/freedome/v1)
aliases:
  venue: '`_` = the default venue (alias default)'
  player: '`_` = the default player (alias main)'
security:
  auth: none in-band (OSC has no credentials) — per-listener CIDR allow-list, each listener mapped to a fixed role
  defaultRole: operator
  adminCidrOnly: addresses flagged adminCidrOnly are accepted only from the listener's admin CIDR list
  rejected: packets from non-allow-listed sources or with malformed type tags are dropped (counter + rate-limited log);
            /freedome/v1/error is sent only to allow-listed senders
bundles:
  timeTags: honoured — scheduled execution on the server clock (multi-node sync)
  past: executed immediately
  farFuture: more than 24 h ahead → rejected with /freedome/v1/error ,s validation-failed
  immediate: time tag 1 (immediately) executes in arrival order
replies:
  ok: commands without a reply address answer nothing on success
  error: '/freedome/v1/error ,s code ,s detail ,s address (problem code of 02e §3.3)'
addresses:
  - address: /freedome/v1/ping
    direction: in
    summary: liveness probe
    args: [{ name: token, type: s, optional: true, description: ignored (reserved) }]
    mapsTo: { internal: ping }
    scope: anon
    reply: { address: /freedome/v1/pong, args: [{ name: serverVersion, type: s }] }
  - address: /freedome/v1/info
    direction: in
    summary: server info as JSON
    args: []
    mapsTo: { operationId: getInfo }
    scope: anon
    reply: { address: /freedome/v1/info, args: [{ name: json, type: s }] }
  - address: /freedome/v1/subscribe
    direction: in
    summary: subscribe the sender to a topic filter (+ / #); events arrive as /freedome/v1/event
    args: [{ name: topic, type: s }, { name: replyPort, type: i, optional: true }]
    mapsTo: { internal: subscribe }
    scope: events:subscribe
  - address: /freedome/v1/unsubscribe
    direction: in
    args: [{ name: topic, type: s }, { name: replyPort, type: i, optional: true }]
    mapsTo: { internal: unsubscribe }
    scope: events:subscribe
  - address: /freedome/v1/venue/{v}/player/{p}/play
    direction: in
    summary: play; the optional id is resolved as playlist, then asset, then show id
    args: [{ name: id, type: s, optional: true }, { name: itemIndex, type: i, optional: true }, { name: position, type: f, optional: true, unit: s }]
    mapsTo: { operationId: play, params: { venueId: '{v}', playerId: '{p}' }, body: { playlistId: $id, itemIndex: $itemIndex, position: $position } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/player/{p}/pause
    direction: in
    args: []
    mapsTo: { operationId: pause, params: { venueId: '{v}', playerId: '{p}' } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/player/{p}/resume
    direction: in
    args: []
    mapsTo: { operationId: resume, params: { venueId: '{v}', playerId: '{p}' } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/player/{p}/stop
    direction: in
    args: []
    mapsTo: { operationId: stop, params: { venueId: '{v}', playerId: '{p}' } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/player/{p}/next
    direction: in
    args: []
    mapsTo: { operationId: next, params: { venueId: '{v}', playerId: '{p}' } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/player/{p}/previous
    direction: in
    args: []
    mapsTo: { operationId: previous, params: { venueId: '{v}', playerId: '{p}' } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/player/{p}/seek
    direction: in
    args: [{ name: seconds, type: f, unit: s }]
    mapsTo: { operationId: seek, params: { venueId: '{v}', playerId: '{p}' }, body: { position: $seconds } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/player/{p}/rate
    direction: in
    args: [{ name: rate, type: f, range: [0.0625, 16] }]
    mapsTo: { operationId: patchPlayer, params: { venueId: '{v}', playerId: '{p}' }, body: { rate: $rate } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/player/{p}/loop
    direction: in
    args: [{ name: loop, type: 'i', range: [0, 1] }]
    mapsTo: { operationId: patchPlayer, params: { venueId: '{v}', playerId: '{p}' }, body: { loop: $loop } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/player/{p}/volume
    direction: in
    args: [{ name: volume, type: f, range: [0, 1] }]
    mapsTo: { operationId: patchPlayer, params: { venueId: '{v}', playerId: '{p}' }, body: { volume: $volume } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/output/brightness
    direction: in
    args: [{ name: value, type: f, range: [0, 1] }]
    mapsTo: { operationId: patchOutput, params: { venueId: '{v}' }, body: { brightness: $value } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/output/volume
    direction: in
    args: [{ name: value, type: f, range: [0, 1] }]
    mapsTo: { operationId: patchOutput, params: { venueId: '{v}' }, body: { volume: $value } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/output/blackout
    direction: in
    args: [{ name: on, type: 'T|F', description: also accepts i 0|1 }]
    mapsTo: { operationId: patchOutput, params: { venueId: '{v}' }, body: { blackout: $on } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/output/lighting
    direction: in
    args: [{ name: on, type: 'T|F', description: also accepts i 0|1 }]
    mapsTo: { operationId: patchOutput, params: { venueId: '{v}' }, body: { lighting: $on } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/output/rotation
    direction: in
    args: [{ name: degrees, type: f, range: [-360, 360] }]
    mapsTo: { operationId: patchOutput, params: { venueId: '{v}' }, body: { rotation: $degrees } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/cue/{c}/fire
    direction: in
    args: []
    mapsTo: { operationId: fireCue, params: { venueId: '{v}', cueId: '{c}' } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/param/{path}
    direction: in
    summary: '`{path}` = the dotted parameter path as one or more address levels (master.brightness or master/brightness)'
    args: [{ name: value, type: 'f|i|s|T|F' }]
    mapsTo: { operationId: setParameter, params: { venueId: '{v}', path: '{path}' }, body: { value: $value } }
    scope: playback:control
  - address: /freedome/v1/venue/{v}/display/{d}/pattern
    direction: in
    args: [{ name: kind, type: s }]
    mapsTo: { operationId: setTestPattern, params: { venueId: '{v}', displayId: '{d}' }, body: { kind: $kind } }
    scope: displays:control
  - address: /freedome/v1/venue/{v}/audio/master/gain
    direction: in
    args: [{ name: gainDb, type: f, unit: dB, range: [-144, 24] }]
    mapsTo: { operationId: patchAudio, params: { venueId: '{v}' }, body: { masterGainDb: $gainDb } }
    scope: audio:control
  - address: /freedome/v1/venue/{v}/audio/zone/{z}/gain
    direction: in
    args: [{ name: gainDb, type: f, unit: dB, range: [-144, 24] }]
    mapsTo: { operationId: patchAudioZone, params: { venueId: '{v}', zoneId: '{z}' }, body: { gainDb: $gainDb } }
    scope: audio:control
  - address: /freedome/v1/venue/{v}/audio/source/{id}/position
    direction: in
    args: [{ name: azimuth, type: f, unit: deg }, { name: elevation, type: f, unit: deg }, { name: distance, type: f, unit: m, optional: true }]
    mapsTo: { operationId: setSourcePosition, params: { venueId: '{v}', sourceId: '{id}' }, body: { azimuth: $azimuth, elevation: $elevation, distance: $distance } }
    scope: audio:control
  - address: /freedome/v1/engine/visual/backend
    direction: in
    args: [{ name: backend, type: s }]
    mapsTo: { operationId: setComputeBackend, body: { backend: $backend } }
    scope: system:admin
    adminCidrOnly: true
  - address: /freedome/v1/event
    direction: out
    summary: any event of a subscribed topic (envelope JSON, 02e §7.1)
    args: [{ name: topic, type: s }, { name: json, type: s }]
    mapsTo: { internal: event }
  - address: /freedome/v1/venue/{v}/player/{p}/position
    direction: out
    summary: player.position fast path (rate = subscription positionHz, default 4 Hz)
    args: [{ name: position, type: f, unit: s }, { name: duration, type: f, unit: s }]
    mapsTo: { internal: event }
  - address: /freedome/v1/error
    direction: out
    args: [{ name: code, type: s }, { name: detail, type: s }, { name: address, type: s }]
    mapsTo: { internal: error }
