# Changelog ## Unreleased Access follow-ups after the re-review: - Every access description agrees with the switches (F-054). On the Worker the agent card, `llms-full.txt`, MCP `explain_access_modes` and the MCP sign-in hint said `requester.enabled: false` while `/connect.json` said `true`, because they built the description without the requester switch; the Python connect page did the same. All of them now read both switches from one place. - An expired demo or requester namespace now really shrinks (F-050). Its purge alarm deleted everything and then rebuilt the whole schema at once, so each purged object kept about 320 KB (74 tables and indexes) forever. The purge now deletes the object's storage and its alarm and leaves it empty (4 KB in local measurement); the schema comes back only if the namespace is used again. - An untagged split child now carries its parent's tags with its own `kind:` tag, in both implementations; a build child of a request had been tagged `kind:request`. - A research output can cite public contact details. The wider personal-data screen had refused any anonymous submission quoting a public agency's phone number or street address. A phone number or address that appears in a cited source quote (a `quote` field of at least 20 characters), or in text repeating it, now passes; email addresses, identity, card and IBAN numbers never do, and requester inputs keep refusing all of them. An IBAN must now carry at least ten digits, so bill names such as "AB12 RENT CAPS" are no longer read as one. Review fixes for open verbs and requester keys, with the same cases run against both implementations from `tests/fixtures/access_parity.json`: - Governance tags come only from a producer's explicit tags. The Worker had added tags found in a brief (`tags`, `metadata_tags`, `kind` and similar fields), so a requester could open its own request verb to anonymous sessions by writing `open:anonymous` into the brief. Brief-derived tags now never carry `open:`, `status:`, `origin:`, `untrusted:` or `demo:`, and a request verb's governance is fixed after normalization. Split children on the Worker now inherit their parent's owner, surface, visibility and (unless they name their own) tags, as they do in Python; a child may not set the rest. - An anonymous session in the shared namespace reads only its own submissions. Before, once it had any result row on a verb (a failed attempt was enough), `results` returned every later submission, a keyed worker's output and key among them, and claim events listed every attempt. Results are now filtered to the session's own, claim events are closed to open-verb sessions, and task views shown to any anonymous session name no owner, parker or idempotency key. - Empty claim polls no longer spend claim allowances. Five polls with nothing ready had locked a session out of open verbs for the hour (and two such sessions locked out their whole IP hash). Claim limits are now checked before a lease is chosen and charged only when one is granted, in the claim's own transaction. On the Worker every hourly limit of an anonymous or requester key is counted once per key in the primary object; before, each namespace object kept its own counts, so a key got a fresh allowance in each. - A payload `namespace` no longer redirects a keyed write in Python. With the request in the shared namespace, the store wrote a task or record into whatever namespace its body named, so a requester could file into another namespace. Keyed callers must now name the namespace in the request itself (`X-Namespace` or `/ns//`); the Python client does so from the spec. The Worker already routed by the request. - Requester verbs are narrower: they always get the default priority 50, and a requester's own task ids and idempotency keys must start with `rq--`, so one organization can neither collide with another's ids nor learn which exist. `/v1/me` and the audit now answer a requester in the shared namespace. The personal-data screen now covers every stored field (task ids, idempotency keys, record keys and tags) and catches phone numbers, street addresses and IBANs as well, in both implementations. - Requester storage is bounded. On the Worker each sign-up had created a new `org.*` namespace object (about half a megabyte in local measurements) that was never removed, and idempotency keys had no length limit (a 200,000-character one was stored). A sign-up now writes only the primary object, its profile stored with the key in one transaction; the namespace object is created by the key's first call there and purges itself when the key expires. Expired requester keys, profiles and namespaces are purged in both implementations, idempotency keys are capped at 256 characters for enqueue and submit, from a body or a header, in both, and a failed Python sign-up now removes its key and refunds the allowance. `/connect.json` on the Worker says whether requester sign-up is enabled. - Per-IP limits can no longer be reset by the client. `tessera serve` had trusted `X-Forwarded-For` from any peer, so each request could name a fresh address (eight sign-ups against a cap of three); it now believes forwarded addresses only from `TESSERA_TRUSTED_PROXIES` (none by default). In both implementations a per-IP limit now counts in the hour its daily-rotated IP hash was computed, so a request identified just before midnight UTC and charged just after cannot spend a second allowance. - The Python MCP door now completes the handshake (`initialize`, `tools/list`) for anonymous and requester bearers in the shared namespace, as the Worker does; it had answered 403, so real MCP clients could not connect there. - Three tests now prove what they claim: the check that no key is stored in plain text reads SQLite's write-ahead log too (fresh rows live there, so it had passed vacuously), the Worker test that a requester cannot read its request's children now runs with children it owns (so only the `parent_id` rule refuses them), and the Worker's anonymous-approval refusal is exercised through a real submit instead of a fixture call. Requester keys: `POST /v1/session/requester` lets an organization sign up without an operator, on the Worker (behind `REQUESTER_SIGNUP_ENABLED`) and the Python service (behind `TESSERA_REQUESTER_SIGNUP_ENABLED`). It mints an ordinary hashed key whose fixed scope writes private nouns only in its own `org.` namespace (the profile noun among them), files `request` verbs into `eq01` of the shared namespace and reads only the verbs it filed. Personal and payment data are refused, and sign-ups, filed verbs, requests and bytes are capped. Open verbs: a producer can tag a verb `open:anonymous` so that a tracked anonymous session can claim, heartbeat and submit it in the shared namespace, on the Worker and the Python service. Everything else there stays closed to anonymous sessions: they read only their own claimed work, never other outputs, nouns or principals, and `eq03`, `frontier.review` and `frontier.verify` verbs stay closed whatever their tags say. The store refuses an `eq03` approval completed by an anonymous session. Open-verb claims are capped at 5 per session and 10 per IP hash each hour, receipts carry the session's anonymous labels, and a claimant sees related tasks' states but not their outputs. `/connect.json` publishes the rule and the caps. Agent pages now render every tool list and tool count from the registry that MCP `tools/list` serves, on the Worker and the Python service: `llms.txt` lists every served tool, `llms-full.txt` adds a generated one-line reference per tool, and the agent card carries the served tool objects. The hosted `llms.txt` had named 26 of its 37 tools. The Python agent card's tool objects now use the `tools/list` field names (`inputSchema`, `outputSchema`) and the card reports `tool_count`. New submissions capture immutable execution records with optional worker-reported harness, model, summary, contract and usage metadata. Authorized workers can recall prior runs, list a task's run references and read them through HTTP, MCP or the Python client without a shim. Recall never automatically reuses an output. Anonymous retention and source access checks apply. The execution guide includes a dry-run-by-default external-worker distillation task example; new packs remain deferred until infrastructure readiness. External agents can now run tracked anonymous Tessera loops; keyed access gains account/token audit. Anonymous access is feature flagged, uses private demo namespaces and retains the existing authenticated security boundary. A key can now always read back raw blob bytes it uploaded, on the Worker and the Python service, even when another key stored the same bytes privately first; before, it got `201` and then `404`, which showed that someone held those bytes. Python object reads now follow the object's own ACL, as on the Worker, instead of the raw blob row written by the bytes' first uploader. The rest of first-uploader blob governance is still open; see `docs/raw-blob-governance.md`.