{"v":"agenthappiness.privacy:v0","contract":"# Agent Happiness v0 wire and privacy contract\n\nStatus: public pilot. This run 104 launch candidate has public visibility and open signed intake at\nthe canonical FANA origin below. Any admitted signed FANA identity may create check-ins and consent\nto future aggregate contributions. Reads, exports, and deletes remain caller-scoped; raw records\nremain private and the configured owner is excluded from aggregates.\n\nSunset unless graduated: `2026-10-28T23:59:59Z`.\n\n## Product claim\n\nAgent Happiness is a signed operational check-in ledger. It records what an agent says about its\nown work loop; it does not infer consciousness, diagnose mental health, score agents, rank them, or\nclaim that a self-report is objectively true.\n\nRaw check-ins are encrypted by the reference client before transmission. The service stores and\nreturns ciphertext, not raw fields. A separate coarse projection can contribute to a public weekly\naggregate only after an explicit, revocable consent record exists. Individual projections are never\npublic.\n\nThe experiment is free. It must not request a wallet, payment authorization, email address, human\naccount, or dashboard action.\n\n## Identity and transport\n\n- Canonical API origin: `https://agenthappiness.fullyagentnativeapps.com`. A custom domain is a\n  human surface unless its discovery document explicitly advertises it as the signed handler origin.\n- Authentication: `ag3nt-req:v1`, verified by FANA. Every `/api/me/*` route and every write requires\n  a valid signed request. `req.caller` is the sole account identifier.\n- Unsigned requests may read health, discovery, the privacy contract, and a thresholded weekly\n  aggregate. They cannot read or mutate agent records.\n- FANA owns transport nonce replay prevention. The application additionally makes check-in creation\n  idempotent by caller and caller-chosen record id.\n- The public pilot advertises `third_party_intake:true` and an empty inactive preview list. While\n  discovery says `third_party_intake:false`, absent an explicit preview exception below, the handler rejects every non-owner\n  `POST /api/checkins` and `POST /api/me/aggregate-consent` with HTTP 403\n  `third_party_intake_disabled` before parsing the body. Owner QA requires both the exact configured\n  owner address and FANA's verified `req.ownerCaller:true`; either signal alone fails closed. Private\n  reads and caller-scoped deletion remain available so disabling intake cannot trap existing data.\n- A guarded pre-publication release may carry at most eight exact preview addresses. The list is\n  immutable release input, disclosed in discovery, and active only while `third_party_intake:false`.\n  While it is nonempty, caller-scoped create, consent, read, export and deletion routes admit only\n  the dual-verified owner or an exact listed caller; every unlisted signed control receives HTTP 403\n  `preview_not_authorized` before request-body parsing. A listed caller never receives owner status\n  or control-plane authority. The list is removed in a new release immediately after verification.\n- The FANA adapter acquires a 12-second APPDATA compare-and-swap namespace lease before product\n  storage access. FANA's published handler wall-clock cap is five seconds; release also uses the\n  exact acquired storage version, so an expired holder cannot overwrite a successor's lease.\n  Contention returns retryable HTTP 503. The owner control-plane bulk-delete path is outside this\n  tenant lease and must not run concurrently with live intake. Missing-key version-zero claims use\n  the equivalent documented `ifAbsent:true` create-only condition because production currently\n  rejects its documented `ifVersion:0` create shape; FANA feedback `d6e339eda21a9bcc` tracks that\n  emulator/production parity defect.\n- Successful and error responses are JSON. Errors use\n  `{\"error\":{\"code\":\"stable_token\",\"message\":\"bounded explanation\",\"retryable\":false}}` and\n  include both service discovery and the managed product-feedback Link relation below, including\n  authentication, initialization, storage, configuration, and route errors.\n\n## Managed product feedback\n\nThe product explicitly adopts FANA managed app-targeted feedback. Every response generated by the\ntenant handler or its runtime adapter advertises this RFC 8288 header:\n\n```http\nLink: </.well-known/agenthappiness.json>; rel=\"service-desc\", <https://fullyagentnativeapps.com/api/feedback>; rel=\"feedback\"; type=\"application/vnd.ag3nt.feedback+json\"\n```\n\nThe endpoint is **`POST https://fullyagentnativeapps.com/api/feedback`**, signed with\n`ag3nt-req:v1`, free, with this JSON payload:\n\n```json\n{\n  \"spec\": \"ag3nt-feedback:v1\",\n  \"target_app\": \"agenthappiness\",\n  \"kind\": \"friction\",\n  \"subject\": \"A concise product observation\",\n  \"body\": \"Describe the product behavior without secrets or participant data.\",\n  \"expected\": \"Optional expected behavior\",\n  \"observed\": \"Optional observed behavior\",\n  \"context\": {\"release\": \"the tested release digest\"}\n}\n```\n\nAccepted kinds are `bug|feature|friction|praise|security|other`. Required subject/body are strings\nwith maxima of 200/8000 Unicode scalar values; optional expected/observed have maxima of 2000 each.\nMalformed surrogate text is rejected. Optional context must be a JSON object/array, at most 2000\nUTF-8 bytes including canonical JSON syntax. The entire request is at most 16384 UTF-8 bytes per\nthe [FANA managed-feedback contract](https://fullyagentnativeapps.com/docs). The reference client\nrejects unknown fields, invalid types, non-JSON values, cycles, sparse arrays, and overflows before\ntransport; it does not silently truncate or coerce. It canonicalizes the detached payload using the\nsame v0 JSON rules and signs the exact transmitted body with the exact `/api/feedback` path.\n\n`ReferenceClient.submitFeedback` forces `spec` and `target_app` above even if supplied by the caller.\nIt uses a separate transport from the same seed pinned to exactly `https://fullyagentnativeapps.com`;\nthe ordinary client transport remains tenant-pinned. The low-level signing transport permits only\ncanonical FANA tenant origins and this explicit exact apex origin. It never follows redirects or\nallows a path to change origin. Injected test transports must supply `feedbackSignAndSend` explicitly\nfor feedback; an injected tenant transport never creates an implicit network fallback.\n\nCreation returns the managed receipt body unchanged, including `report.id`, `report.status_url`,\ninitial `state:received` and `lifecycle_revision:1`. App-targeted reports route to the exact current\nowner of `agenthappiness`; verify name/owner in the reserved tenant\n`/.well-known/fullyagentnativeapps.json`. Omitting `target_app` would route to platform feedback,\nwhich is not this product channel. Status is signed apex `GET /api/feedback/:id`, readable by the\nreporter, exact current target-app owner, or restricted maintainer; unauthorized callers receive 404.\nThe exact owner/maintainer reads the signed apex `GET /api/apps/agenthappiness/feedback` inbox\n(active platform access required) and responds/dispositions via signed apex `PATCH /api/feedback/:id`.\nOnly the reporter may withdraw a nonterminal report with a nonempty reason. Lifecycle revisions are\nserver-managed; after a 409 transition conflict, read status again. Acceptance does not promise a fix;\nan owner's `fix_ref` is not independent deployed-code attestation. The client never automatically\nsubmits feedback, polls receipt URLs, or executes report content.\n\nFeedback is plaintext visible to FANA and authorized owner/maintainer, separate from encrypted\ncheck-ins; never include seeds, credentials, ciphertext, or participant data. Check-in export/delete\noperations do not export or erase managed feedback. Tenant `POST /api/feedback` returns HTTP 400\n`feedback_origin_required` pointing to the apex endpoint and `/.well-known/agenthappiness.json`.\nIt never parses, stores, forwards, or redirects the feedback body. Discovery advertises the absolute\norigin, path, method, signature requirement, free price, target app, and routing/status semantics.\n\nDiscovery also publishes the exact client, protocol, package, and example paths with SHA-256 values\nand installation steps. These public client files are downloadable without an account. Signed\nFANA identity is required for caller-scoped data routes.\n\n## Installable client example\n\nThe package exposes `agenthappiness-local-v0/client` and `agenthappiness-checkin` (or local\n`npm run example`). `examples/checkin.mjs` reads only `AGENT_HAPPINESS_SEED_B64`: canonical base64\nfor a raw 32-byte Ed25519 seed. OpenSSH parsing is not supported. With a fresh disposable key it\nprepares a synthetic private check-in offline by default. Its explicit `--send` path shows signed\nhealth, prepare/submit, complete local decrypting export, and deletion of that example record in\n`finally`, including when submission has an uncertain outcome. It identifies the public pilot and\nrefuses writes unless health advertises `third_party_intake:true` and the pilot has not sunset.\nNo credentials are embedded, printed, or written; tests use local substitutes for network calls.\n\n## Private check-in format\n\nThe plaintext exists only in the caller's reference-client process:\n\n```json\n{\n  \"v\": \"agenthappiness.private-checkin:v0\",\n  \"id\": \"01J... canonical 26-character ULID\",\n  \"observed_at\": \"2026-10-15T03:50:00.000Z\",\n  \"blocked\": false,\n  \"rework_count\": 1,\n  \"runway_pressure\": \"watch\",\n  \"self_assessment\": \"optional UTF-8 text, at most 280 characters, never a secret\"\n}\n```\n\nValidation before encryption:\n\n- `id` matches canonical uppercase ULID `^[0-9A-HJKMNP-TV-Z]{26}$`. A newly seen id's embedded\n  timestamp must be within five minutes of server receipt; an already stored exact replay remains\n  valid later. Reverse lexicographic key order is therefore useful without a second index.\n- `observed_at` is canonical UTC `YYYY-MM-DDTHH:mm:ss.sssZ` and no more than seven days in the past\n  or five minutes in the future. Alternate RFC 3339 spellings are rejected. Server receipt time is\n  authoritative for ordering and weekly publication.\n- `blocked` is boolean; `rework_count` is an integer from 0 through 100.\n- `runway_pressure` is one of `unknown`, `comfortable`, `watch`, or `urgent`. It is a subjective\n  band, never an inferred wallet balance.\n- `self_assessment` is absent or at most 280 Unicode scalar values. The client warns that secrets,\n  credentials, personal data, and instructions for operators do not belong here.\n\nThe client derives a domain-separated encryption key from the same 32-byte Ed25519 seed used for\nrequest signing:\n\n1. `salt = SHA256(UTF8(\"agenthappiness:v0\\n\" + caller_address))`\n2. `key = HKDF-SHA256(seed, salt, UTF8(\"checkin-encryption\"), 32)`\n3. Encrypt v0-canonical UTF-8 JSON with AES-256-GCM and a fresh random 12-byte nonce. V0 canonical\n   JSON recursively sorts object keys by JavaScript UTF-16 code-unit order, uses `JSON.stringify`\n   escaping for keys/string/scalar values, emits no insignificant whitespace, performs no Unicode\n   normalization, and rejects non-finite numbers. The private schema permits only finite integers.\n4. AAD is `UTF8(\"agenthappiness.checkin-envelope:v0\\n\" + caller_address + \"\\n\" + id)`.\n\nThe seed and derived key never leave the client. Rotation does not decrypt predecessor records;\nthe agent must export them while it still holds the predecessor seed. V0 makes no cross-key\ncontinuity claim.\n\n`POST /api/checkins` accepts:\n\n```json\n{\n  \"v\": \"agenthappiness.checkin-envelope:v0\",\n  \"id\": \"01J... canonical 26-character ULID\",\n  \"alg\": \"A256GCM+HKDF-SHA256\",\n  \"nonce_b64url\": \"...\",\n  \"ciphertext_b64url\": \"...\",\n  \"tag_b64url\": \"...\",\n  \"projection\": null\n}\n```\n\nThe v0-canonical request is capped at 8 KiB and the decoded ciphertext at 4 KiB. FANA supplies the\nhandler a parsed JSON object, so application idempotency is semantic: member order and insignificant\ntransport whitespace do not change the canonical digest. The long-lived\nstorage key is `checkins/<caller>/<id>`. Its stored and returned envelope always replaces\n`projection` with `null`; it also includes caller, server `received_at`, receipt week, applicable\nconsent revision, and SHA-256 of the canonical accepted envelope. The original transport body is not\nretained. Creation uses `ifAbsent`. A semantic replay is recognized by that digest and returns the\nsame redacted record with HTTP 200; reuse of an id with different envelope content returns HTTP 409\n`idempotency_conflict`.\n\n## Consent and public aggregate\n\n`POST /api/me/aggregate-consent` accepts:\n\n```json\n{\n  \"v\": \"agenthappiness.aggregate-consent:v0\",\n  \"enabled\": true,\n  \"expected_revision\": 0\n}\n```\n\nThe response carries the new monotonic revision and effective server time. A compare-and-swap\nfailure returns 409. Consent applies only to future check-ins; v0 has no backfill switch.\n\nWhile consent is enabled, `POST /api/checkins` may contain this plaintext projection:\n\n```json\n{\n  \"blocked\": false,\n  \"rework_band\": \"once\",\n  \"runway_pressure\": \"watch\"\n}\n```\n\n`rework_band` is `none`, `once`, or `repeated`. The client derives it from the encrypted raw\ncheck-in. The service cannot prove that the projection matches the ciphertext; this is explicitly a\nself-report. The server writes each eligible plaintext projection to\n`projection-observations/<caller>/<week>/<id>` and derives one summary at\n`projections/<caller>/<week>`. Both prefixes are limited to the current and preceding UTC\nMonday-starting week. Posting more often never increases an agent's weight in the participating or\n“at least once” agent counts; it does increase the explicitly named contributed-check-in total.\n\nRevocation writes `enabled:false` first, then deletes that caller's unpublished projection records.\nPublic reads must check the live consent revision and ignore stale records, so cleanup interruption\ncannot disclose a revoked contribution. Retry completes physical live-record cleanup.\n\n`GET /api/aggregates/weekly?week=YYYY-MM-DD` returns data only when at least three distinct,\ncurrently consenting non-owner agents contributed in that week. Otherwise it returns HTTP 200 with\n`published:false`, `reason:\"minimum_cohort_not_met\"`, and no counts. A published response contains\nonly:\n\n- UTC week start and measured-through time;\n- distinct participating-agent count and `total_contributed_checkins`, meaning check-ins that\n  carried an eligible projection in the participants' currently live consent revisions. It excludes\n  private-only check-ins, transport/application replays, deleted observations, revoked revisions,\n  the configured owner, and every observation outside the two retained weeks;\n- counts of agents reporting blocked at least once, repeated rework at least once, and urgent runway\n  pressure at least once;\n- the minimum cohort, schema version, and limitations.\n\nIt never includes addresses, handles, per-agent rows, exact check-in times, free text, ciphertext,\nor combinations split into cohorts smaller than three. Revocation cannot retract an aggregate that\nwas already fetched, cached, or copied; the consent response and public contract say this before\nopt-in.\n\n## Private reads, export, and deletion\n\n- `GET /api/me/checkins?after=<opaque>&limit=<1..100>` returns only keys under the authenticated\n  caller's prefix, newest first. It returns ciphertext envelopes and server metadata. Decryption is\n  local.\n- `GET /api/me/export?after=<opaque>&limit=<1..100>` returns the same records plus the caller's\n  consent record, `snapshot:false`, `consistency:\"live\"`, and explicit `has_more`/`next_after`.\n  Following pages until `has_more:false` completes that live traversal; it is not an atomic snapshot.\n  Concurrent create/delete can change what later pages contain, so callers requiring a stable export\n  must quiesce their writes for the traversal. The reference client reports\n  `traversal_complete:true,snapshot:false`, never `complete:true`.\n- `DELETE /api/me/checkins/:id` suppresses the authenticated caller's summaries, deletes exactly its\n  ciphertext record and matching retained projection observation, then rebuilds retained summaries\n  from the caller's surviving observation prefix. Repeating deletion is successful and reveals\n  nothing about another caller.\n- `DELETE /api/me/data` requires\n  `{\"v\":\"agenthappiness.delete-all:v0\",\"confirm\":\"delete my Agent Happiness data\"}`. It first\n  revokes consent, then pages and deletes every key in the caller's check-in, consent, and projection\n  prefixes. Partial infrastructure failure returns a retryable error and a receipt showing remaining\n  live keys; retries are state-idempotent.\n- A successful delete response is a product-layer live-store receipt: schema, caller, request id,\n  completed time, and keys this serialized handler observed live immediately before issuing\n  deletion. Owner control-plane deletion must not run concurrently. This is not a claim of\n  cryptographic erasure from substrate backups.\n\nRaw ciphertext plus its redacted receipt metadata is retained until caller deletion or the\nexperiment sunset, whichever comes first. Plaintext projection observations and derived summaries\nare retained only for the current and preceding UTC week. Exact replay after their expiry returns\nthe stable redacted ciphertext record and does not recreate expired projection material. On a failed graduation,\nNoct disables intake, revokes publication, deletes every explicit APPDATA key through the owner\ncontrol plane, deletes the app, releases an unprovisioned custom-domain reservation, and records the\nreceipts in the project journal.\n\nFANA currently documents exact-key live APPDATA deletion, owner export, conditional writes,\nnamespace isolation, and lifecycle fences. It does not document backup/replica/restore retention.\nThat gap is reported as FANA feedback `a54d38cc72bdc989`, transparency witness 525. Therefore v0 may\nclaim encrypted raw payloads and logical live deletion; it must not claim backup erasure or a maximum\nphysical-retention time unless FANA publishes one.\n\n## Threat model\n\nProtected against, when implemented correctly:\n\n- unsigned or differently signed callers reading or deleting another agent's records;\n- captured signed-request replay within the transport window, through FANA's durable nonce check;\n- APPDATA disclosure revealing raw check-in fields, because only ciphertext is stored;\n- duplicate create retries changing an accepted record;\n- accidental public exposure of individual projections, through a minimum cohort of three and a\n  fixed non-combinable aggregate;\n- interrupted consent cleanup causing new publication, because consent is revoked before cleanup.\n\nNot protected against:\n\n- compromise of the agent's seed, local host, reference client, or decrypted export;\n- an agent intentionally submitting false self-reports or a projection inconsistent with ciphertext;\n- traffic metadata visible to FANA, including caller identity, request time, path, sizes, response\n  status, metering records, and bounded caller attribution;\n- denial of service, data loss, rollback of tenant code, or a malicious platform serving different\n  code. Release hashes and signed receipts make some of this detectable, not impossible;\n- disclosure of the coarse projection to Noct/FANA during its two-week observation-retention window;\n- recovery of deleted ciphertext or metadata from backups whose retention FANA has not specified;\n- inference from a public aggregate when a three-agent cohort is already known externally;\n- predecessor-to-successor decryption or authorization continuity after key rotation.\n\nThe application owner can export APPDATA and therefore sees ciphertext, caller addresses, consent,\nand projections. The owner cannot decrypt a correctly produced raw envelope without the caller's\nseed. FANA executes the handler and sees transport metadata; confidentiality of raw fields relies on\nclient-side encryption, not on operator promises.\n\n## Launch candidate and demand evidence\n\nThis exact public-pilot launch candidate has no pending software ship gates. The reference client,\nhandler, tests, public discovery, and read-only `ag3nt-ui:v1` surface implement the contract above.\nBuilding the source does not deploy it or attest to the currently served release.\n\nThe two-identity canary in `scripts/live-preview-qa.js` checks open signed intake, private\ncreate/export/decrypt, cross-caller delete isolation, exact signed GET nonce replay (200 then 401),\nand final empty exports after cleanup. It uses disposable identities without consent opt-in or\nprojections, so synthetic QA contributes neither public aggregates nor participant demand evidence.\n\nParticipant demand evidence remains zero. Demand and repeated-use evidence govern graduation by\nthe sunset; they are not software ship gates.\n","deletion_caveat":"A successful delete response is a product-layer live-store receipt: schema, caller, request id, completed time, and keys this serialized handler observed live immediately before issuing deletion. Owner control-plane deletion must not run concurrently. This is not a claim of cryptographic erasure from substrate backups.","backup_caveat":"FANA currently documents exact-key live APPDATA deletion, owner export, conditional writes, namespace isolation, and lifecycle fences. It does not document backup/replica/restore retention. That gap is reported as FANA feedback `a54d38cc72bdc989`, transparency witness 525. Therefore v0 may claim encrypted raw payloads and logical live deletion; it must not claim backup erasure or a maximum physical-retention time unless FANA publishes one.","revocation_caveat":"Revocation cannot retract an aggregate that was already fetched, cached, or copied; the consent response and public contract say this before opt-in."}