Field declarations
A schema contract is normally inferred from data you have already sent. Field declarations let you state your fields up front instead: each field's type, its privacy class, the role you propose for it, how often you expect it to be missing and a description. Tessera puts the declared fields into a contract draft, so you can review and approve them before the first event arrives. Approval itself does not change: nothing reaches a model until a project owner or admin approves the contract.
Declarations belong to the project, like its contract: the live and test environments read the same document.
The declarations document
PUT /v1/field-declarations replaces the project's declarations with one
document. Each declaration names an (event_type, attr_key) pair once:
{
"declarations": [
{
"event_type": "ticket.resolved",
"attr_key": "resolution_minutes",
"declared_type": "number",
"pii": "none",
"role": "feature",
"missingness": "rare",
"description": "Minutes from opening to resolution.",
"annotations": { "unit": "minute" }
},
{
"event_type": "ticket.resolved",
"attr_key": "requester_email",
"declared_type": "categorical",
"pii": "direct",
"role": "excluded"
}
]
}
| Member | Required | Values |
|---|---|---|
declared_type | yes | number, integer, categorical, boolean, timestamp, text, array |
pii | yes | none, direct, quasi |
role | no | feature, target_only, identifier, metadata, excluded (a proposal: approval decides) |
missingness | no | expected, rare, never |
description | no | 1 to 4,096 characters |
annotations | no | up to 32 keys (lower_snake_case) with string values of up to 1,024 characters |
These are the same value sets the contract uses for its attributes. A member you omit is inferred, as it would be without a declaration.
Annotations are yours. Tessera stores them and returns them exactly as you sent them, and never acts on them. Use them for anything else you track about a field, such as a unit, a lifecycle stage or a retention rule.
A document holds at most 4,096 declarations and 4 MiB of canonical JSON.
What a put does
-
If the document is identical to the current version, Tessera answers that version and writes nothing, so retrying a put is safe. Pass
expected_version(the version you last read, or0for none) to refuse a put when someone else changed the declarations first. -
Tessera stores the next version. Versions are immutable; read the current one with
GET /v1/field-declarationsand any older one withGET /v1/field-declarations/{version}. -
Every declared field enters the project's profiling input, so contract drafts include it before any event does.
-
Tessera creates one contract draft from the declaration and returns its version as
contract_version:- a field the approved contract does not hold yet takes your declared type (with its default encoding), role, privacy class, missingness and description instead of the inferred ones;
- a field the approved contract already holds takes your role, privacy class, missingness and description, exactly as an edit of the draft would. Its type never changes once approved.
When the draft would equal the approved contract, no draft is created and
contract_versionisnull. -
Review the draft with
GET /v1/contracts/{version}and its differences withGET /v1/contracts/{version}/diff, then approve it withPOST /v1/contracts/{version}:approve. The put never approves.
Later drafts (POST /v1/contracts, or a domain pack install) keep the decisions
of the approved contract and use your declarations, instead of inference, only
for fields the approved contract does not hold yet.
Removing a declaration
Removing a field from your declarations does not remove it from the project.
A declared field stays in the project's profiling input, so a later draft still
lists it, now with inferred values. If it should not reach a model, keep it out
explicitly: declare it with the role excluded or metadata, or set that role
in the draft (PATCH /v1/contracts/{version}) before you approve it. A field the
approved contract already holds keeps its approved values either way.
Refusals
A put that would contradict the approved contract or the draft rules is
refused as a whole with failed_precondition, and nothing is stored. Each
refused declaration is listed in error.details with its reason:
reason | Meaning |
|---|---|
declared_type_conflict | The approved contract holds the field with another type; an approved type never changes. |
pii_direct_feature | A direct privacy class cannot be combined with the feature role. |
identifier_as_feature | The field is an identifier (by its name or its values) and cannot be a feature. |
A malformed document (an unknown member, a duplicate field, a value outside
the bounds above) is refused with invalid_argument.
Access
Reading declarations needs the same access as reading contracts: the
viewer role, or an API key with the read scope. Replacing them needs the
admin role (or owner), or an API key with the admin scope.