Chapter 37. What Done Means, and Who Decides

Done is a claim about the world, and every claim in this estate has to survive being checked. I hold the result to four conditions: checked against the live surface, not the config meant to produce it; carrying a human name where a human name belongs; written somewhere the next agent finds it before it repeats the mistake; and reported without asking anyone for permission to care.

Nothing about that list is philosophy. Each condition exists because I skipped it once and paid.

Checked against the live surface, not the config

Until 2026-06-11, Suede Social served an empty server-rendered body on every route. Crawlers saw nothing. Every build passed, every deploy succeeded, every page looked correct in a browser, because the client rendered it after load. The cause was one line in frontend/src/components/theme-provider.tsx:

if (!mounted) return null;

A mount gate wrapped the entire app body. PR #290 (f6d2dd7) made children always SSR, moved the dark-default localStorage migration into a blocking inline script that runs before next-themes’ pre-paint script, and put suppressHydrationWarning on <html>. The standing rule I wrote afterward is a one-liner I now run against each domain after production deploys:

curl -s <domain> | grep '<h1'

The check costs a second. The condition it catches lasted months.

A month later, on 2026-07-16, agents.suedeai.ai gave me the same lesson in a different register. Vercel Production env vars DB_DRIVER and SUPABASE_* were pulling as empty strings. The code read them, found nothing, and fell back to a non-durable SQLite repository inside a serverless function without raising an error. Agent Studio’s Supabase runtime had never been implemented, though Phase 9 assumed DB_DRIVER=supabase in production. A same-day multi-lane audit found a false save/recovery conflict that auto-persisted a draft on page view, a disabled Run button, and live /api/v2/* endpoints returning 503. PR #71 covered visual and UX, PR #72 covered database security. Supabase config was restored, RLS hardened, the service-role key removed from Vercel, atomic RPCs added, and an end-to-end canary of create, bind, version, and run passed. PR #73 followed.

An env var that reads as an empty string does more damage than one that is missing, because the missing one throws. Dev runs SQLite, prod runs Supabase, and there is no local .env, so production behavior can only be established by hitting production. That is the whole rule: verify the datastore identity from the datastore, not from the intent recorded in a dashboard.

I also wrote a caveat into that memory the same day, and I stand by it. Given how much churn happened in one day, the state is worth an independent spot-check before anything downstream depends on it. Recording your own uncertainty is part of finishing.

Verification takes a specific shape when the thing being verified is access rather than output. Suede-AI/strumly is private and owned by the Suede-AI account. The proof that the four-step invitation flow worked is not the fact that the invitation was accepted:

gh repo view Suede-AI/strumly --json viewerPermission
# "WRITE"

Attributed to a person, not a bot account

In May 2026 a comment on solana-foundation/awesome-solana-ai#155 went out under Suede-AI, a deprecated bot account, because gh had that identity active in the keyring and nothing checked before the mutating command ran. The comment is permanent and public. On an external repository, in an ecosystem where the people reading are the people I want to work with.

The rule now: verify gh auth status shows the personal account before gh pr create, gh pr comment, gh issue create, gh issue comment, gh pr review, gh pr merge, or gh release create. Read-only commands can run under whichever account is active, because they stamp nothing. The asymmetry is the point. Mutating commands write an identity into a record I cannot retract, and the check costs one line.

For a solo founder, attribution is a distribution asset. Investors, hiring partners, and collaborators look at founder GitHub activity and read package metadata. A bot byline does not compound anything. So the split is explicit: Suede-AI appears in repository paths, and contributors, maintainers, and founders resolve to the human. The one place the org account is correct is the Org entity’s sameAs field, which is the inverse of the attribution rule and catches people who learned the first half.

Two traps sit inside this. The account was renamed from jasoncola1 to JasonColapietro on 2026-05-11, and gh’s local keyring kept displaying the old identifier afterward, which looks exactly like the wrong account being active. The OAuth token is bound to the account and resolves correctly; the cached display name lags. gh auth refresh -h github.com clears it, and until then gh auth switch --user jasoncola1 targets the same human account. Know which layer is authoritative or you will chase a non-bug for an hour. The second trap: when sweeping old-username references out of repos and docs, replace the username and leave jasoncola1@gmail.com alone. It is a Gmail address, a separate system from GitHub usernames, still the verified email on the renamed account, and rewriting it breaks commit attribution.

Attribution extends into the machine-readable layer, where it fails without any visible symptom. On agentix, founder SEO shipped with a freshly minted Person @id of /#person-jason-colapietro. The page rendered fine. It would not merge with the live entity graph, because graph consolidation keys on identical @id strings. Canonical values live in suede-home/src/lib/seo-entity.ts in Suede-AI/Suede-AI-App: Person https://suedeai.ai/founder#person, Org https://suedeai.ai/#organization, Website https://suedeai.ai/#website, JCIG https://jcinvestmentgroup.ventures/#organization. Satellites mirror those strings verbatim. The canon was backported into seo-entity.ts on 2026-06-11 (Suede-AI-App main 756f1205), and agentix now matches.

The same surface carried @suedeai in its social metadata, a handle that does not exist. Corrected on 2026-06-11 to site @AISUEDE and creator @johnnysuede. Next.js replaces the whole metadata object per segment, so a page-level twitter block overrides a correct root layout without warning. Verify the rendered metadata per route.

Built and dark does not count as shipped

A feature sitting behind a disabled flag is code, not a product. It has no users, no error rate, and no evidence. When I write it up as shipped, I have corrupted the only record I have of what the estate does, and the corruption compounds: the next agent reads the memory, believes the capability exists, and builds on top of nothing. Agent Studio is the clean case. Phase 9 assumed DB_DRIVER=supabase in production and the runtime behind that assumption had never been implemented. The plan said durable Postgres. Production ran ephemeral SQLite. Both statements were true at once for as long as nobody checked.

The census is what keeps the distinction honest. It is a flat list of every surface and what each one does right now, rebuilt by hitting the surfaces rather than reading the repository. Anything built and dark gets recorded with the flag name and the reason it is off, in the same list, so the gap between intent and behavior stays visible instead of resolving itself into optimism. The census entry I point at when explaining this is paid memory sync on muse.suedeai.ai: built, merged, and sitting behind MEMORY_SYNC_ENABLED=false. It is in the codebase and it is not in production, and the census says so in those words rather than listing the feature as shipped.

The census also caps the estate’s claims. The public claim can be no larger than the census entry that backs it.

The voice standard for outward-facing copy

On 2026-05-14 a re-engagement email draft came back to me opening with “just wanted to flag what’s new in case any of it matters.” What I said in response is the whole standard in one line: “its suede of course it fucking matters.”

The line asked the reader to grant the update permission to matter. Suede claims best-in-category positioning, and copy that pre-apologizes tells the reader the claim is soft. The rule covers outward-facing artifacts: emails, marketing, social, READMEs, app store listings, blog posts, outreach, slide decks. Approved openers instead: “Here’s what shipped since:”, “Quick update on what’s new:”, “Three things worth knowing about:”. Warmth stays legal. “Hey {name}” and “reply if you bounced” are fine, because confidence is about whether the copy claims its own value and has nothing to do with tone temperature. The rule bites hardest on dormant re-engagement, where apologetic copy lowers reply rates at the exact moment reply rate is the metric.

Commit messages, code comments, and debugging notes are exempt. Private working text does not need a brand voice.

Vocabulary sits under the same standard. On 2026-05-15, mid-write, a spec draft called the Suede Social audience “players” and I stopped it. The shipped codebase had already standardized on “members” in discover page metadata and OG cards, so the spec had drifted from live copy. Substitutes: “members” for the collective, “guitarists” when the context is gear, “musicians” when it is broader, or direct pronouns. If the sentence reads without a collective noun, drop the noun. The correction applies upstream, to specs, design proposals, PR descriptions, and brainstorming summaries, because wording that survives a spec propagates into the build and then has to be fixed at every layer it reached.

Claims the estate refuses to make

On 2026-06-10, during the master registry rebuild, the first pass kept the existing section structure and swapped the accent color and the copy. I cut in mid-pass: “this is the same old site, i want it to look so much better… much cleaner and more interactive.” The corrective includes a claim boundary. Prefer live on-chain and honest numbers over static mock numbers on any surface a visitor can touch pre-signup. A mock number on a live page is a small lie that gets screenshotted.

On 2026-07-21, scoping a metered external-tool node for Agent Studio, a session proposed Monid, a peer tool-marketplace aggregator, as the backend supplier. It had 1 GitHub star, a release five days old, and no README. I killed it: “no we dont integrate any third partys yet.” Every external-call surface in the estate is first-party, including the 12 Business Skills templates and all Suede Tools canvas nodes, each calling its provider directly. The written rationale, that a reseller stacks Suede’s margin on the aggregator’s and adds dependency risk, is flagged in the memory as a working theory rather than a reason I gave, and it stays flagged that way. “Yet” makes it a maturity bar rather than a ban.

The claim case now has a winner. On 2026-07-17, X and Substack bios still carried unverified “Forbes contributor” wording after it had been removed from suedeai.ai, and two memory files disagreed about whether the phrase belonged in my canonical description. For this editorial revision I checked the live founder record. On 2026-07-29, suedeai.ai/founder identifies me as a published author and does not make a Forbes claim. That live record wins: do not describe me as a Forbes contributor unless a primary publication record later verifies it. Claim hygiene still needs a surface inventory covering the site, GitHub, X, Substack, LinkedIn, and store listings, because a retired claim can survive wherever the pass did not reach.

The same 2026-07-17 pass shows what recording looks like when it goes well. The profile README went from 245 lines and 1,816 words to 38 lines and 320 words across PR #5 and PR #6, COMBINED.md from 725 lines to 17, topics from 20 to 14. Before any of it, the original was archived byte-for-byte with hashes and git bundle verify, so the prune is reversible and unique material can be routed to its canonical surface instead of republished as duplicate markdown. Two items from that pass remain open: the pinned suede-sdk-python card carries a stale description that my personal account cannot edit, since pin cards render the owning repo’s description and the repo lives under Suede-AI, which leaves a Suede-AI-admin lane or dropping the pin. And Suede-AI/suede-docs’s repo manifest and skill count still need a cleanup pass.

The loop: instruct, verify, record, hand off

Instruct, verify, record, hand off. The instruction names the surface and the acceptance check. Verification hits the live thing: curl the domain, read viewerPermission, run the canary. Recording writes what happened, what remains open, and where the disagreement is, in the place the next session will read before it starts. The handoff assumes the reader has no memory of any of this, because in the strict sense that is true.

The Reown/WalletConnect allowlist gap on social.suedeai.ai closes the shape. Found and fixed live on 2026-07-17. Right after verifying it I called off further investment in that surface: “dont worry about crypto or wallets with suede social right now.” The fix stays deployed and correct. EvmProvider, WalletProviders, the Solana wallet-adapter, WalletConnect/Reown config, and wallet-bind UI on Suede Social are off the initiative list until I say otherwise, with (app-host)/app.suedeai.ai excluded from that boundary because wallets are core there. Fixing a surface is not a mandate to keep spending attention on it, and that decision only holds if someone writes it down.

I run this alone with agents. The estate has surfaces I have verified this month and surfaces I have not, two memory files that contradict each other about a bio line, and a pinned card I lack the permission to edit from where it appears. That list is the honest position. Done means the record and the live surface say the same thing, and I keep the list short enough to check.