Schema

This chapter describes the IpTool YAML types you author and the conventions that govern how they map to OTTO JSON.

Top-level typed instances

IpTool defines three top-level types. Every IpTool YAML file consists of one or more named instances of these types, tagged with !cfg.type:<TypeName>.

IpToolParameter

Definition of a single parameter (keyword). Authored as a top-level named instance whose name is the OTTO parameter name with / as the segment separator (INS/COLL/NAID, DET/WIN1/NX, …) — see Parameter names below for why / and not .. Declared once in a *.keys.ipt.yaml file; per-template overrides are expressed by !cfg.merge <PARAM_NAME>: blocks in *.tpl.ipt.yaml files.

IpToolTemplate

A Sequencer template. Authored as a top-level named instance whose name is the OTTO templateName. Lists the parameter names it uses (by name only — no ref/overrides wrapper) and optionally carries a phase1 block with reference_targets for Phase-1 acquisition templates.

IpToolIpMetadata

Instrument-wide metadata: format, constraints, checklist, laser. Authored once per instrument as ip_metadata: !cfg.type:IpToolIpMetadata in a *.meta.ipt.yaml file. The conventional instance name ip_metadata is what the compiler looks up.

The full type definitions live in ifw-iptool/lib/resource/schema/ifw/iptool/iptool.schema.yaml, expressed as CII Config Service !cfg.typedef declarations.

Conventions

snake_case vs. camelCase

IpTool YAML uses snake_case field names throughout, matching the convention used by VLT/ELT instrument control-system configuration (also CII Config Service-based). The OTTO JSON schemata use camelCase. The compiler translates field names at emit time; authors only ever see the snake_case form.

Examples of the translation:

  • initial_valueinitialValue

  • template_name (instance name) → templateName (OTTO field)

  • reference_targetsreferenceTargets

  • max_lengthmaxLength

One thing that does not get snake-cased:

  • Enum values (P1Setup, acquisition, PHO, …) are OTTO literals and pass through unchanged.

Parameter names (/ in YAML, . in OTTO)

OTTO parameter names are FITS-style dotted identifiers (DET.WIN1.NX, TEL.TARG.ALPHA, …). The CII Config Service, however, rejects ``.`` in instance names — and IpTool authors parameters as typed instances. To work around this, IpTool YAML uses / as the parameter-name segment separator:

  • In YAML: DET/WIN1/NX, TEL/TARG/ALPHA

  • In OTTO output: DET.WIN1.NX, TEL.TARG.ALPHA

The compiler converts / to . at emit time. The conversion applies to YAML instance names and references to instance names (!cfg.merge X:, template parameters: lists). It does not apply to string fields whose value happens to contain a parameter name (such as the laser block’s checked_parameters) — those are authored dotted, exactly as they will appear in OTTO output.

The lifecycle of one parameter

Before the detailed type reference, it is worth following a single parameter all the way from author’s YAML to OTTO JSON. The example is DET/READ/CLKIND (the CCD read-out mode keyword) taken from the bundled TSTINS example.

Author view (include/det.keys.ipt.yaml)

The IP author declares the parameter once, in the keys file matching its FITS prefix:

DET/READ/CLKIND: !cfg.type:IpToolParameter
  type: keyword
  label: CCD Read-out
  tooltip: 'CCD Read-out Setup: port, binning, gain'
  domain:
    keyword_values:
      - 100kHz,1x1,high
      - 100kHz,2x2,high
      - 200kHz,1x1,low
      - 200kHz,2x2,low
  initial_value: {string: '200kHz,2x2,low'}
  hide: [P1Setup, P1Observation, ETC, P2, SEQ]

