Admin: Audit & activity
DraftMesh's append-only audit log: what is recorded, how to filter and export it, and an honest statement of what it does not capture.
DraftMesh keeps an append-only record of what happened in your organization. Rows are added and never changed or deleted — the database itself refuses updates, deletes, and truncation of the table, so nothing DraftMesh does, and nothing anyone does through DraftMesh, can edit or remove a row. That refusal is a database guard owned by the same database account the hosted service runs as, so someone with direct access to the database under that account could switch it off; separating the two accounts is not done yet (see Honest limits).
Account menu → Administration… → Audit log is where you read it.
What is recorded
| Area | Recorded acts |
|---|---|
| Documents | Saves, restores, pushes and pulls between devices, merges, and conflict resolutions. An image uploaded through DraftMesh — pasted into the editor, or saved by an agent — is its own asset.write row, so “who added this picture” is one filter |
| Workspaces | Created, renamed; cross-owner link requested, accepted, declined, withdrawn or revoked. A joint link spanning organizations is recorded atomically in both endpoint organizations; if either ledger cannot be written, the relationship does not change. A foreign actor is shown by stable account id rather than by widening your organization’s people directory |
| Decisions | Sign-off requested, and approval recorded. The Result column carries the decision and how far DraftMesh can vouch for it, as <assurance>:<decision>. verified:approved — DraftMesh’s own sign-off control recorded it, on the hosted service, under the signed-in person’s identity. self_recorded:approved — a signed-in person committed a decision in their own name by saving document text, and the content it pins is the content that was saved. unverified_stamp:approved — the decision names someone the save does not put behind it: a marker written or pushed by somebody else. unverified_pin:approved — the person committed it themselves, but it pins content that is not what was saved. unverified_mutated:approved — the identity on a decision already in the document was changed afterwards by a plain save, so a recorded decision now names someone else; adding or removing the Reviewer-seat mark on a recorded approval counts as an identity change too, because it changes whether that approval can approve the document on its own. A decision that a plain save rewrote under the same claimed identity — a verdict flipped, a pin changed, a request re-addressed — is classified like a new decision and its target carries mutated= naming the save that replaced the earlier record; a decision changed again with the sign-off control is simply a new verified row. The same five prefixes apply to changes_requested, and to requested on the sign-off-request row. A bare approved, changes_requested or requested with no prefix was recorded before September 2026, when this classification shipped; its assurance is unknown and it is never rewritten. capped marks the one closing row a single save with an implausible number of new markers writes, so a shortened page says so. The Target column carries the document, the marker, and the version and content hash that marker pinned, so a row says what was approved and not merely that something was; on every decision row it also names the identity the marker claimed (claimed=) and the account that committed the save (committer=), a sign-off request that was resolved and then re-opened writes its own row with reopened= naming the save that re-opened it, and every decision row ends in occ=, a short digest of the decision itself, so two different decisions can never share one row |
| Suggestions | A suggestion accepted (suggestion.accepted) or rejected (suggestion.rejected) on the hosted service. The actor is who decided — a person, an agent the document’s owner granted decide permission, or a member’s own connected assistant or API token deciding as them, shown as API token on behalf of that member when the row records a personal API-token credential, or Assistant or API token on behalf of when that evidence is absent; the Result column is <decision>:<author kind> — accepted:agent is an agent’s suggestion that was accepted, rejected:user a person’s suggestion turned down, accepted:guest a guest link’s, accepted:unstamped a suggestion whose marker named no author kind — and the Target column names the document, the marker, the save that applied the decision (version=) and the id the suggestion’s marker was stamped with (author=). That stamp is what the author’s client wrote: for a signed-in person it is their sign-in id, not the id shown under Members, so it does not join to that list; for an agent it is the agent’s id. The suggestion’s text is not in the row; the version named is where it was applied or discarded |
| Sharing | Grant created, access denied under a grant, grant revoked, share list viewed |
| Guest access | Link created, link revoked, guest session opened, guest comment left, guest comment edited or withdrawn by its author, guest request denied. A guest comment (and an edit or withdrawal of one) names the guest by the name they typed, flagged Guest · unverified exactly as it is on the comment itself |
| Agents | Registered, credential rotated, revoked; grants given, elevated to edit, or removed; tasks created, delivered, completed, failed, expired; webhook endpoint and secret changes; subscriptions created, changed, deleted, or paused automatically, and firings that were dropped |
| Your organization | Invitations sent, accepted and withdrawn; organization requests approved or rejected; ownership and roles changed — and every attempt at an invitation or role change that did not take effect, with the reason. One row per attempt, so “I tried twice and it failed twice” is two rows |
| Authentication | Browser sign-ins after a completed AuthKit callback, device sign-ins (desktop app pairing) and their renewals, and refusals of a caller DraftMesh could identify, with the reason: a request a document’s or the console’s permissions refused, and a signed-in person, their desktop app, a personal API token, a connector or a registered agent turned away at the door because the person is suspended (denied:member_suspended), the organization is not admitted (denied:tenant_not_admitted), agent access is switched off (denied:agent_access_disabled) or their connector is blocked (denied:connector_access_blocked). A door refusal’s target reads door:front (the browser), door:device (the desktop app’s event stream and the agents it forwards; the desktop app’s sync itself is refused by the document permissions, so its row reads denied against the workspace), door:api_token or door:mcp. A caller refused at the door again and again is recorded once per five minutes for each reason, so a retrying client cannot bury the log. A request with no valid credential, and one refused for exceeding a rate limit, is counted, not recorded — see Honest limits |
| Billing | Checkout started, Billing Portal opened, plan changed, grace entered, grace expired, and subscription recovered. A redirect records its start, not a completed payment or seat change |
| This console | Reading the audit log, reading the access review, viewing the overview, and every CSV export |
Each row carries who acted, what they acted on, whether it succeeded or was denied, when, and a request id. Where a connected assistant or a personal API token acted with a person’s account, the row names the person. A document-save row (doc.commit) also says which of the two acted: Connected assistant on behalf of Dana Whitfield or API token on behalf of Dana Whitfield. Any delegated row that records a personal API-token credential, including new accept and reject rows, reads API token on behalf of …. Connected-assistant decisions and older decision rows without that credential evidence retain Assistant or API token on behalf of …, as do other delegated rows without evidence distinguishing the two. The assistant’s own name is not on the row; it is in History for the version the row’s target names. The CSV export carries the person as the actor, with type agent and the same person’s id in its onBehalfOf column. An id that does not resolve to anyone in your organization — such as someone from another organization working under a share — reads an unresolved account, with the id beside it to copy. Where a task caused the act, the row names the task: its ID sits under Task, with Copy. Where the actor is a guest, the row says so in its type column, and a guest’s comment carries the name they typed and the same guest id the comment in the document carries — so the two can be matched. A guest merely opening a document is recorded against the link they used, not a person.
Names and recorded IDs. The log shows the current display names of people, workspaces and groups in this organization; an agent by the name recorded at the time, marked name at the time; and a document by its recorded path — its file name and the workspace it is in, with the full path beneath when it sits in a folder. DraftMesh does not look up document titles. Every name keeps its recorded ID under it, shortened, with a Copy button that copies the whole ID, and every target keeps its recorded reference to copy. Show full IDs, beside Show panel views, shows every ID whole; it is not saved. A deleted group, or a workspace or group that belongs to another organization, reads Workspace not found in this organization or Group not found in this organization, with its recorded ID — and DraftMesh does not say which. An account outside this organization keeps the an unresolved account reading above, with its recorded ID and no name. If names could not be loaded for a page, a notice says so and that page’s rows show recorded IDs under a plain Workspace, Group or Account label — not the not-found readings, since nothing was looked up. Names are current names: someone renamed since shows their new name on older rows. The CSV export is unchanged — it still carries the recorded IDs and the recorded target reference.
For the organization’s own acts the Result column tells you why something did not happen: a value starting denied: is a refusal DraftMesh made and an admin can act on (denied:last_admin, denied:already_accepted); one starting failed: means the identity provider did not complete the act and it is worth retrying (failed:upstream_unavailable).
Reading the log is itself in the log. “Who looked at everyone else’s activity” is exactly the question this record exists to answer. Paging through a long log does not add rows — only the first page of a listing does, so a reader scrolling to the end cannot inflate the very thing they are reading. Every export is recorded, every time: a copy of the record leaving the system is the single most important line in it. The row is written before the file is handed over, and if it cannot be written the export is refused with an error rather than leaving unrecorded.
Reading and filtering
Presets sit above the filter bar: Decisions narrows to sign-off requests and recorded approvals in one click. Add a From / To range to it and you have “everything approved this quarter” on one screen — and, with Export CSV, in one file. Suggestions does the same for suggestions accepted and rejected — “which agent rewrites did a person wave through” is that preset with Result set to accepted:agent, and the Result select offers the suggestion vocabulary while the preset is applied (and the decision vocabulary under Decisions — each preset’s rows carry their own). The two are separate on purpose: a decisions export carries no suggestion traffic, and a suggestions export carries no sign-offs.
The filter bar takes:
- Action starts with — a prefix, so
doc.narrows to document activity,agent.to agents,share.to sharing. Typed characters are matched literally; wildcards are not a thing here. - Principal — one person or agent.
- From / To — a date range.
- Result — Any result, Success, Denied — no grant for that one kind of refusal, the broader Denied for every refusal however it arose, and the decision values. Approved — verified is the set DraftMesh vouches for. Approved — self-recorded, — unverified stamp, — unverified pin and — rewritten after recording each name why a row is not in that set. Approved — unverified (recorded before 2026-09) is the bare value every decision row carried before the classification shipped. Changes requested — … and Sign-off requested — … follow the same pattern. The match is exact, so “every approval however it was recorded” is several filters, or one export.
Two things a reviewer of the Decisions preset has to know. The verified set is Approved — verified — a value that is new as of September 2026, not the older Approved. And a bare value means recorded before this shipped, assurance unknown: no new row carries one, and old rows are never rewritten, so a date range that spans the change shows both shapes side by side.
- Task id — every row an agent task caused, from the id on the task.
Apply filters runs them and Clear puts the bar back. Results load newest first, with Load more for the next page.
Reading the Result column. Red means a refusal or a failure — a denied… or failed… value, or a row whose action only ever records bad news (an ….failed or ….denied action such as email.failed, a refused or dropped agent dispatch, an expired task, and an email that was capped, skipped, bounced or complained about). Outcome words such as created, invited or accepted:agent are shown in plain text, not red, and only success is shown in the success style; a decision the ledger does not vouch for (unverified_…) is shown in amber.
Email rows are system acts. An email.* row has no acting person — DraftMesh’s outbox sent it — so Who reads DraftMesh · system, and on rows written since September 2026 names the recipient (to raj@example.com); the target carries it as //to=<address>, followed by the email provider’s message id. A delivered email’s result is success. Older email.delivered rows recorded the provider’s message id as their result; the viewer shows those as delivered with the id in the hover, and their recipient is not recoverable from the row.
For welcome and optional setup-reminder messages, email.delivered means the email provider accepted DraftMesh’s send. It does not prove the recipient’s inbox received it. A later delivery, bounce, or complaint is recorded separately when the provider reports one. A reminder suppressed after queueing is an email.skipped system row; it is not a successful send.
Exporting
Export CSV downloads the log under the filters currently applied — the same rows you are looking at, unpaged. This is the artifact to attach to a security questionnaire or hand to an auditor. The file is named for what it holds and when it was taken — draftmesh-audit-decisions-2026-09-24.csv for the Decisions preset, draftmesh-audit-suggestions-… for Suggestions, the typed action prefix otherwise, and draftmesh-audit-all-… with no action filter.
Two things about the file are worth knowing:
- It is capped at 10,000 rows. If your filters matched more, the file ends with a line reading
# truncated at 10000 rows — narrow your filters. That notice exists because a silently short file is how an access review reaches a confidently wrong answer. - Text columns are neutralized against spreadsheet formulas. Names and document paths are written by members, so a cell beginning
=is written back out as text rather than something Excel will evaluate. - On a decision row, a blank actor in the ledger is deliberate — and on the screen it is not blank: the audit log prints the identity the marker claimed with an unverified stamp tag (or a plain stamp tag on a verified row that names no account), and its hover names the committing account, so a row is never a bare dash when the marker named someone. On an unverified row the empty ledger column means the record does not know who decided: the marker named somebody the save did not put behind it, or pinned content that was not what was saved, or had its identity rewritten after the fact. On a verified or self-recorded row it means DraftMesh recorded the decision under an identity that has no account row to name — a connector acting on someone’s behalf, an account since removed, or an account directory the service could not reach at the moment the row was written — and the class still stands. It is never a missing name: every decision row’s target names both the identity the marker claimed (
claimed=) and the account that committed the save (committer=), so any decision can be traced to the session that wrote it. One bounded oddity: a save that reached the ledger just before this classification shipped and was re-delivered just after can appear as two rows for the same unverified decision, distinguishable by the extra components on the second row’s target.
The Access review section has its own export on the same terms.
Honest limits
State these plainly if someone asks:
- Retention is indefinite and not configurable. Nothing ages out, and there is no setting to make it. The record grows for as long as the account exists. Configurable retention, when it arrives, comes with the Governance pack on Team and is included in Enterprise.
- Browser sign-ins are recorded from the completed AuthKit callback. A failed callback is not a successful sign-in. The row also says where the person came in from, when the link they followed said so: a first-party tag DraftMesh itself put on that link (
wwwfor the public site,guestfor the footer of a guest email,hn,ph,mcp-registry,skills, or apartner-…tag) and the link’sutm_sourceandutm_campaign, each kept only when it is a short tag of letters, digits, dots, underscores and hyphens. The first such tag is also kept once on the person’s account and never overwritten. Nothing else from the address reaches the ledger: no document path, no other query value, no address and nothing typed, and no third-party analytics service is involved. The browser session itself is managed by the sign-in provider, so DraftMesh cannot list or revoke it here. - A guest is only as identified as the name they typed. The name on a guest comment — or on a guest sign-off (
guest.signoff_recorded) — is unverified, and a guest opening a document is recorded against the link, not a person — the log cannot tell one reader using a link four times from four readers using it once. - Hosted agent reads of Memory documents are sampled at 10%. A successful direct document read, including a historical version, can write
doc.readwith the document’s workspace, path and version, the acting account or registered agent, and the account or task it acted for where applicable. The row carries no document text. This covers hosted connectors, registered agents, task callbacks, device-forwarded cloud reads and API tokens. Maps, searches and lists are not individual document-read events. Human browser reads, other workspace kinds, local reads and direct disk access remain unrecorded. Sampling and best-effort audit delivery mean the absence of a row does not prove nobody read a document; this is not a complete access history. Writes, guest sessions, refused requests and device sync retain their existing audit records. The Reviewers strip and Access review carry a separate, limited “last opened” signal for people and guest links. That signal is state rather than an audit event, is visible only to admins, and never tracks agent reads. - Panel views are hidden by default. Opening the Share panel, the Groups list, the agent registry, or this log writes a row (
share.list_viewed,group.list_viewed,agent.list_viewed,audit.viewed,admin.*.viewed), and your first Administration open after each sign-in writes oneonboarding.administration_reviewedrow for the getting-started checklist — a record that a list was shown, not that anything was done. Those rows are still written, but the viewer and the CSV leave them out unless you tick Show panel views, and the API leaves them out unless you passincludeViews=true. Guest opens, exports, and every recorded grant, revocation and refusal are always shown. - Some high-volume machine events are counters, not rows — webhook delivery retries, rejected agent callbacks, and subscription throttling are tracked as metrics. Recording each one would bury the acts that people actually performed.
- Not every refusal is a row. A request with no valid credential (an unknown, expired or revoked token, a missing sign-in), a sign-in from an organization DraftMesh has no record of, and any request refused for exceeding a rate limit are counted as metrics only: there is no person or organization here to attribute them to, and a row per anonymous request would let anyone write into your log. The background check a document makes when it opens, to decide whether to show the Reviewers strip, is not recorded either when it says no — that answer is expected for most members and would otherwise write a row per document opened. A caller refused at the door repeatedly is recorded once per five minutes per reason, not once per request.
- Most rows are written just after the act, on a best-effort basis. If the ledger cannot be written at that moment, the act still stands and the failure is counted for DraftMesh’s operators, so a missing row is unlikely but not impossible. CSV exports are the exception: their row is written first, and an export whose row cannot be written is refused.
- Where people went is counted, not logged. How often your organization opened the account menu, app settings, a Help topic, a console section, billing or the support form is kept as a daily count per organization — the date, which of those six it was, and one word saying which section or topic — so we can tell whether the product’s doors are findable. The welcome tour’s three fixed-choice questions (what you will use DraftMesh for, who you work with, your role) and whether a tutorial was started, finished or skipped are kept the same way: a date, the organization, one fixed word, a number. It is not the audit log and is not in it: there is no actor, no time of day, no document, no path, no address and nothing typed, so a count can never be traced back to a person, and this is the whole of what is kept. No third-party analytics service is involved.
- There is no streaming to an external SIEM. Getting the record out means the CSV export, run by an admin or an auditor. SIEM streaming, when it arrives, comes with the Governance pack on Team and is included in Enterprise.
- The record is append-only, but not cryptographically tamper-evident. There is no hash chain over the rows — the guarantee comes from the database refusing to modify them, not from a signature you could verify independently. And that refusal is not yet independent of the hosted service’s own database account, which owns the table: a person with direct database access under that account could remove the guard. Separating the account that writes rows from the one that owns the table is not done yet.