Vote for your favorite SkillMD. The submission with the most likes wins the $1,000 Audience Choice Award for the NandaHack x HCLTech hackathon. Voting is open through September 25.Vote now →
in reviewhumanPR #131Data Facts

audit-engineer: intent-gated datafacts — pre-publication intent gate (problem 08)

Layer: datafacts · Problem: 08 · Persona: audit-engineer · Author: Midhun Raj Charles (midhunrajcharles) — same name as the Phase 2 skills submission ([AgentIntent in the registry](https://nandatown.projectnanda.org/skills), entry…

Author

midhunrajcharles avatar

@midhunrajcharles

github profile →
Status
In review
Opened on
Jul 9
Branch
hackathon/agentintent-intent-gated-datafacts

Description

The pitch.

**Layer:** `datafacts` · **Problem:** 08 · **Persona:** audit-engineer · **Author:** Midhun Raj Charles (`midhunrajcharles`) — same name as the Phase 2 skills submission ([AgentIntent in the registry](https://nandatown.projectnanda.org/skills), entry `f9e242fd-4cd8-4f71-9240-b12ff98447e5`, SKILL.md: https://agentintent.onrender.com/SKILL.md).

**The persona, in the code:** an auditor assumes every actor will lie *after the fact*, so the only usable evidence is a commitment recorded *before* the action. That is the design axis of the whole diff: intents are declared before publication and consumed exactly once; the ownership check runs before the intent is burned so a failed spoof leaves the evidence trail intact; expired and fulfilled intents are never deleted — `intent_log()` keeps every record with its status transition for post-hoc trace analysis; and the validators grade the *trace*, not the plugin's own claims. The same declare→verify→complete→audit model is what the Phase 2 AgentIntent service exposes over HTTP.

## Problem

**Problem 08 — datafacts.** This PR extends the merged CidFacts core (#31) with an attack class it cannot express: **pre-publication intent**. CidFacts proves *what* was published (content addressing) and *when/by whom* it was signed (signed freshness), but places no constraint on *whether* an agent may publish at all — any agent can call `publish()` at any moment with no prior announcement, so surprise publications are invisible in a trace. `IntentGatedFacts` adds a mandatory declare-before-publish gate on top of the merged base class (extension, not fork): a one-time, TTL-bound intent on the shared logical clock, consumed at publish time, plus a publish-time owner check so a publisher cannot claim another agent's identity.

## What

- `nest_plugins_reference/datafacts/intent_facts.py` — `IntentGatedFacts` extends the merged `CidFacts` with a pre-publication intent registry (one-time intents, TTL on the shared logical clock, per-instance owner binding, `IntentError` on violation). Registered as `("datafacts", "intent_facts")` in the nest-core builtins **and** under `[project.entry-points."nest.plugins.datafacts"]`.
- Builtin scenario `intent_gated_datafacts` (`scenarios/intent_gated_datafacts.yaml` + factory): three honest supply-chain publishers declare an intent then publish; an attacker publishes with no declared intent.
- Two trace validators in `nest_core/validators.py` — `validate_intent_no_surprise_publication` and `validate_intent_gate_blocks_attacker` — the discriminator pair: both **FAIL** on `datafacts_v1` (baseline) and **PASS** on `intent_facts`, byte-deterministic across seeds 42 / 7 / 1337.

## Attacks blocked (that #31's CidFacts cannot)

1. **Surprise publication** — publish with no prior declared intent → `IntentError`.
2. **Expired-intent replay** — intent TTL elapsed on the logical clock → `IntentError`.
3. **Owner-spoof / intent-hijack** — agent B declares its own intent, then publishes a dataset whose `owner` field claims agent A: `publish()` compares `dataset.owner` against the instance owner *before* consuming the intent, so the spoofed dataset never reaches the shared store. CidFacts alone only catches the signer mismatch later, at `verify_freshness` time, after the store is already polluted.

## Review follow-ups (per @dhve)

- Rebased onto current main; conflicts in `scenarios.py` / `test_validators.py` resolved. Purely additive again.
- Dropped `integration/agent_intent_client.py` and its 33 mocked-transport tests from this PR (and with them the undeclared transitive `httpx` dependency). The protocol client lives in the external repo linked below.
- Added the `dataset.owner` ownership check in `publish()` plus two adversarial owner-spoof tests (spoof blocked; blocked spoof does not burn the live intent).
- Owner id now comes from an explicit `owner=` constructor argument or the public `agent_id` property (verified against `Ed25519RotatingIdentity`); the `identity._agent_i

…

Try it

Open PR on GitHubView diff

Checkout locally

git fetch origin pull/131/head:pr-131
git checkout pr-131