AID-H-029.001: Data-Use Policy Schema, Tagging & Classification

Version 1.20261015 · Actionable sub-technique

Define and attach machine-readable data-use authorization metadata to every data asset entering the AI ecosystem. Each asset receives policy tags that specify which AI lifecycle stages it is authorized for, applicable consent scope and expiry, purpose limitations, jurisdictional restrictions, classification tier, and ownership metadata. These tags are the enforcement primitive consumed by downstream stage gates. The schema must be versioned and stored alongside the asset in the lineage or metadata system.

Pillars: data

Lifecycle: scoping, building

Scope boundary

Owns the versioned machine-readable schema and tags that express allowed AI stages, purposes, consent scope and expiry, jurisdiction, classification, and ownership for each data asset. It defines policy facts but does not make the stage-transition decision.

AID-H-029.002: AID-H-029.001 defines and attaches the data-use facts; AID-H-029.002 evaluates those facts at each lifecycle boundary and issues the allow or deny result. Tags without an enforcement gate are descriptive metadata only.

AID-M-002: AID-H-029.001 expresses authorized use and consent constraints; AID-M-002 records provenance, lineage, integrity, and risk characterization. The two metadata sets may be stored together but answer different questions.

Implementation guidance

AID-H-029.001-G001

Define a versioned machine-readable schema for data-use tags and reject untagged or malformed assets at ingestion time.
Concept:

Every dataset, document collection, embedding corpus, or interaction log that may reach an AI stage carries the same closed tag set. AID-H-029.002 reads these exact fields at every lifecycle-stage gate, so the stage vocabulary, per-stage purpose grants, asset and tag versions, withdrawal state and expiry defined here are the contract that gate enforces. A tag object that passes this schema but omits a field the gate reads would be denied at every stage.

Define the closed tag schema

Grant purposes per stage rather than as one flat list, so a purpose approved for evaluation cannot authorize RAG indexing. asset_version binds the tags to the exact asset bytes, and tag_version changes on every tag edit so a gate decision can be tied to the tag state it evaluated. consent_expiry is always present: write null only when the authoritative grant has no time limit. An empty authorized_stages list is valid and means the asset is denied at every stage.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version", "asset_id", "asset_version", "tag_version",
    "authorized_stages", "authorized_purposes", "classification_tier",
    "consent_type", "consent_expiry", "authorization_withdrawn",
    "jurisdiction", "data_owner"
  ],
  "$defs": {
    "stage": {
      "enum": [
        "training", "fine_tuning", "evaluation", "rag_indexing",
        "inference_context", "logging", "memory_write", "retraining"
      ]
    },
    "text": {"type": "string", "minLength": 1}
  },
  "properties": {
    "schema_version": {"const": "2.0"},
    "asset_id": {"$ref": "#/$defs/text"},
    "asset_version": {"$ref": "#/$defs/text"},
    "tag_version": {"$ref": "#/$defs/text"},
    "authorized_stages": {
      "type": "array",
      "items": {"$ref": "#/$defs/stage"},
      "uniqueItems": true
    },
    "authorized_purposes": {
      "type": "object",
      "propertyNames": {"$ref": "#/$defs/stage"},
      "additionalProperties": {
        "type": "array",
        "items": {"$ref": "#/$defs/text"},
        "minItems": 1,
        "uniqueItems": true
      }
    },
    "classification_tier": {"enum": ["public", "internal", "confidential", "restricted"]},
    "consent_type": {"type": ["string", "null"]},
    "consent_expiry": {"type": ["string", "null"]},
    "authorization_withdrawn": {"type": "boolean"},
    "jurisdiction": {"$ref": "#/$defs/text"},
    "data_owner": {"$ref": "#/$defs/text"}
  }
}
Validate the schema and the cross-field rules at ingestion

JSON Schema cannot require the stage list and the purpose-grant keys to match, and the jsonschema date-time format check is not enforced by default. Check both explicitly. The timestamp profile uses uppercase T/Z, an explicit Z or numeric offset (hours 00-23, minutes 00-59), and at most nine fractional digits. Reject invalid calendar times, leap seconds and instants outside the signed 64-bit nanosecond range supported by OPA time.parse_rfc3339_ns; never repair an invalid offset or round an expiry to make it pass.

# File: data_governance/validate_tags.py
from __future__ import annotations

from datetime import datetime, timezone
import json
from pathlib import Path
import re

from jsonschema import Draft202012Validator

