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()
field changed datasource=mongo env=production field=app_port from=8080 to=9090 file=configs/app.yaml 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.