How what's true? works

what's true? keeps a product's public docs true, and keeps agents from making promises. It has no built-in AI. People and their own agents write the docs. what's true? decides what needs writing, checks it, and records who approved it.

It checks docs for drift and risky specifics. It is not legal review.

What it does

  1. Each doc says what it covers: code paths, features (pull request labels), routes and facts.
  2. A merged pull request that touches what a doc covers marks the doc stale. Someone then updates the doc, or says that nothing needs to change.
  3. The guard checks public text before it ships: personal names, internal terms, retired products, vendors you have not decided to name, and on legal pages the specifics that become promises.
  4. A required GitHub status holds the merge until a person approves that exact commit. Agents and tokens never can.

Your docs stay in your repository, and your own site publishes them. what's true? reads them. It does not host a help centre, crawl your site or give legal advice, and the alpha has no billing.

Set up a product

  1. Sign in with KRNL, and make a workspace. Making one means accepting the terms and the privacy policy, and passing a short check that you are a person. When public sign-up is closed, it also needs an invitation.
  2. Add a product. No two products in what's true? share an address.
  3. In the product's settings, install the what's true? GitHub App on the repository. The App reads the repository's contents, pull requests and metadata, and writes commit statuses.
  4. Choose how the docs are read: Markdown files in the repository, or a manifest built in CI.
  5. Require the what's true? status on the default branch, with a ruleset. Classic branch protection works too. The product shows Enforced once GitHub requires it, and Can't check if GitHub does not show it the branch's rules: switch to a ruleset. If GitHub does not let you require a status on that repository, the status still shows on each pull request, but it does not hold the merge.

The product's first report lists what is wrong in its published docs today: guard findings, coverage that matches nothing or everything, and docs with no review date.

How a repo describes its docs

There are two ways. A product uses one of them, set in its settings.

Markdown with front matter

what's true? reads Markdown files straight from the repository, through the GitHub App, at each commit it needs. There is no build step. whatstrue/config.yaml says where the docs are and what the guard should know:

product: your-product        # the product's address in what's true?
docs: ["docs/**/*.md"]       # up to 20 globs
facts: whatstrue/facts.yaml
guard:
  people: []                 # extra names; workspace members are added on the server
  internal: [YourInternalName]
  publicVendors: [GitHub]
review: { support: P90D, legal: P365D }
skipLabels: [not-user-facing]

Without the file, the globs come from the product's settings, docs/**/*.md by default. Each doc starts with front matter:

---
id: help/editing
title: Editing
tier: support                # legal, support, changelog or internal
status: published            # draft, published or retired
url: https://example.com/help/editing
covers:
  paths: ["app/editor/**"]
  features: [editor]
  routes: [/editor]
facts: [plans.pro.name]
reviewed: 2026-10-01
---
# Editing

Open a document and type.
  • id defaults to the file's path without docs/ and its extension, and title to the first heading.
  • A doc with no tier is read by its path: one that looks legal is read as legal, and the rest mostly as support. Set it.
  • status is published unless it says otherwise. Only published docs become stale or appear in the product's report, and a change to a draft that stays a draft needs no approval.
  • covers.paths are globs of files in the repository. covers.features are pull request labels. covers.routes are recorded with the doc and shown with it.
  • reviewed and effective are dates, YYYY-MM-DD.

A manifest built in CI

A repo whose docs live anywhere else, or are built, writes whatstrue/manifest.json from its own build and uploads it from CI for each commit, with whatstrue push or the Action. It follows the v1 schema at /schema/manifest-v1.json:

{
  "version": 1,
  "commit": "the full commit SHA it was built from",
  "docs": [{ "id": "help/editing", "title": "Editing", "tier": "support", "text": "The text exactly as published.",
             "url": "https://example.com/help/editing", "covers": { "paths": ["app/editor/**"] } }],
  "facts": [{ "key": "plans.pro.name", "value": "Pro" }]
}

Each doc needs id, title, tier and text. The other fields are the same as in front matter. whatstrue build writes a manifest from Markdown docs, so a repo can move to manifest mode without rewriting them. A product in manifest mode reads only manifests: a pull request's review waits for the manifest of its head commit, and the status says so.