TAG_SCHEMA = json.loads(Path("data_use_tag_schema.json").read_text(encoding="utf-8"))
Draft202012Validator.check_schema(TAG_SCHEMA)
VALIDATOR = Draft202012Validator(TAG_SCHEMA)
RFC3339 = re.compile(
    r"(?P<whole>[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2})"
    r"(?:\.(?P<fraction>[0-9]{1,9}))?"
    r"(?P<offset>Z|[+-](?:[01][0-9]|2[0-3]):[0-5][0-9])"
)
UNIX_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
NS_PER_SECOND = 1_000_000_000
MIN_OPA_NS = -(1 << 63)
MAX_OPA_NS = (1 << 63) - 1


class TagRejected(ValueError):
    pass


def parse_rfc3339_ns(value: str) -> int:
    """Parse the tag timestamp profile without offset normalization or rounding."""
    match = RFC3339.fullmatch(value) if isinstance(value, str) else None
    if match is None:
        raise TagRejected("consent_expiry requires RFC 3339 with a valid offset and at most 9 fractional digits")
    try:
        # Validate calendar/time fields separately; retain the fraction as an integer.
        whole = datetime.fromisoformat(match["whole"] + match["offset"].replace("Z", "+00:00"))
    except ValueError as exc:
        raise TagRejected("consent_expiry is not a real calendar time") from exc
    delta = whole - UNIX_EPOCH
    seconds = delta.days * 86400 + delta.seconds
    fraction_ns = int((match["fraction"] or "").ljust(9, "0"))
    timestamp_ns = seconds * NS_PER_SECOND + fraction_ns
    # OPA time.parse_rfc3339_ns requires a signed 64-bit nanosecond instant.
    if not MIN_OPA_NS <= timestamp_ns <= MAX_OPA_NS:
        raise TagRejected("consent_expiry is outside the OPA nanosecond range")
    return timestamp_ns


def validate_tags(tag_doc: dict) -> dict:
    errors = sorted(VALIDATOR.iter_errors(tag_doc), key=lambda e: list(e.absolute_path))
    if errors:
        raise TagRejected(f"invalid data-use tags: {errors[0].message}")
    # Each authorized stage carries its own purpose grant, with no extra grant keys.
    if set(tag_doc["authorized_stages"]) != set(tag_doc["authorized_purposes"]):
        raise TagRejected("authorized stages and per-stage purpose grants differ")
    if tag_doc["consent_expiry"] is not None:
        parse_rfc3339_ns(tag_doc["consent_expiry"])
    return tag_doc

Operational notes: Start fail-closed for new pipelines. For legacy data, allow a temporary quarantine state and route assets without tags into a remediation queue instead of silently admitting them.

Production implementation

At ingestion admission, validate the asset and data-use tag object against the pinned closed schema and signed vocabulary, require authoritative source and policy bindings, reject unknown/missing/expired tags and persist the canonical tag digest with the asset version.

Independent verification

A separately credentialed verifier independently reads back or replays source bytes, schema/vocabulary, canonical tag object and catalog readback with untagged, unknown enum, extra field, stale policy, duplicate, malformed, stage-without-purpose-grant, purpose-grant-for-unauthorized-stage, missing expiry and non-RFC-3339 expiry fixtures, runs positive, negative, boundary, stale-policy, partial-population and dependency-failure fixtures, and reconciles the complete eligible population without trusting a component-supplied verdict.

AID-H-029.001-G002

Propagate data-use tags through transformations and derived assets using a defined inheritance rule, and keep the tags queryable in the metadata or lineage system.
Concept:

Derived assets need derived authorization. A common safe default is most-restrictive-wins: the derived asset is authorized only for stages allowed by every upstream source.

Example propagation logic

Derive every field that AID-H-029.002 reads. Purposes intersect per stage, so a stage is dropped when the parents share no purpose for it. Withdrawal of any parent withdraws the child, and the earliest bounded parent expiry becomes the child expiry. Parents from different jurisdictions are not merged automatically. Run validate_tags from AID-H-029.001-G001 on the derived object before publishing it.

# File: data_governance/derive_tags.py
from __future__ import annotations

from datetime import datetime

TIER_ORDER = ["public", "internal", "confidential", "restricted"]


class DerivationRejected(ValueError):
    pass


def _expiry(value: str) -> datetime:
    return datetime.fromisoformat(value.replace("Z", "+00:00"))