Reading top to bottom:

  • DET/READ/CLKIND — the instance name. Authors use / because the CII Config Service rejects . in instance names; the compiler converts to . at emit time (see Parameter names above).

  • !cfg.type:IpToolParameter — tags the mapping with the typedef declared in iptool.schema.yaml. The CII Config Service type-checks the body against the typedef before the compiler ever sees it.

  • type: keyword — picks the OTTO per-type schema variant (KeywordParam.json). The remaining fields (domain shape, initial-value union) are constrained by that choice.

  • domain.keyword_values — the allowed values. Why keyword_values and not just values: IpToolDomain is a union typedef so the type-checker can reject mismatched combinations (e.g. num_values on a type: keyword); the compiler picks the right OTTO field at emit time.

  • initial_value: {string: '200kHz,2x2,low'} — the inner string: selects the union slot that matches type: keyword (OTTO keyword initial values are JSON strings; see Schema Per-type value constraints in OTTO).

  • hide: — which preparation-tool contexts suppress the parameter from the UI. P1Setup / P1Observation hide it during Phase 1 proposal preparation; P2 during Phase 2 OB preparation; ETC in the Exposure Time Calculator; SEQ from Sequencer-side listings.

Note

Out-of-range defaults are omitted. OTTO rejects an initialValue that falls outside the parameter’s domain. For numeric parameters this most often arises when no initial_value is authored: the CII Config Service auto-fills the type-default 0, which is illegal on a parameter whose range starts above 0 (e.g. an exposure time with min: 0.25). The author’s intent in that case is “no default — the operator must choose”, so the compiler emits no initialValue rather than an illegal one. A non-default value that is out of range is more likely an authoring mistake and is logged at WARNING (the field is still omitted so the IP stays valid).

What the compiler emits (templates/tstins_img_cal_bias.json)

After ifwIpToolCompiler, the same parameter appears inside its Template Signature as:

{
  "name": "DET.READ.CLKIND",
  "type": "keyword",
  "label": "CCD Read-out",
  "tooltip": "CCD Read-out Setup: port, binning, gain",
  "domain": {
    "values": [
      "100kHz,1x1,high",
      "100kHz,2x2,high",
      "200kHz,1x1,low",
      "200kHz,2x2,low"
    ]
  },
  "initialValue": "200kHz,2x2,low",
  "hide": ["P1Setup", "P1Observation", "ETC", "P2", "SEQ"]
}

Translations the compiler applied:

  • DET/READ/CLKINDDET.READ.CLKIND (/. on the instance name).

  • initial_valueinitialValue (snake_case → camelCase).

  • keyword_valuesvalues (the IpTool union field collapsed to OTTO’s plain values array).

  • {string: '200kHz,2x2,low'}"200kHz,2x2,low" (the inner IpTool union slot collapsed to the JSON scalar OTTO expects).

What OTTO requires of it

The emitted JSON is validated by ifwIpToolValidator against KeywordParam.json (a slice of ifw-iptool/lib/resource/schema/otto/ip_schemata/parameters/):

{
  "type":         { "const": "keyword" },
  "domain": {
    "required": ["values"],
    "values": {
      "type": "array",
      "uniqueItems": true,
      "minItems": 1,
      "items": { "$ref": "#/$defs/Keyword" }
    }
  },
  "initialValue": { "$ref": "#/$defs/Keyword" },
  "required":     ["domain"]
}

"Keyword": {
  "type": "string",
  "minLength": 1,
  "maxLength": 40,
  "pattern": "^[A-Za-z0-9_.:,/#+-]+$"
}

