Purpose keys
A scenario says what kind of question is being asked. A purpose says why the data is being used. They are independent axes: purpose_key never becomes a scenario key, and a scenario never implies a purpose.
purpose_key is an optional input on exactly two tools — authorize_use and validate_query_context. It is decision-bearing: stating a purpose can change the answer, and so can omitting one. This page is the complete, honest account of when it does and when it does not.
The registryDirect link to The registry
Purposes come from a global, versioned, closed registry. It is system-owned: it is the same vocabulary in every workspace, it is not editable in the app, and it grows only when Metatate ships a new version. Registry version 2 defines eleven category tokens and sixteen keys.
Every key is family.name, and its first segment is its category. The two namespaces are structurally disjoint: every key contains a ., no category token does.
| Category | Keys | What it covers |
|---|---|---|
analytics | analytics.reporting | Business analytics, BI, dashboards, and internal reporting. |
operations | operations.support | Customer support and operational troubleshooting by support teams. |
fraud | fraud.detection | Detecting, investigating, and preventing fraud or abuse. |
compliance | compliance.reporting, compliance.audit | Regulatory reporting to authorities or auditors; audit trails and accountability reviews. |
marketing | marketing.advertising, marketing.personalization | Advertising, ad targeting, and campaign measurement; personalizing experiences, recommendations, and offers. |
ai | ai.training, ai.evaluation, ai.inference, ai.third_party_export | Training or fine-tuning models; evaluating and benchmarking them; using data as prompt, retrieval, or model context at inference; sending data to a third-party AI provider or external model. |
sharing | sharing.external, sharing.third_party | Sharing or exporting data outside the organization; third-party processing under contract terms. |
security | security.incident_response | Security or privacy incident investigation and response. |
research | research.general | Research, exploration, and non-commercial analytical work. |
commercial | commercial.general | Revenue-generating, product, sales, or other commercial work. |
custom | (none) | Reserved. Version 2 defines no keys in this family. |
Answers report the purpose they evaluated as evaluated_purpose: {purpose_key, purpose_category, registry_version}, or null when the call stated no purpose. You supply only the key — the category and the version are derived server-side and re-proved in the database before anything is recorded. Supplying purpose_category or registry_version yourself is rejected as an unknown parameter; there is no path by which a caller can assert either.
Exact match on input, no normalizationDirect link to Exact match on input, no normalization
purpose_key is compared byte-exactly against the registry, exactly like scenario_key. All of these are rejected with invalid_parameters:
AI.TRAINING wrong case
"ai.training " trailing space
ai_training underscore instead of dot
ai.finetuning not a registry key
A rejected call never runs and nothing is recorded. Note that an unknown structured purpose_key is a parameter error, not a review outcome — review is what happens to unmapped authored text in your policies, which is a different rule described below. Do not conflate them.
One honesty note on the rejection itself: Metatate's own refusal carries a fixed message and does not echo the value you sent. However, MCP clients validate arguments against the registered tool schema before the request reaches that layer, and the protocol-level error from that stage does render the offending value. This is pre-existing behaviour for every schema-validated field — an invalid scenario_key behaves identically — and is not specific to purpose_key.
Where purpose is decision-bearingDirect link to Where purpose is decision-bearing
Purpose participates in two deliberately different ways:
- it substitutes
usage_guidance, the rows derived fromspec.usage.permittedUsesandspec.usage.prohibitedUses; and - it selects a purpose-bound rule inside
access_window.
These mechanisms never substitute for one another. A purpose-derived usage allow does not satisfy a data window, and selecting a window does not create a usage permission.
Usage guidanceDirect link to Usage guidance
If neither a usage_guidance nor an access_window row participates, purpose adds no restriction and changes nothing. The answer is identical with or without purpose_key. This is the common case for masking questions, row-access questions, retention questions, and transfer questions that do not cross either purpose-bearing family.
When usage_guidance rows do participate, matching is normalized exact key or category equality only. There is no fuzzy matching, no prefix matching, no substring matching, and no semantic similarity anywhere in the evaluator.
- A category token in an authored list (
ai) matches every key in that family, and only those. - A key in an authored list (
ai.training) matches only itself. It never widens to its family.
The outcomes, in order of precedence:
| Situation | Result on the usage_guidance rows |
|---|---|
| A prohibited-uses entry matches the stated purpose | deny. Every permitted-uses row on the asset is forced to require_review, at any specificity — a matching prohibition can never be outranked by a permitted rule. |
| No prohibition matches, and a permitted-uses entry matches | allow. |
| No prohibition matches, all entries resolve to registry tokens, none matches | require_review. |
| An entry cannot be resolved to a registry token (a legacy free string) | It can never prove coverage — a permitted row becomes require_review. It can never neutralize a prohibition either — a deny stands. |
| A prohibition whose entries all resolve, none matching the stated purpose | Decision-neutral (log_only) for this call. The prohibition simply does not name this purpose. |
No purpose_key supplied at all, with permitted-uses rows present | require_review. A purpose-blind call never rides a permitted-uses allow. |
That last row is the one most likely to surprise you. Omitting purpose_key is not neutral where permitted-uses rules exist — it fails closed. If your agent used to get allow on a purpose.allowed_use question and now gets review_required, add the purpose.
Purpose-bound access windowsDirect link to Purpose-bound access windows
An access_window policy names an exact purposeKey per rule, an exact set of
token-bound actorRoles, a timeColumn, a window type, and a lookback. The
purpose selects the rule; it does not widen or override it.
rollinguses the server'sevaluated_atas its anchor;as_ofrequiresdata_access_context.as_of;- missing or unmatched purpose/context/role fails closed as
review_required / access_window_context_required.
The answer's condition carries the exact time column, type, duration, anchor,
and lower bound. validate_query_context must then prove that SQL stays inside
that bound. See Policy YAML and the
access-window cookbook.
What purpose can never doDirect link to What purpose can never do
Purpose never overrides a restriction from another instruction family. A purpose-derived allow on a usage_guidance row does not erase a deny, mask, role restriction, access window, retention obligation, transfer rule, or AI restriction. Purpose may select an access_window rule, but the selected window remains a conditional restriction that must be proved. can_proceed_now stays false while any restrictive row survives.
Purpose is not authority. It is a question parameter, like scenario_key and use — never an identity claim and never an entitlement. It cannot grant a role (roles come only from the verified token's bound_role) and it cannot satisfy a condition (only a server-verified exception does that).
Authoring: what goes in permittedUses and prohibitedUsesDirect link to authoring-what-goes-in-permitteduses-and-prohibiteduses
The recommended form is registry vocabulary:
spec:
usage:
permittedUses: [analytics.reporting, compliance.audit]
prohibitedUses: [marketing, ai.third_party_export]
Here marketing is a category token — it prohibits marketing.advertising and marketing.personalization — while ai.third_party_export prohibits exactly that one purpose.
Legacy free strings already in your policies are not broken, but they are handled conservatively. An authored entry resolves in this fixed order:
- Exact registry key — the trimmed entry is byte-equal to a key (
ai.training). - Exact category token — the trimmed entry is byte-equal to a family token (
ai). This step is byte-exact only;AIandA Iare not category references. - Normalized match onto a key — the entry is normalized and compared to a fixed table. Normalization lowercases the 26 ASCII uppercase letters, replaces every run of characters outside
a–z0–9with a single_, and strips leading and trailing_. SoAI Training,ai-training, andai_trainingall reachai.training. Normalization maps onto keys only, never categories. - Unmapped — anything else. An unmapped entry can never prove coverage and can never neutralize a prohibition.
Version 2's normalization table has twenty rows: one mechanical row per registry key (the normalized form of the key itself), plus four curated rows — advertising → marketing.advertising, personalization → marketing.personalization, model_training → ai.training, and third_party_ai_export → ai.third_party_export.
If a legacy entry is unmapped, you have two honest options: rewrite it as registry vocabulary if the author's intent is unambiguous, or leave it and accept require_review where it is not. The Customer 360 sample deliberately keeps one of each so both behaviours are visible.
The second place purpose participates: exception scopeDirect link to The second place purpose participates: exception scope
A steward resolving a request may grant an exception scoped to a purpose category. When an agent later retries authorize_use with satisfied_conditions, the server matches the exception's purpose scope against the evaluated category.
That matching is exact and disjoint, not monotonic:
- A purpose-scoped exception verifies only for a call stating a purpose in that category.
NULLpurpose scope means purposeless, and matches only a purposeless call. It is not "any purpose", and no wildcard scope exists.
This is a separate axis from the policy evaluation above. Either one alone can change an answer, and neither implies the other.
What is recordedDirect link to What is recorded
Purpose is recorded as decision evidence on every call that states one. explain_why surfaces the recorded triple later — inside evaluated for an authorization, as a top-level purpose_context for a validation — and null for purposeless calls and for records written before purpose shipped.
Because purpose is recorded, the request lane's open-request identity includes it: two requests for the same asset and scenario under different purposes stay distinct requests.
Your free-text use field is unchanged and is not affected by any of this. It stays redacted by default and never enters the recorded purpose context. Purpose adds a closed vocabulary beside free text; it is not a new path for raw text.
RelatedDirect link to Related
authorize_useandvalidate_query_context— the two tools that acceptpurpose_key- Scenarios — the other axis, and why they are not the same thing
- Typed answer states — how
require_reviewis reported - Policy YAML — the
spec.usageblock