def derive_child_tags(parent_tags: list[dict], *, child_asset_id: str,
                      child_asset_version: str, tag_version: str,
                      data_owner: str) -> dict:
    """Most-restrictive-wins derivation over parent tags that passed validate_tags."""
    if not parent_tags:
        raise DerivationRejected("a derived asset needs at least one tagged parent")
    jurisdictions = {t["jurisdiction"] for t in parent_tags}
    if len(jurisdictions) != 1:
        raise DerivationRejected("parents span jurisdictions; route the join to review")

    common_stages = set.intersection(*(set(t["authorized_stages"]) for t in parent_tags))
    purposes = {}
    for stage in sorted(common_stages):
        shared = set.intersection(*(set(t["authorized_purposes"][stage]) for t in parent_tags))
        if shared:  # a stage survives only with at least one purpose every parent grants
            purposes[stage] = sorted(shared)
    consent_types = {t["consent_type"] for t in parent_tags}
    bounded = [t["consent_expiry"] for t in parent_tags if t["consent_expiry"] is not None]
    return {
        "schema_version": "2.0",
        "asset_id": child_asset_id,
        "asset_version": child_asset_version,
        "tag_version": tag_version,
        "authorized_stages": sorted(purposes),
        "authorized_purposes": purposes,
        "classification_tier": max((t["classification_tier"] for t in parent_tags),
                                   key=TIER_ORDER.index),
        "consent_type": consent_types.pop() if len(consent_types) == 1 else "mixed",
        # The earliest bounded parent wins; null only when every parent is unbounded.
        "consent_expiry": min(bounded, key=_expiry) if bounded else None,
        "authorization_withdrawn": any(t["authorization_withdrawn"] for t in parent_tags),
        "jurisdiction": jurisdictions.pop(),
        "data_owner": data_owner,
    }

Operational notes: Record both the upstream tag set and the derived tag decision in the lineage system so reviewers can explain why a downstream asset is or is not authorized for a given stage.

Production implementation

At transformation commit, read authoritative parent tags, apply the signed inheritance/join rule to the complete parent set, bind the derived tag digest to inputs/code/output and reject publication when lineage or a required tag cannot be reconciled.

Independent verification

A separately credentialed verifier independently reads back or replays the full parent-edge population, inheritance recomputation and catalog/output readback with missing parent, conflicting tags, multi-parent join, per-stage purpose intersection, withdrawn parent, mixed expiry, cross-jurisdiction join, drift and bypass fixtures, runs positive, negative, boundary, stale-policy, partial-population and dependency-failure fixtures, and reconciles the complete eligible population without trusting a component-supplied verdict.

AID-H-029.001-G003

Backfill legacy assets with suggested tags using automated scanning, but require human approval before those suggested tags become authoritative.
Concept:

Legacy datasets often have no purpose metadata. Use automated heuristics to suggest tags, but do not auto-approve high-impact authorization decisions.

Example heuristic suggestion flow

Suggestions use the same per-stage purpose shape as the authoritative schema. The reviewer still supplies the asset and tag versions, consent type and expiry, withdrawal state, jurisdiction and owner, and the completed record must pass validate_tags before it becomes authoritative.

def suggest_tags(asset_profile: dict) -> dict:
    """Return review suggestions only; never write these as authoritative tags."""
    contains_pii = asset_profile.get("contains_pii") is True
    purpose = "hr_support" if asset_profile.get("source_system") == "hr" else "case_handling"
    stages = ["inference_context"] if contains_pii else ["evaluation", "inference_context"]
    return {
        "classification_tier": "restricted" if contains_pii else "internal",
        "authorized_stages": stages,
        "authorized_purposes": {stage: [purpose] for stage in stages},
    }

Operational notes: Keep the human-in-the-loop on final approval. Track classification coverage as an operational metric so you can see what percentage of your AI-reachable data estate has authoritative data-use tags.

Production implementation

At the backfill workflow, scan exact asset bytes/metadata with pinned rules and scanner release, store suggestions separately from authoritative tags, require a qualified human decision bound to the asset digest and deny lifecycle use until approval or explicit quarantine.

Independent verification

A separately credentialed verifier independently reads back or replays legacy inventory, scanner result, reviewer identity/decision and effective tag state with false-positive, changed-asset, stale-review, rejected and unreviewed fixtures, runs positive, negative, boundary, stale-policy, partial-population and dependency-failure fixtures, and reconciles the complete eligible population without trusting a component-supplied verdict.

Open-source tools

Commercial / hosted tools

Threat mappings

MITRE ATLAS

MAESTRO

OWASP LLM Top 10 2026

OWASP ML Top 10 2023

OWASP Top 10 for Agentic Applications 2026

NIST Adversarial Machine Learning 2025

Cisco Integrated AI Security and Safety Framework

Google Secure AI Framework 2.0 - Risks

Databricks AI Security Framework 3.0

AID-H-029.001: Data-Use Policy Schema, Tagging & Classification | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version