Skip to main content

Contradictory Annotation Detection

Contradictory annotations occur when two or more AXAG attributes on the same element express incompatible semantics. These contradictions create ambiguity for agents, which cannot determine the correct interpretation.

Why It Matters​

When an agent encounters contradictory annotations, it must either:

  • Guess which attribute to trust (dangerous)
  • Reject the operation entirely (safe but limits functionality)
  • Ask for human clarification (possible but defeats automation)

None of these outcomes are acceptable in production. Contradictions MUST be detected and resolved before deployment.

Common Contradictions​

1. Risk Level vs Action Type​

A read operation annotated with critical risk:

❌ Contradictory — read with critical risk
<!-- ❌ Contradictory: reads don't destroy anything -->
<button
axag-intent="product.search"
axag-entity="product"
axag-action-type="read"
axag-risk-level="critical"
>Search</button>

Rule: AXAG-LINT-013 (error) — a read operation MUST NOT be critical. AXAG-LINT-025 (info) also notes a read above low, which is sometimes right for a sensitive export.

Fix:

✅ Fix — risk level matches action type
<button
axag-intent="product.search"
axag-entity="product"
axag-action-type="read"
axag-risk-level="none"
>Search</button>

2. Approval Without Roles​

<!-- ❌ Contradictory: approval required but nobody can give it -->
<button
axag-intent="expense.approve"
axag-entity="expense"
axag-action-type="write"
axag-approval-required="true"
axag-risk-level="high"
>Approve</button>

Rule: AXAG-LINT-012 (error) — when axag-approval-required="true", axag-approval-roles MUST be specified.

3. Preconditions Contradicting Action Type​

<!-- ❌ Contradictory: create with "must exist" precondition -->
<button
axag-intent="product.create"
axag-entity="product"
axag-action-type="write"
axag-risk-level="low"
axag-idempotent="false"
axag-preconditions='["product must exist"]'
>Create Product</button>

Rule: AXAG-LINT-014 (warning) — a write whose preconditions say the entity "must exist" or "already exists" is either an update, or the precondition is wrong.

4. Navigation With Side Effects​

<!-- ❌ Contradictory: navigation that changes state -->
<a
href="/logout"
axag-intent="session.end"
axag-entity="session"
axag-action-type="navigate"
axag-side-effects='["session_destroyed"]'
>Log out</a>

Rule: AXAG-LINT-015 (warning) — navigate means changing the view. An action with side effects is a write or a delete.

5. Tenant Roles Outside Tenant Scope​

<!-- ❌ Contradictory: a tenant_ role approving a global action -->
<button
axag-intent="billing.adjust"
axag-entity="billing"
axag-action-type="write"
axag-risk-level="high"
axag-idempotent="false"
axag-confirmation-required="true"
axag-scope="global"
axag-approval-required="true"
axag-approval-roles='["tenant_admin"]'
>Adjust Billing</button>

Rule: AXAG-LINT-016 (warning) — a tenant_-prefixed approval role with a non-tenant scope means one of the two is wrong.

Also Worth Checking by Hand​

Some contradictions no rule can see. An operation declared axag-idempotent="true" whose side effects include email_sent is one: repeating it sends another email, so either the declaration or the side effect is wrong. AXAG-LINT-017 only catches the opposite case — a non-idempotent mutation that declares no side effects at all.

Detection Rules Summary​

RuleContradictionDefault severity
AXAG-LINT-012Approval required + no approval rolesError
AXAG-LINT-013Read + critical riskError
AXAG-LINT-014Write + "must exist" preconditionWarning
AXAG-LINT-015Navigate + side effects declaredWarning
AXAG-LINT-016Tenant-only approval roles + non-tenant scopeWarning
AXAG-LINT-025Read + risk above lowInfo

CI Integration​

# In your CI pipeline
- name: Check for contradictory annotations
run: npx axag-lint src --format github

Next Steps​