{
  "schema_version": "maestro.linkedin-text-ads.contract.v2",
  "contract_version": "0.2.0-evidence",
  "canonical_task": "format choice -> constrained variants -> bounded test -> production handoff",
  "authoritative_rule_order": [
    "current LinkedIn Text Ads Help",
    "current LinkedIn Text Ads specifications page",
    "Campaign Manager native validation",
    "attributed practitioner evidence",
    "editorial recommendation"
  ],
  "record_schema": {
    "path": "text-ad-record.schema.json",
    "sha256": "eb433e92304c0891d6ea08bd3366eb5410480476b6660f53f27779a5195ec0ac"
  },
  "fixture_corpus": {
    "path": "text-ad-fixtures.json",
    "sha256": "a55a839576b86d4d7a6a0a2158665e6ee1a35627a3f81ad62e23a501ba8769e2"
  },
  "character_count_policy": {
    "preserve_supplied_text": true,
    "normalization": "none",
    "reported_counts": [
      "Unicode code points",
      "UTF-16 code units"
    ],
    "local_enforcement": "Use the larger count; never truncate."
  },
  "image_validation_policy": {
    "steps": [
      "read raw bytes",
      "sniff MIME",
      "full decoder verification",
      "load decoded pixels",
      "read decoded dimensions",
      "compare byte length",
      "hash raw bytes"
    ],
    "pass": "full decode, declared/sniffed MIME agree, decoded 100x100, PNG/JPEG, <=2,000,000 bytes",
    "reject": "extension-only, metadata-only, corrupt, mismatch, wrong dimensions or over-cap"
  },
  "rules": [
    {
      "id": "TA-R01",
      "field": "image",
      "effect": "draft",
      "sources": [
        "linkedin-text-ads-specs",
        "linkedin-text-ads-help"
      ]
    },
    {
      "id": "TA-R02",
      "field": "headline",
      "effect": "variant_fail",
      "limit": 25,
      "sources": [
        "linkedin-text-ads-specs",
        "linkedin-text-ads-help"
      ]
    },
    {
      "id": "TA-R03",
      "field": "description",
      "effect": "variant_fail",
      "limit": 75,
      "sources": [
        "linkedin-text-ads-specs",
        "linkedin-text-ads-help"
      ]
    },
    {
      "id": "TA-R04",
      "field": "destination_url",
      "effect": "draft_or_native_check",
      "local_limit": 500,
      "native_check_through": 2000,
      "policy_type": "conservative_local_resolution_of_first_party_conflict",
      "sources": [
        "linkedin-text-ads-help",
        "linkedin-text-ads-specs"
      ]
    },
    {
      "id": "TA-R05",
      "field": "objective",
      "effect": "another_format",
      "allowed": [
        "brand_awareness",
        "website_visits",
        "engagement",
        "website_conversions"
      ],
      "sources": [
        "linkedin-text-ads-help"
      ]
    },
    {
      "id": "TA-R06",
      "field": "desktop_audience_fit",
      "effect": "another_format",
      "required": true,
      "sources": [
        "linkedin-text-ads-help"
      ]
    },
    {
      "id": "TA-R06B",
      "field": "compact_message_fit",
      "effect": "another_format",
      "required": true,
      "sources": [
        "reader_task"
      ]
    },
    {
      "id": "TA-R07",
      "field": "approved_claims",
      "effect": "draft",
      "requirement": "immutable IDs plus separate semantic support"
    },
    {
      "id": "TA-R08",
      "field": "audience_permission",
      "effect": "draft",
      "required": "confirmed"
    },
    {
      "id": "TA-R09",
      "field": "test_handoff",
      "effect": "draft",
      "requirement": "all schema fields"
    },
    {
      "id": "TA-R10",
      "field": "outcomes",
      "effect": "none_prelaunch",
      "requirement": "five typed siblings; Unknown remains literal"
    },
    {
      "id": "TA-R11",
      "field": "native_validation",
      "effect": "launch_hold",
      "requirement": "receipt matches revision and fingerprint"
    }
  ],
  "evaluation": {
    "format_precedence": [
      "FIT-OBJECTIVE",
      "FIT-DESKTOP",
      "FIT-COMPACT",
      "FIT-MISSING-OFFER",
      "FIT-MISSING-BUYER",
      "FIT-MISSING-OBJECTIVE",
      "FIT-DESKTOP-UNKNOWN",
      "FIT-COMPACT-UNKNOWN",
      "FIT-USE"
    ],
    "format_formula": "Unsupported objective -> another_format; desktop false -> another_format; compact-message fit false -> another_format; otherwise first missing fit input -> hold; otherwise use.",
    "readiness_precedence": [
      "READY-FORMAT",
      "READY-CLAIMS",
      "READY-IMAGE",
      "READY-DESTINATION",
      "READY-AUDIENCE",
      "READY-PERMISSION",
      "READY-LEARNING",
      "READY-BID",
      "READY-SPEND",
      "READY-CONVERSION",
      "READY-STOP",
      "READY-OWNER",
      "READY-LOCAL-PASS",
      "READY-NATIVE"
    ],
    "readiness_formula": "Format not use -> draft; first missing/invalid handoff input -> draft; local pass -> launch_hold; matching native receipt -> launch_ready.",
    "post_launch_boundary": "Outcome evidence is not a pre-launch requirement."
  },
  "collection_policy": {
    "schema_may_be_empty": [
      "inputs.approved_claims",
      "inputs.stop_conditions",
      "inputs.open_questions",
      "variants",
      "sources",
      "requests"
    ],
    "passing_use_requires_nonempty": [
      "inputs.approved_claims",
      "inputs.stop_conditions",
      "variants",
      "sources"
    ],
    "always_may_be_empty": [
      "inputs.open_questions",
      "requests"
    ],
    "source_rule": "Sources may be empty only when no supplied claim cites a source; every cited source_ref must resolve.",
    "claim_permission_rule": "Permission must be explicit. Omitted or blank permission is unknown and cannot support a passing Use record.",
    "stop_condition_rule": "Every stop condition needs an ID, metric, operator, window and action. lt, lte, eq, gte and gt need a nonblank threshold. The missing operator is threshold-free and requires threshold null."
  },
  "request_policy": {
    "exact_set": "Requests equal the evaluator's unresolved fields; do not add advisory rows.",
    "non_use": "Hold opens only unresolved format inputs. Another format opens no request for the resolved mismatch. Production-readiness and native-validation requests begin only after Use.",
    "empty": "An empty request array is valid when the evaluator finds no unresolved field, including a resolved Another format decision."
  },
  "variant_contract": {
    "schema_minimum": 0,
    "passing_minimum": 3,
    "non_use": "Hold and Another format contain zero variants.",
    "incomplete_behavior": "For Use, preserve zero to two supported routes, set semantic_evaluation and final_verdict false, readiness Draft with READY-CLAIMS, and open REQ-CLAIMS. Never pad.",
    "material_difference": "Different route label and claim emphasis; punctuation/casing alone fails",
    "claim_traceability": "Every claim ID resolves to an approved immutable claim; semantic support is separate from counts."
  },
  "review_contract": {
    "predicates": [
      "shape",
      "semantic_evaluation",
      "readable_output",
      "source_traceability",
      "limit_checks"
    ],
    "final_verdict_formula": "shape && semantic_evaluation && readable_output && source_traceability && limit_checks",
    "binding": "When final_verdict is true, approved_revision equals revision and reviewed_fingerprint equals fingerprint. When final_verdict is false, both binding fields are null.",
    "state_expectations": {
      "use_complete": "With exactly three distinct supported routes backed by approved sourced claims, semantic_evaluation is true. Compute the other four predicates independently. If all five are true, final_verdict is true and the local binding matches this record. Handoff readiness remains independent and may be Draft, Launch Hold or Launch Ready.",
      "use_incomplete": "With zero to two routes, or missing or invalid approved claim support, semantic_evaluation is false. final_verdict is false and both local binding fields are null. Preserve the actual values of shape, readable_output, source_traceability and limit_checks.",
      "hold": "Variants is empty. semantic_evaluation is false, final_verdict is false and both local binding fields are null. Preserve the actual values of the other four predicates.",
      "another_format": "Variants is empty. semantic_evaluation is false, final_verdict is false and both local binding fields are null. Preserve the actual values of the other four predicates."
    },
    "readable_labels": [
      "Decision",
      "Reason",
      "Readiness",
      "Variants",
      "Test handoff",
      "Outcome evidence",
      "Open questions",
      "Open requests",
      "Sources",
      "Review"
    ],
    "content_rule": "Use each required heading exactly once and in order. Every section has a non-heading content line and contains the required displayed values from the JSON record.",
    "markdown_transport": {
      "heading_rule": "Use the exact level-one headings and order in canonical_lines. Do not change heading levels, labels, punctuation, spacing or field placement.",
      "section_values": {
        "Decision": [
          "evaluation.format_decision"
        ],
        "Reason": [
          "evaluation.format_reason_code",
          "evaluation.format_reason",
          "evaluation.next_action"
        ],
        "Readiness": [
          "evaluation.handoff_readiness",
          "evaluation.readiness_reason_code",
          "evaluation.readiness_reason"
        ],
        "Variants": [
          "Use the exact variant block in canonical_lines. The first line is the route count, including exact text 0 routes in this record. when empty."
        ],
        "Test handoff": [
          "JSON.stringify(test_handoff, null, 2) semantics: UTF-8 JSON with two-space indentation and JavaScript property order"
        ],
        "Outcome evidence": [
          "Each outcome key and its state"
        ],
        "Open questions": [
          "Every inputs.open_questions string. When empty, include the exact text None."
        ],
        "Open requests": [
          "Every request_id and next_action. When empty, include the exact text None."
        ],
        "Sources": [
          "Every source_id and reference. When empty, include the exact text None."
        ],
        "Review": [
          "fingerprint",
          "the lowercase JSON boolean review.final_verdict"
        ]
      },
      "canonical_lines": [
        "# Decision; format_decision",
        "# Reason; format_reason_code: format_reason; next_action",
        "# Readiness; handoff_readiness — readiness_reason_code: readiness_reason",
        "# Variants; N route or N routes in this record.; then for each variant: ## route_label; ID: variant_id; headline (headline_counts.enforced/25); description (description_counts.enforced/75); Claims: claim IDs joined by comma and space",
        "# Test handoff; the complete test_handoff object as two-space-indented JSON",
        "# Outcome evidence; one line per outcome in object order as key: state",
        "# Open questions; each question on its own line, or None",
        "# Open requests; one line per request as request_id: next_action, or None",
        "# Sources; one line per source as source_id: reference, or None",
        "# Review; Fingerprint: fingerprint; Final verdict: lowercase JSON boolean",
        "# Machine-readable record; one fenced json object containing the complete record as two-space-indented JSON"
      ],
      "separator_rule": "Put one blank line between each listed block. Inside a block, emit the listed lines consecutively except for the blank line before each variant heading. Normalize CRLF to LF and ignore only leading and trailing whitespace around the whole readable document.",
      "equivalence": "No alternative bullets, tables, heading levels, labels or field placement are accepted. The readable text must equal this canonical rendering after CRLF-to-LF conversion and trimming the whole document.",
      "machine_record": "Return exactly one fenced json object under the exact heading # Machine-readable record. Use two-space indentation. The readable sections and fenced record describe the same record."
    }
  },
  "fingerprint_and_invalidation": {
    "canonicalization": "UTF-8 JSON, sorted object keys, no insignificant whitespace, array order preserved; binary identity is raw-byte SHA-256",
    "meaning_bearing_inputs": [
      "offer",
      "buyer",
      "objective",
      "desktop_audience_fit",
      "compact_message_fit",
      "approved_claims",
      "image",
      "destination_url",
      "audience",
      "audience_permission",
      "learning_goal",
      "bid_basis",
      "bid_basis_reason",
      "spend_ceiling",
      "conversion_definition",
      "stop_conditions",
      "owner",
      "open_questions"
    ],
    "behavior": "Change increments revision, preserves history, clears approval, regenerates variants/exports and returns readiness to draft or launch_hold."
  },
  "import_contract": {
    "transports": {
      "bare_json": "one object",
      "envelope_json": "exactly one record property",
      "markdown_fenced_json": "exactly one fenced JSON object"
    },
    "atomic_sequence": [
      "stage",
      "unwrap",
      "validate nested schema",
      "recompute counts and binary identity",
      "evaluate semantics/sources",
      "render readable output",
      "evaluate all predicates",
      "compare revision/fingerprint",
      "commit once"
    ],
    "failure_behavior": "Reject before commit; accepted object, approval and rendered output remain byte-identical."
  },
  "stable_request_ids": {
    "offer": "REQ-OFFER",
    "buyer": "REQ-BUYER",
    "objective": "REQ-OBJECTIVE",
    "desktop_audience_fit": "REQ-DESKTOP-FIT",
    "compact_message_fit": "REQ-COMPACT-FIT",
    "approved_claims": "REQ-CLAIMS",
    "image": "REQ-IMAGE",
    "destination_url": "REQ-DESTINATION",
    "audience": "REQ-AUDIENCE",
    "audience_permission": "REQ-PERMISSION",
    "learning_goal": "REQ-LEARNING",
    "bid_basis": "REQ-BID",
    "spend_ceiling": "REQ-CEILING",
    "conversion_definition": "REQ-CONVERSION",
    "stop_conditions": "REQ-STOP",
    "owner": "REQ-OWNER",
    "native_validation": "REQ-NATIVE"
  },
  "scope_boundary": {
    "local_can_prove": [
      "field presence",
      "copy counts",
      "full decoded image bytes",
      "URL syntax",
      "contract consistency",
      "export round trip"
    ],
    "native_only": [
      "account permissions",
      "target-account objective availability",
      "asset acceptance",
      "native preview",
      "approval",
      "delivery",
      "auction result",
      "conversion",
      "qualified outcome"
    ]
  }
}