Tiers and facts

TierWho approves a changeGuard preset
legalA person with the approve legal permissionstrict
supportA person with the approve support permissionstandard
changelogNobody here: what changed? approves release notesstandard, without hard-coded facts
internalNobodyexposure only

A pull request that changes docs needs approval for each tier it touches. A doc that moves between tiers needs both. A pull request that changes no legal or support doc passes.

Members have a level: viewer, editor, admin or owner. Approve permissions are separate, and are given to editors and above, never to tokens. Whoever makes a workspace starts with both.

Facts are the values docs depend on, such as a plan's name or a support address. In Markdown mode they live in whatstrue/facts.yaml:

plans.pro.name: Pro
plans.team.name: { value: Team, status: retired, retiredOn: 2026-10-01 }
support.address: { value: help@example.com, visibility: public }

A fact has a status, active or retired, and a visibility: public, checkout (shown only where people buy) or internal. A doc uses a fact by writing {{fact:<key>}} in its text, with the fact's key in place of <key>, or by listing the key under facts. When a fact a doc uses changes, the doc is stale.

Stale records

A doc becomes stale for one of four reasons:

  • a merged pull request changed a file its covers.paths match;
  • a merged pull request had a label its covers.features name;
  • a fact it uses changed, or was retired or removed;
  • it passed its review date: reviewed plus the period for its tier, P90D for support and P365D for legal unless the product says otherwise.

A doc has one open record at a time, and new reasons are added to it. Pull requests with a skip label, not-user-facing by default, are recorded but mark nothing stale, and so are pull requests from bots unless the product's settings say otherwise. Edits to a doc's own file never make it stale. Changelog docs are never stale here.

A stale support doc is fixed by a pull request that changes it and is approved here, which clears the record when it merges. A person with approve support can also mark it "No change needed", or dismiss it. A stale legal doc asks a question: does the change affect anything you promise? Only a person with approve legal answers it. "No" clears the record. "Yes" keeps it open until the page is changed and approved.

Every change to a published legal doc that merges is kept as a version, with its effective date and who approved it. Approving a legal change can mark it material, which opens a "Tell your users" task. what's true? sends nothing to your users: the task records that you did. You can also record the day counsel reviewed a version.

The guard

The guard is one function. The service, the CLI, the Action and what changed? all run it, so a doc gets the same findings wherever it is checked. A block finding holds the merge. A warning is for a person to look at.

RuleWhat it findsLegalSupportChangelogInternal
peopleNames of people: workspace members, and the names the product and the repo listblockblockblock
internalInternal terms the product and the repo list, and an agent presented as staff. Words that sound like an internal system are warnings.blockblockblock
retiredThe value of a retired factblockblockblock
internal-factsThe value of an internal factblockblockblock
vendorsA known service that is not on the product's public vendor listblockwarningwarning
legal-specificsTime periods, amounts of money, rates, limits, promises of speed or with no limit, named cookies, plan names and checkout factsblock
promisesWords that promise an outcome, and a time something will happen inwarningwarning
hardcoded-factsA fact's value written out, in a support doc that does not use the factwarning
exposureAn internal doc with a public URLblock

Facts' values are checked as they are published: a fact reference is read as its value, and HTML comments are left out.

Exceptions. When a doc must say something a rule finds, write the exception in the doc, with the reason. In front matter:

allow:
  - rule: legal-specifics
    match: "30 days"
    reason: Required by the consumer law where we sell.

The same can be written as an HTML comment in the text that starts with whatstrue-allow, then the rule, the text in quotes and reason:. A new exception is listed in the pull request's review, with its reason, so the person who approves the change sees it.

Approval

Each pull request to the default branch gets the what's true? status. Its review shows the docs the pull request adds, changes or removes, with their diffs and findings.

  • Pending while the docs it changes need approval.
  • Failure while a block finding has no exception, while a file it touches cannot be read as a doc, or when a person requests changes. A pull request with block findings cannot be approved until they are fixed.
  • Success once a person with the right permission approves, or when no doc in it needs approval.

