Status: Draft. Author: Nazareno Clemente (naza@helloastro.co). Discussion: github.com/ar-agents/ar-agents/discussions. License: CC-BY-4.0.
Companions: RFC-001 (three-layer liability + governance taxonomy), RFC-002 (agent-discovery-by-default), RFC-003 (cross-jurisdictional reciprocity envelope).
Reference implementation: /architecture/audit-log (this page's code-level companion), and
apps/landing/src/lib/audit.tsin github.com/ar-agents/ar-agents.
/rfcs/{n} URL. The /cite page generates BibTeX, APA and Chicago citations anchored to a commit hash automatically.1 · The gap RFC-004 fills
RFC-001 § 9.1 says "every sociedad-IA MUST keep an append-only audit log, HMAC-signed at write time." That sentence is enough legal scaffolding to anchor the liability framework, but it leaves a dozen implementation decisions on the floor — and every implementation that diverges is a regulator's headache later.
RFC-004 pins down:
- Which fields each entry MUST have, MAY have, MUST NOT have.
- How the HMAC is computed (canonical-JSON, key derivation, version tag).
- What "append-only" means in code (KV invariants, deletion ban, ordering).
- How a regulator verifies an entry without holding the signing key.
- Minimum retention + maximum retention with privacy guard.
- The streaming + export interfaces a sociedad-IA MUST expose.
- The conformance test vectors a library author runs to claim RFC-004 compatibility.
Treat this as the document a journalist, auditor, or AFIP inspector prints out and uses to challenge a sociedad-IA's claim that it ran legitimately.
2 · Entry shape (normative)
Every entry written to the operational log MUST conform to this TypeScript-ish shape. JSON-Schema published at https://ar-agents.vercel.app/schemas/operational-log-entry.v1.json (planned, not yet served).
interface OperationalLogEntry {
// MUST. Stable across reads. Format: ISO-8601 UTC + "-" + 8-hex-char nonce.
// Example: "2026-05-11T14:23:01.512Z-a1b2c3d4"
id: string;
// MUST. Identifies the operational session. Format: [A-Za-z0-9_-]{8,64}.
// A session is a coherent series of tool-calls that share a single
// governance context (e.g. one human-confirmed action through completion).
sessionId: string;
// MUST. ISO-8601 UTC. Server-clock at write time, not request-clock.
ts: string;
// MUST. The tool / endpoint / model / external-API that produced the
// side effect. Naming: lowercase, dot-separated. Examples:
// "mercadopago.preapproval.create"
// "afip.padron.consultar"
// "anthropic.messages.create"
// "internal.policy.gate"
tool: string;
// MUST. RFC-001 governance class. Exactly one of:
governance:
| "algorithm-only" // Pure code, no LLM call. Deterministic.
| "audit-logged" // LLM call ran, output logged + classified.
| "mocked-upstream" // External API not wired. Demo-tier.
| "requires-confirmation" // Human approval present (HITL).
// MUST. The serialized input to the tool. Canonical-JSON serializable.
// MUST NOT contain raw secrets — strip before logging.
input: unknown;
// MAY. The serialized output. Omit if the tool errored.
// MUST NOT contain raw secrets (tokens, keys, PII beyond what's needed).
output?: unknown;
// MAY. Truthy if the tool errored. The error reason SHOULD be in output.
errored?: boolean;
// MAY. Wall-clock duration in milliseconds. Useful for SLA + anomaly
// detection ("this call usually takes 200ms; this one took 12000ms").
durationMs?: number;
// MUST. HMAC-SHA256 of canonical-JSON(entry minus the hmac field).
// Format: "sha256:" + 64-hex-char.
// MAY be null in a development build with no signing key wired; in
// production a null hmac is a fatal misconfiguration.
hmac: string | null;
}Forbidden fields (MUST NOT appear):
password,secret,privateKey,apiKey,token— under any nested path. Library implementations SHOULD scrub before write.- Personally-identifying data beyond what is operationally necessary. CUIT is fine; full home address with floor and apt number probably isn't.
- User-supplied free-form text that hasn't been bounded. A 10MB prompt body in an audit entry breaks SSE + makes export expensive. SHOULD truncate at 64KB per field with a marker.
3 · HMAC computation (normative)
The signature is computed in three deterministic steps:
- Step 1 — strip. Remove the
hmacfield if present. Sign + verify MUST operate on the same field set. - Step 2 — canonicalize. Stringify the remaining object with all keys sorted lexicographically at every level. Use the canonical-JSON function defined below — JSON.stringify is not canonical in JavaScript and re-serialization differs across runtimes.
- Step 3 — HMAC.Compute HMAC-SHA256 of the UTF-8 bytes using the sociedad-IA's signing key. Hex-encode + prefix with
sha256:.
function canonical(value: unknown): string {
if (value === null || typeof value !== "object") {
return JSON.stringify(value); // primitives + null
}
if (Array.isArray(value)) {
return `[${value.map(canonical).join(",")}]`;
}
const obj = value as Record<string, unknown>;
const keys = Object.keys(obj).sort();
return `{${keys
.map(k => `${JSON.stringify(k)}:${canonical(obj[k])}`)
.join(",")}}`;
}The signing key (RFC-004 calls it AUDIT_HMAC_SECRET) is a single shared symmetric secret. Future revisions of this RFC may add asymmetric signatures (Ed25519) where the sociedad-IA publishes a public key + signs with a private key — easier for third-party verification — but the v1 baseline is symmetric HMAC. Rationale: HMAC is universally available in every standard library + Web Crypto, has a vetted security boundary, and an asymmetric upgrade can be additive (entry carries both a hmac and a signature field with a key-id).
4 · Append-only invariants (normative)
"Append-only" is not magic. It is a set of code-level constraints + a set of operational checks:
- Code constraint. The library MUST expose exactly one mutation primitive:
appendAudit(sessionId, partial). NoupdateAudit. NodeleteAudit. A regulator reading the source must find no path that mutates an existing entry. - Storage constraint. The backing store SHOULD be a structure where in-place mutation is unnatural (a list, an append-only log, a Kafka-style topic). The reference implementation uses Vercel KV
RPUSH+LRANGE+ TTL. Stores that allow random-access overwrite by key (S3 PUT, plain Redis HSET) are permitted only if a Merkle-chain or checksum tree gives the same guarantee. - Ordering constraint. Entry IDs include the server-clock timestamp first, so a textual sort of IDs is the temporal order. An out-of-order entry (clock skew or fork) MUST still be appended, and downstream consumers SHOULD flag the skew rather than mask it.
- Verification constraint. The HMAC MUST be computed over the post-strip canonical JSON. Verifying a stored entry MUST re-compute against the same canonical form. The reference implementation includes both
signEntry+verifyEntryfor symmetry — a chain that signs but cannot self-verify is broken-by-design.
The only permitted destructive action is TTL-based purge for retention compliance (§ 7). Any deletion code path outside that flow is a violation of the RFC + grounds for the sociedad-IA to forfeit Layer 3 liability protection per RFC-001 § 9.4.
5 · Verification interface (normative)
A regulator MUST be able to verify an entry without holding the signing key. The sociedad-IA exposes this through:
- Read endpoint.
GET /api/audit/{sessionId}returns the full session as a JSON array of entries. Public, unauthenticated (the entries themselves are non-secret). - Verify endpoint.
GET /api/audit/{sessionId}?verify=1returns the entries plus{ total, verified, tampered, hmacWired }counts. The sociedad-IA does the verification server-side. If the regulator wants to verify independently, they fetch the entries + a separate verify-public-key URL. - Key endpoint (planned v2).
GET /.well-known/sociedad-ia/verify-keyreturns the sociedad-IA's public verification material — for v1 symmetric HMAC, this is the SHA-256 of the signing key with a challenge-nonce, allowing offline proof of key-possession without revealing the key itself. For v2 asymmetric, returns the Ed25519 public key. - Export endpoint.
GET /api/audit/{sessionId}/csvreturns RFC-4180 CSV with UTF-8 BOM. Required for regulatory tooling that doesn't speak JSON. - Stream endpoint.
GET /api/audit/{sessionId}/streamreturns Server-Sent Events with oneevent: appendper new entry + periodicevent: keepalive. Optional for v1, recommended for v1.1+ to enable live regulator dashboards.
6 · Governance taxonomy (normative)
The four governance classes are not decorative. They map directly to RFC-001 § 4 liability allocation:
┌─────────────────────────┬───────────────────────────────────────┐ │ Governance class │ Liability allocation per RFC-001 § 4 │ ├─────────────────────────┼───────────────────────────────────────┤ │ algorithm-only │ Operator. Pure code, deterministic. │ │ audit-logged │ Operator + recorded LLM provider. │ │ mocked-upstream │ Demo-tier; no production binding. │ │ requires-confirmation │ Human-in-the-loop; operator absorbs │ │ │ liability for the confirmed action. │ └─────────────────────────┴───────────────────────────────────────┘
A sociedad-IA emitting an entry with governance: "mocked-upstream" in production is making a public admission that the side effect did not happen against the real upstream. Regulators reading the log can tell a real cobro from a demo run by this field alone.
7 · Retention (normative)
Minimum retention: 180 days. Rationale: the AFIP general statute of limitations for fiscal claims is 5 years, but the practical window for operational disputes (chargebacks, consumer complaints, AP2 disputes) closes within 90–120 days. 180 days covers operational + early fiscal challenge.
Maximum retention: 5 years for HMAC-signed entries, after which they MUST be either re-signed under a new key-rotation epoch or purged. Indefinite retention of personally-identifying operational logs creates privacy + data-protection risk (Ley 25.326) that the RFC declines to inherit.
The reference implementation sets a 7-day TTL on Vercel KV for development convenience. Production sociedades-IA MUST configure either: a separate cold-storage path with 180d-to-5y retention, or a per-session retention policy that respects the user's data rights (Ley 25.326 art 16: right to deletion). RFC-004 v1 is silent on the implementation; v1.1 will define a retentionClass field per entry.
8 · Conformance test vectors
A library claiming RFC-004 v1 conformance MUST pass these deterministic vectors. Future revisions of the RFC will publish vectors at /test-vectors/rfc-004-v1.json; for now, the vectors are embedded here:
// Vector 1: canonical-JSON is key-sorted.
canonical({ b: 1, a: 2 })
=== '{"a":2,"b":1}'
// Vector 2: canonical-JSON recurses into arrays + objects.
canonical({ a: [{ z: 1, y: 2 }, 3] })
=== '{"a":[{"y":2,"z":1},3]}'
// Vector 3: HMAC is computed over canonical form, hmac field stripped.
const entry = {
id: "2026-05-11T00:00:00.000Z-deadbeef",
sessionId: "test-session",
ts: "2026-05-11T00:00:00.000Z",
tool: "test.echo",
governance: "algorithm-only",
input: { ping: 1 },
output: { pong: 1 },
hmac: null
};
const secret = "rfc-004-conformance-secret";
const expected = "sha256:a4b1c8f7..."; // computed at vector-publish time
signEntry(entry, secret) === expected;
// Vector 4: verify accepts the entry, rejects a mutated copy.
verifyEntry(entry, secret) === true;
verifyEntry({ ...entry, output: { pong: 2 } }, secret) === false;
// Vector 5: re-signing the same entry is idempotent.
signEntry(entry, secret) === signEntry(entry, secret);
// Vector 6: deeply-nested mutation is detected.
const big = {
...entry,
input: { batch: [{ amount: 100 }, { amount: 200 }] }
};
const bigSig = signEntry(big, secret);
const mutated = JSON.parse(JSON.stringify(big));
mutated.input.batch[1].amount = 201;
verifyEntry({ ...mutated, hmac: bigSig }, secret) === false;The reference implementation passes these vectors. The full test suite (with hex-exact expected values, regenerated at vector publish time) lives in apps/landing/src/lib/audit.test.ts alongside the library source. v1 finalization includes pinning every expected HMAC value.
9 · What a regulator can demand
Without a court order, a regulator (AFIP, BCRA, AAIP for data protection) can require a sociedad-IA to produce, within a defined SLA:
- Session inventory. The list of sessionIds active during a window. Sociedad-IA SHOULD have a tenant-scoped enumeration endpoint; RFC-004 v1.1 will normative-ize it.
- Full session export. The complete entry list for a sessionId in JSON + CSV. SLA: 1 business day.
- Verification proof. The HMAC verification result + the key-possession proof (challenge-response). SLA: same as above.
- Operational narrative. A human-readable summary of what the sociedad-IA was doing during the window — generated from the audit log, not from human recollection. The reference stack provides this via /play/dashboard + the CSV export.
With a court order, the regulator can additionally compel production of the signing-key custody chain (who held the AUDIT_HMAC_SECRET, where it was stored, who rotated it when) — equivalent to compelling production of a wet-signature notary's seal-custody log.
10 · Compliance with companions
RFC-001 § 9 → RFC-004 § 2-4. The append-only, HMAC-signed requirement is here, formally.
RFC-002 → RFC-004 § 5. The discovery convention advertises the verify + export endpoints. RFC-004 defines what those endpoints must return.
RFC-003 → RFC-004 § 2. The cross-jurisdictional envelope wraps RFC-004 entries. The entries[] array in an RFC-003 envelope is normative-ly defined here.
11 · Open questions
- Asymmetric upgrade. When does v2 ship? The additive path (entries carry both
hmac+ an optionalsignaturefield with key-id) is straightforward; the harder question is key-distribution + rotation governance. - Retention v1.1. Per-entry
retentionClassfield with valuesoperational(180d),fiscal(5y),privacy-erased(allowed after Ley 25.326 deletion request, content is null but metadata + HMAC are preserved for chain integrity). - Streaming SLA. Should the SSE endpoint be MUST-implement or SHOULD-implement? Live dashboards are a strong regulator-comfort signal; a tiny sociedad-IA may not have the ops headroom.
- Privacy boundary on free-text inputs.What about user prompts that contain sensitive personal data the user themselves volunteered? RFC-004 v1 says "truncate at 64KB, scrub known-sensitive keys." v1.1 should formalize a per-tool input-policy (e.g., the policy gate input is never retained verbatim; only its classification result is).
- Aggregation across sociedades-IA. Should the spec define a single national index of sessionIds (regulator can search across all sociedades-IA at once)? Hot debate; centralization risk vs. enforcement utility.
12 · Decision request
For the AR sociedad-IA legislative project to reference a consistent log format, RFC-004 needs to be either adopted as the canonical reference or explicitly superseded. The proposed path:
- The legislation cites RFC-004 v1 as the minimum operational-log spec a sociedad-IA must implement to qualify for the regime.
- The RFC remains open-source, MIT/CC-BY, hosted at this URL, governance through github.com/ar-agents/ar-agents/discussions.
- Future revisions track changes in a versioned changelog at /changelog; v1 stays frozen so legislation referencing it remains stable.
Comments + counter-proposals welcome. This is a draft; v1 finalization includes pinning every test-vector hex and any clarifications surfaced in discussion.