event: the published contract says what the door does
CI/CD / image (push) Successful in 23m20s
CI/CD / gate (push) Successful in 1m16s
CI/CD / containment (push) Successful in 2m39s
Hanzo CI/CD / cicd (push) Successful in 1m16s
CI/CD / rollout (push) Failing after 7s
CI/CD / reach (push) Skipped
CI/CD / fanout (push) Skipped
CI/CD / receipt (push) Failing after 2s

The door description still promised "NO CREDENTIAL IS ALSO ADMITTED ... filed
under the reserved $public tenant" — and it goes into the OpenAPI document
customers read. The code has refused since the key work landed: 401
ingest_key_required with nothing presented, 403 ingest_key_unknown for a
credential that names no project. A contract that promises the opposite of the
code is worse than no contract, because a client writes against it.

The projection survives for the reduced principal it was narrowed for — a
workspace token writing into its own org.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
2026-08-04 12:49:29 -07:00
parent f345ac70ae
commit a4a1f99852
+18 -21
View File
@@ -852,30 +852,27 @@ var doors = []door{
"back always takes a real bearer. A Hanzo Team workspace token resolves its org at " +
"REDUCED capability: the signed " +
"account names the person, so a `distinctId` in the body cannot pin events on a colleague.\n\n" +
"NO CREDENTIAL IS ALSO ADMITTED, and that is the point — a logged-out visitor has none. " +
"Such a write is PROJECTED: filed under the reserved `$public` tenant, narrowed to what the " +
"SERVER can name — pageviews and errors, plus the closed autocapture vocabulary ($click, " +
"$input, $change, $submit, $view) — where EVERY one of those names is resolved through a " +
"server-owned table and stored as that table's value, so the name on the wire is never the " +
"name in the row. Stripped, too, to the fields the projection names, so revenue, personId, " +
"groupId and every property but the element annotation " +
"cannot reach a row — and an exception is carried only on an error, never on an " +
"interaction, so a click cannot ship a stack trace into a row's attributes. " +
"ITS IDENTITY IS NAMESPACED for the same reason the name is: nobody signed for it, so a " +
"`distinctId` off the wire is stored under a reserved `$anon:` prefix that no identified " +
"subject carries — an anonymous visitor still counts as one visitor, and still cannot be " +
"joined to a person the org knows. Everything refused is counted in `dropped`. On a " +
"published-site host " +
"the same projection applies with that site's org as the tenant. But a credential that IS " +
"presented and does NOT resolve is 403, never quietly downgraded: filing a misconfigured " +
"key's events under $public would hide them in a partition their owner cannot read.\n\n" +
"The anonymous lane alone is bounded: 413 over 64 KiB, 400 over 50 events, 429 on the " +
"NO CREDENTIAL IS REFUSED: a write the server cannot attribute to a project is 401 " +
"`ingest_key_required`, and a credential that IS presented but resolves to no project is " +
"403 `ingest_key_unknown`. Nothing is filed under a shared tenant — events nobody can " +
"read are worse than events nobody sent, because the caller is told it succeeded. A " +
"browser bundle therefore always ships a pk-, which is what /v1/event.js takes.\n\n" +
"A REDUCED principal — a Hanzo Team workspace token — writes through the PROJECTION into " +
"its own org: narrowed to what the SERVER can name (pageviews and errors, plus the closed " +
"autocapture vocabulary $click, $input, $change, $submit, $view), where every one of those " +
"names is resolved through a server-owned table and stored as that table's value, so the " +
"name on the wire is never the name in the row. Stripped, too, to the fields the projection " +
"names, so revenue, personId, groupId and every property but the element annotation cannot " +
"reach a row — and an exception is carried only on an error, never on an interaction, so a " +
"click cannot ship a stack trace into a row's attributes. It does NOT name the person: the " +
"signed account is the identity, so a `distinctId` in the body cannot pin events on a " +
"colleague. Everything refused is counted in `dropped`.\n\n" +
"The projected lane alone is bounded: 413 over 64 KiB, 400 over 50 events, 429 on the " +
"per-client-IP and per-peer caps, and a DNT:1 or Sec-GPC:1 request stores nothing and says " +
"so in the receipt. Two stored values carry their own bounds on top, because a request cap " +
"does not bound one value: an element annotation over 2 KiB (or a trail over 32 steps) and " +
"an exception class over 256 bytes are dropped from the row, which still lands. Where a " +
"deployment switches anonymous capture off, a credential-less " +
"write is 403 instead. Authenticated bodies are offered to the observability plane first, " +
"an exception class over 256 bytes are dropped from the row, which still lands. " +
"Authenticated bodies are offered to the observability plane first, " +
"which claims LLM-observability ingestion batches and declines everything else.",
},
}