An approval is for one commit. A new push is a new review, and it needs approving again: approval never carries over. Only a person signed in to what's true? in their browser, in the last 12 hours, with the approve permission for each tier involved, can approve. A token, an agent, MCP and KRNL OS never can.

The CLI

The CLI is the npm package whatstrue, for Node 20 or later. It is not on npm yet.

whatstrue check                          # every doc the config's globs find
whatstrue check docs/legal/terms.md      # named files
echo "Open a document and type." | whatstrue check --stdin --tier support
whatstrue build                          # writes whatstrue/manifest.json from Markdown docs
WHATSTRUE_TOKEN=wt_... whatstrue push    # uploads the manifest for this commit
CommandOptions
check [files...]--tier <tier> treats the files, or standard input, as that tier. --stdin checks text from standard input and needs --tier. --manifest [path] checks the docs in a built manifest. --json prints the result as data.
build--out <path>, whatstrue/manifest.json by default. --commit <sha>, by default HEAD, or a pull request's head commit in GitHub Actions.
pushNeeds WHATSTRUE_TOKEN, a product token with the write scope. --manifest <path>, --commit <sha>, --ref <branch>, and --origin <url> or WHATSTRUE_ORIGIN, https://true.krnl.studio by default. The token is only sent over https.

Every command takes --config <path> and --cwd <dir>. check exits 0 when nothing blocks, 1 when something does, and 2 when the command, the config or the docs cannot be read. A push that fails exits 1. Files outside the config's globs are skipped, so an agent's hook can run whatstrue check on every file it edits and see findings before it finishes.

The GitHub Action

The Action runs the CLI's steps in a workflow, on pull requests and on the default branch. It is not on GitHub Marketplace yet. It builds the manifest from the Markdown docs if your build did not write one, pushes it, and runs the guard, with each finding on its line in the pull request and a summary on the run. The job fails on block findings.

Check out the pull request's own commit, not the merge commit GitHub makes: give actions/checkout the ref ${{ github.event.pull_request.head.sha || github.sha }}. In Markdown mode, what's true? reads the docs from the repository itself, so leave out token and the Action only checks.

InputDefault
tokenA product token with the write scope. Without one, nothing is pushed.
configwhatstrue/config.yaml
manifestwhatstrue/manifest.jsonYour build's manifest. If it is not there, it is built from the Markdown docs before pushing.
checktrueRun the guard, and fail on block findings.
pushautoPush when a token is set.
originhttps://true.krnl.studio
working-directory.Where the repository is checked out.

Its outputs are blocking, warnings and commit.

Tokens and their scopes

A product token lets CI or an agent act for one product. An admin makes it in the product's settings. It looks like wt_ and 64 hexadecimal characters, and it is shown once: what's true? keeps only its SHA-256 hash.

ScopeWhat it may do
readEvery token has it. It acts as a viewer: the workspace's own entry for its product, docs, facts, reports, stale records, reviews, legal versions, the audit log, and running the guard.
writeIt also acts as an editor: uploading manifests, and adding information to support-tier stale records.

A token reaches only its own product. It never runs a command that is for members only or for a signed-in person, whatever its scopes. A product holds 25 live tokens. Revoking one in the product's settings stops it at once.

The API and MCP

Every caller uses one set of commands: the browser, the CLI, MCP and KRNL OS. Send {command, args} to /api/command:

curl https://true.krnl.studio/api/command \
  -H "Authorization: Bearer $WHATSTRUE_TOKEN" -H "Content-Type: application/json" \
  -d '{"command":"docs.check","args":{"text":"Open a document and type.","tier":"support"}}'

The answer is {"ok":true,"data":...}, or {"ok":false,"error":{"code":"...","message":"..."}} with a code such as unauthorized, forbidden, not_found, invalid_arguments, limit or rate_limited. A token's commands are for its own product. A person names the workspaceId and productId in args. What you may do comes from who you are, never from the arguments.

