Skip to content

CUE Schema Annotations

varTrack supports two custom annotations in CUE schema files: @secret() and @logger(). Both are declared on fields in your schema, not in your config files.


@secret()

Marks a field whose value is a Vault path at ETL time instead of a real value. Before writing to any sink, the orchestrator reads the schema, finds every @secret-annotated field, fetches the real value from Vault, and injects it in-place.

Syntax

```cue notest // default secret manager "db.password": string @secret()

// named secret manager (matches secret_managers[].vault tag in the bundle) "api_key": string @secret(ref="other-vault")

### Config file side

In the actual config file being synced (YAML, JSON, TOML, etc.) the field value is the Vault path:

```yaml
# configs/app.yaml
database:
  password: "@secret(secret/myapp/db#password)"

auth:
  jwt_secret: "@secret(secret/myapp/auth#jwt_secret)"

The format is @secret(mount/path#field) — mount path + # + field name inside the Vault secret.

What happens at ETL time

1. Parse schema → find @secret-annotated fields
2. For each field present in flat_data:
   a. Determine secret manager: ref= or "default"
   b. Fetch value from Vault: the path and field from "@secret(mount/path#field)"
      (the manager's mount point may lead the path)
   c. Replace flat_data[field] with the real value
3. Write to sink — Vault value arrives, not the path string

Dry-run behaviour

In dry-run mode, @secret fields are masked as *** in the report. The real value is fetched from Vault but never returned to the caller.

Bundle wiring

The ref= value must match the tag of a secret manager declared in the bundle:

bundle: {
  secret_managers: [
    {
      vault: {
        tag:         "default"   // matched by @secret()
        endpoint:    "https://vault.mycompany.com"
        mount_point: "secret"
        kv_version:  2
        token_auth: { token: "hvs.xxxx" }
      }
    },
    {
      vault: {
        tag:         "other-vault"   // matched by @secret(ref="other-vault")
        endpoint:    "https://vault2.mycompany.com"
        mount_point: "secret"
        kv_version:  2
        token_auth: { token: "hvs.yyyy" }
      }
    },
  ]
}

@secret() without ref= uses the rule's secret_manager when it names one, else the entry tagged default, else the bundle's only one. Secret managers are declared in config.cue's secret_managers only; a rule's own secrets block was removed. A field whose value is not in the @secret(path#field) form is read at the manager's path_prefix, under the field's own name.


@logger()

Marks a field for structured change logging. Every time a value changes between ETL runs, the orchestrator emits an INFO log entry showing the field name, old value, new value, datasource, and environment.

Syntax

```cue notest app_port: int @logger() database.host: string @logger()

### Log output

When a non-secret field changes:
field changed datasource=mongo env=production field=app_port from=8080 to=9090 file=configs/app.yaml
When a field is written for the first time (no old value):
field changed datasource=mongo env=production field=database.host to="db.prod.internal" file=configs/app.yaml
### Combining with `@secret()`

You can annotate a field with both. The change is logged but the values are **never revealed** — only `changed=true` is recorded:

```cue notest
"database.password": string @secret() @logger()

field changed  datasource=mongo  env=production  field=database.password
               changed=true  file=configs/app.yaml

Full schema example

```cue notest // app.yaml.cue at the root of the schema repo — checks every file named app.yaml "app.port": string & =~"^[0-9]+$" @logger() "log.level": "debug" | "info" | "warn" | "error" "database.host": string & !="" @logger() "database.port": string & =~"^[0-9]+$" @logger() "database.password": string & !="" @secret() @logger() "api.secret_key": string & !="" @secret() "max_connections"?: string & =~"^[0-9]+$"

The fields are at the top level, not inside a `#Definition` — `cue vet` checks the data only against top-level fields. Values are strings, as the file is flattened first, so a number is a string matching a pattern; see [CUE schemas](schema-registry.md#cue-schemas).

Register the schema in your bundle:

```cue
bundle: {
  schema_registry: {
    platform: "github"
    repo:     "my-org/schemas"
    branch:   "main"
  }
  // ...
}

varTrack fetches the schema from Git, runs cue vet to validate the payload, then processes @secret and @logger annotations before writing to any sink.