exd schema
Emits the eval-context shape a flag (or the whole flag namespace) expects — attribute names, inferred types, requiredness, and source citations.
Synopsis
exd schema [<flag>] [--env <env>]
[--manifest <path-or-uri>]
[--format human|json|jsonschema|rust|typescript]
[--http-backend curl|in-process]
Description
The schema is inferred from lint::attr_types: every well-formed predicate atom and every [segment.bucket].entity_id_attribute contributes one attribute → type mapping. This is the same cross-file inference that backs the E034 lint diagnostic.
Without <flag>, emits the flag-namespace-wide union schema — every attribute every flag's transitive predicates touch.
Schema vs. fixtures.
exd schemaanswers "what type is each attribute?" — type-based.exd fixturesanswers "what values trigger each rule?" — example-based. Pair both for thorough SDK testing.
Use cases
-
Codegen a typed context struct. Emit a Rust struct or TypeScript interface for the consuming SDK:
exd schema onboarding-banner --env prod --format rust > src/contexts.rsexd schema onboarding-banner --env prod --format typescript > src/contexts.ts -
Wire up runtime validation. Emit a JSON Schema 2020-12 document and validate user requests against it before evaluation:
exd schema --format jsonschema > schemas/marketing-context.json -
Audit a flag namespace's attribute surface. Without
<flag>, see every attribute every flag depends on — useful before declaring a[namespace].private_attributesset:exd schema -
Discover the input shape for an agent. An agent constructing an evaluation context can call
exd schema <flag> --env <env> --format jsonto learn exactly which keys to populate.
Arguments and flags
| Argument / flag | Required | Notes |
|---|---|---|
<flag> (positional) | no | When present: schema for that flag's transitive attribute closure. When absent: flag-namespace-wide union. |
--env <env> | no | When present: filters to attributes reachable from rules that fire in that env. When absent with <flag>: union across every env block in the flag. When absent without <flag>: union across every (flag, env) pair. |
--manifest <path-or-uri> | no | Defaults to .. |
--format <fmt> | no, default human | One of human, json, jsonschema, rust, typescript. |
--http-backend curl|in-process | no | URI loads only. |
What "the schema" contains
For each attribute:
- Name — the dotted-path attribute name as it appears in predicates.
- Type — one of
boolean,integer,float,number,string,semver.numberis the loose type used when only relational operators have been applied; it accepts bothintegerandfloat. - Required —
trueiff at least one site referencing the attribute uses an operator other thanis_set/is_not_set. - Source citations — list of
{ kind, file, operator?, line? }items, one per site (segment / segment_bucket / flag_rule).
Output formats
--format human
$ exd schema onboarding-banner --env prod
schema for flag 'onboarding-banner' in env 'prod'
user.id string required sources:
- segments/onboarding-banner-bucket-treat-a.toml [segment.bucket].entity_id_attribute
user.country string required sources:
- flags/onboarding-banner.toml rule[0] inline predicate `in ["US","CA"]`
user.tier string optional sources:
- segments/tier-pro.toml predicate `eq "pro"`
Flag-namespace-wide: each flag gets its own subsection, with a # union across all flags row at the bottom.
--format json
{
"query": "schema",
"result": {
"scope": { "kind": "flag", "flag": "onboarding-banner", "env": "prod" },
"manifest_fingerprint": "marketing@a1b2c3",
"attributes": [
{
"name": "user.id",
"type": "string",
"required": true,
"sources": [
{ "kind": "segment_bucket", "file": "segments/onboarding-banner-bucket-treat-a.toml" }
]
}
]
}
}
Flag-namespace-wide: scope.kind = "namespace", flag field omitted; result.per_flag carries the per-flag breakdowns, top-level attributes carries the union.
--format jsonschema
Standard JSON Schema 2020-12, with vendor extensions for exd-specific provenance:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://exd.dev/schemas/marketing/onboarding-banner/prod",
"title": "EvalContext for marketing/onboarding-banner in prod",
"type": "object",
"properties": {
"user.id": { "type": "string" },
"user.country": { "type": "string" },
"user.tier": { "type": "string" }
},
"required": ["user.id", "user.country"],
"additionalProperties": true,
"x-exd-fingerprint": "marketing@a1b2c3",
"x-exd-sources": { /* per-attribute source citations */ }
}
additionalProperties: true is normative — passing extra context to the eval engine is legitimate.
Type mapping: boolean → "boolean"; integer → "integer"; float → "number" + "format": "double"; number → "number"; string → "string"; semver → "string" + "pattern": "^\\d+\\.\\d+\\.\\d+(?:[-+].+)?$".
--format rust
// Generated by `exd schema onboarding-banner --env prod --format rust`.
// Manifest fingerprint: marketing@a1b2c3 — rerun if predicates change.
pub struct OnboardingBannerContext {
pub user_id: String, // required: segments/onboarding-banner-bucket-treat-a.toml [segment.bucket].entity_id_attribute
pub user_country: String, // required: flags/onboarding-banner.toml rule[0] inline predicate
pub user_tier: Option<String>, // optional: segments/tier-pro.toml predicate
}
Field names: snake_case(<dotted-attr-name>) with . → _. Optional attributes become Option<T>. integer → i64, float / number → f64.
--format typescript
// Generated by `exd schema onboarding-banner --env prod --format typescript`.
// Manifest fingerprint: marketing@a1b2c3 — rerun if predicates change.
export interface OnboardingBannerContext {
"user.id": string; // required
"user.country": string; // required
"user.tier"?: string; // optional
}
Quoted property names preserve the dotted form — Record-typed eval contexts in the TS SDK accept dotted keys natively.
Exit codes
| Code | Condition |
|---|---|
0 | Success. |
1 | Unknown flag (positional); manifest lint errors; typed-mode unknown env. |
2 | Bad CLI args; manifest URI fetch failure. |
See also
exd explain— same data appears in the "Required context" section.exd fixtures— concrete (ctx, variant) examples.exd lint—E034is the diagnostic backed by the same inference table.