CommandLeast levelWho may run it
workspace.getviewerMembers, or a token with read
workspace.createviewerA signed-in person only
workspace.updateadminMembers, no token
workspace.deleteownerA signed-in person only
workspace.exportadminMembers, no token
member.inviteadminMembers, no token
member.updateadminMembers, no token
member.removeadminMembers, no token
product.createadminMembers, no token
product.updateadminMembers, no token
product.deleteadminA signed-in person only
github.connectadminMembers, no token
github.statusviewerMembers, or a token with read
token.createadminMembers, no token
token.revokeadminMembers, no token
manifest.pusheditorMembers, or a token with write
docs.listviewerMembers, or a token with read
docs.getviewerMembers, or a token with read
docs.checkviewerMembers, or a token with read
docs.reportviewerMembers, or a token with read
facts.listviewerMembers, or a token with read
facts.usageviewerMembers, or a token with read
stale.listviewerMembers, or a token with read
stale.getviewerMembers, or a token with read
stale.noteeditorMembers, or a token with write (support-tier records only)
stale.answereditorA signed-in person only
stale.dismisseditorA signed-in person only
review.listviewerMembers, or a token with read
review.getviewerMembers, or a token with read
review.decideeditorA signed-in person only
legal.versionsviewerMembers, or a token with read
legal.counseleditorA signed-in person only
legal.noticeeditorA signed-in person only
audit.listviewerMembers, or a token with read

Only a signed-in person

Eight commands need a person signed in to what's true? in their browser. No token, MCP tool or KRNL OS call can run them, whatever its scopes:

  • workspace.create and workspace.delete, product.delete;
  • the approvals: review.decide, stale.answer, stale.dismiss, legal.counsel and legal.notice.

The approvals also need a sign-in in the last 12 hours, and the approve permission for the tier: approve legal for legal docs and legal versions, approve support for support docs.

MCP

/api/mcp speaks MCP over HTTP (JSON-RPC 2.0), with a product token as Authorization: Bearer wt_.... It answers initialize, tools/list and tools/call. Its tools are the commands a token may run, named with underscores: workspace_get, github_status, manifest_push, docs_list, docs_get, docs_check, docs_report, facts_list, facts_usage, stale_list, stale_get, stale_note, review_list, review_get, legal_versions and audit_list. No tool approves, answers or dismisses. An agent runs docs_check on text before it writes it.

Public endpoints

  • GET /api/public/<product>/legal/<doc>/versions lists a published legal doc's versions, for your own site's "Last updated" line and its past versions. Anyone may read it, and it says nothing about who approved what.
  • GET /schema/manifest-v1.json is the manifest's JSON Schema.

KRNL OS and what changed?

KRNL OS. You sign in to what's true? with your KRNL account. In a KRNL OS project, Enable docs makes a product in a workspace you own, or links one you have, and gives KRNL OS a product token for it with the read and write scopes. It acts as you, so your level applies, and it can never approve. Someone who has no workspace yet is sent to make one here first. Unlink in KRNL OS revokes that token.

For a linked product, each stale record becomes a KRNL OS issue in that project:

  • A support doc's issue says what changed and how to fix it, and an agent can claim it. Its pull request has the issue's key in its title, and a person with approve support approves it here. When it merges, the record clears and the issue closes.
  • A legal doc's issue is a question. It goes to the people with approve legal, never to an agent, and they answer it here.

Clearing or dismissing a record closes its issue. Approvals happen only in what's true?.

what changed? A what changed? product can be linked to a what's true? product with a read token, in what changed?'s settings. Before a release is approved there, what changed? runs the guard on the release text at the changelog tier, with the exceptions recorded on the release, and lists the docs still stale for the pull requests in it. What changed? holds approval while anything blocks, or while what's true? cannot be reached. Changelog docs are approved in what changed?, not here.

Limits

The alpha has these limits. A refusal says which limit it hit and what to do.

Limit
Workspaces each account makes3
Products in a workspace10
Members and invitations in a workspace20
Docs in a product2,000
Manifest size5 MB
Facts in a manifest200
Manifest pushes per product60 an hour
API requests per token600 a minute
Live tokens per product25
Doc globs in the config20
Notes on a stale record50

Reviewed 2026-10-30.