Two rules worth remembering because they cause most early-author errors:

  • ``initialValue`` must be one of the ``values``. OTTO does not enforce this with a strict enum, but every per-type IP schema pulls initialValue from the same Keyword definition the domain entries match. A value outside the declared set passes IpTool typedef checking and is caught only by the OTTO validator.

  • Both domain entries and ``initialValue`` are restricted by the ``Keyword`` pattern (40 chars, no whitespace, limited punctuation). The full set is [A-Za-z0-9_.:,/#+-].

Per-template override with !cfg.merge

A bias template wants the high-gain, fast-readout mode and a tighter hide-list. Rather than re-declaring the whole parameter, the template file overrides only what differs:

!cfg.include ipt/tstins/include/det.keys.ipt.yaml:
!cfg.include ipt/tstins/include/seq.keys.ipt.yaml:
!cfg.include ipt/tstins/include/dpr.keys.ipt.yaml:

!cfg.merge DET/READ/CLKIND:
  initial_value: {string: '100kHz,2x2,high'}
  hide: [P1Setup, P1Observation, ETC]

tstins_img_cal_bias: !cfg.type:IpToolTemplate
  type: calib
  instrument: TSTINS
  label: TSTINS imaging bias
  description: Bias frames for TSTINS imaging
  parameters: [..., DET/READ/CLKIND]

The merge modifies the existing DET/READ/CLKIND instance (declared in the included keys file) for this compilation unit only. Two properties make this work cleanly:

  • Each *.tpl.ipt.yaml file is its own CII Config Service compilation unit. Merges in tstins_img_cal_bias.tpl.ipt.yaml do not leak into tstins_img_obs_crsplit.tpl.ipt.yaml even though both files !cfg.include the same keys aggregator. The compiler emits each template using its file’s post-merge parameter view, so the OBD for the bias template sees 100kHz,2x2,high while the crsplit OBD sees the library default 200kHz,2x2,low.

  • !cfg.merge is field-level: only the fields named under it are replaced, others inherit from the library entry. There is no per-field “deep merge”; setting hide: [P1Setup] replaces the whole list, not just the first element.

Two CII Config Service rules constrain !cfg.merge:

  • The merged instance must already be defined (typically via an included *.keys.ipt.yaml). Merging into an undefined name fails at parse time with B0070_UNDEFINED_INSTANCE.

  • Only existing fields can be modified — new fields cannot be introduced — and the new value must satisfy the field’s declared type. A type mismatch fails at parse time with C0003_INVALID_VALUE.

Both errors point at the source file and line; chase those rather than the IpTool compiler output.

Failure modes mapped to where each is caught

The same parameter has several common ways to go wrong. The table below shows which layer catches each — useful when reading error messages.

Mistake

Caught by

Error code / message

Typo in field name (initiaal_value:)

CII Config Service (parse time)

C0011_MEMBER_NOT_DEFINED

Wrong inner type (initial_value: {integer: 5} on type: keyword)

CII Config Service (parse time)

C0003_INVALID_VALUE

initial_value not in domain.keyword_values

ifwIpToolValidator (post-compile)

'BOGUS' is not one of [...]

initial_value violates Keyword pattern (contains whitespace, > 40 chars)

ifwIpToolValidator (post-compile)

pattern '...' does not match

!cfg.merge against a name no !cfg.include declared

CII Config Service (parse time)

B0070_UNDEFINED_INSTANCE

!cfg.merge introduces a field the typedef doesn’t have

CII Config Service (parse time)

C0011_MEMBER_NOT_DEFINED

The split between “parse time” (CII) and “post-compile” (ifwIpToolValidator) is worth remembering: CII can verify shapes (you wrote a keyword, so the domain must be keyword_values) but cannot verify values (it does not know KeywordParam.json’s Keyword pattern, nor that initialValue must be a domain member). Those latter checks only fire when the JSON reaches the OTTO validator.

Typed initial_value and domain

Both initial_value and domain are union types. Set exactly one of their inner fields according to the declared parameter type (e.g. initial_value: {integer: 1} for type: integer; the compiler rejects mismatched combinations).

Per-type value constraints in OTTO

OTTO enforces extra constraints on certain parameter types beyond the IpTool YAML schema’s type check. These apply to the initial_value (and per-row value in OBDs). The two surprising ones to keep in mind:

  • ``ra`` and ``dec`` values are sexagesimal strings with at most 3 fractional-second digits:

    • ra pattern: HH:MM:SS or HH:MM:SS.f / HH:MM:SS.ff / HH:MM:SS.fff (24 h range, 8-12 chars total).

    • dec pattern: same shape with an optional + / - sign, 00 to 90 degrees inclusive (8-13 chars total).

    Values with more than 3 decimals (10:00:00.123456) fail validation with a pattern-mismatch on the parameter’s initialValue. This is an OTTO ip_schemata constraint, not an IpTool one. Authoring more than millisecond precision in an OB is not currently supported by the OTTO IP format.

  • ``keyword`` initialValue must be a 1-10 character string that appears in the values array. Strings outside that array, or longer than 10 characters, are rejected.

If a value fails one of these checks the validator now reports the real pattern / enum violation (B5b fix); pre-fix the error would surface as a misleading “type X is not one of [‘binfile’, ‘textfile’]” via the closest-miss branch heuristic.