{
  "markdown": "# handbook\n\nA **Knowledge System of Record**: the governed source of knowledge this\nproject's people and AI agents operate from.\n\nThree things live here, and they are not the same kind of thing:\n\n- **`knowledge/` — the record.** Plain governed markdown (plus the optional\n  study attachments a document may carry). Yours forever, readable anywhere,\n  portable without this repository's code.\n- **`system/` — the system.** The site, and later the services, that serve the\n  record. Replaceable machinery.\n- **`.ksor/governance.yaml` — the authority.** Which audiences exist, who may\n  approve a document, who may take one down. Every governance act is checked\n  against it, so the record never claims authority nobody granted.\n\nThe point of all this is the **agent interface**: an MCP door that answers from\nthe record with citations, and declines — in as many words — when the record\ndoes not cover the question. That refusal is the feature; an agent that\nimprovises over a gap is the thing a system of record exists to prevent.\nBeside it, the **human interface** is a site your readers browse. Same record,\nsame governance, different machinery: the site is files, the door is a process,\nand they deploy separately.\n\n**Contents**\n\n- [Quick start](#quick-start)\n- [Explore the human interface](#explore-the-human-interface)\n- [Serving to agents](#serving-to-agents)\n- [Quick deployment](#quick-deployment)\n- [Make the record yours: replace the starters](#make-the-record-yours-replace-the-starters)\n- [Writing knowledge](#writing-knowledge)\n- [Deploying](#deploying)\n- [Reference](#reference) — [commands](#commands) · [the files](#the-files-explained) · [when something refuses you](#when-something-refuses-you) · [dependencies](#dependencies-and-advisories)\n- [Ownership](#ownership)\n\n---\n\n## Quick start\n\n```sh\nnpm install\nnpm run dev        # browse the knowledge at http://localhost:3000\n```\n\n\n**Commit the lockfile your first install writes.**\n\n\nNo lockfile ships with this scaffold: npm keeps ONE lock for the whole\nworkspace, and the `@panaversity/ksor` version pinned in `package.json` was\nstamped by the CLI that scaffolded this project — it could not be resolved into\na lock before it existed. Your FIRST `npm install` writes `package-lock.json`;\nrun it before you push, and COMMIT the result — that lock is why two machines\nbuild the same site.\n\nOne honest difference from the pnpm scaffold: pnpm quarantines newly published\ndependency versions for 48 hours (`minimumReleaseAge`), so a routine install\nnever picks up a day-zero compromised release. npm has no equivalent — the\n`.npmrc` here carries the install-script denial half of that posture, and this\nsentence is the disclosure of the half it cannot.\n\n\n---\n\n## Explore the human interface\n\nBefore changing anything, get a feel for how the record behaves. With\n`npm run dev` running:\n\n- **Edit a starter document.** Change its body, save, watch the page update.\n- **Add a new document.** It appears on the dev site, marked — and it would be\n  on no built surface at all, because a new document is `status: draft`. No\n  page, no sidebar row, no `/llms.txt` entry. That is the system working, not a\n  broken build.\n- **Ask your coding agent to do the same.** `AGENTS.md` carries the working\n  rules; read it before you change how documents are written here. The kit in\n  `.agents/skills/` already knows this project: `intake-interview` (define the\n  record with you), `add-sources` (turn what you have — files, or what you\n  know — into governed documents), and `format-checker` (the rules, as a program — also what\n  `npm run check` runs).\n\n**Treat the starters as scratch paper.** They ship approved by\n`ksor-starter/0.0.58` — a tool, not a person. Edit the body and that\napproval stays stamped on text nobody reviewed, and `npm run check` will not catch\nit, because the frontmatter is still internally consistent. So explore in them\nfreely, but start nothing you intend to keep in one of them. Replacing them\nproperly is [below](#make-the-record-yours-replace-the-starters).\n\nTo see what a build actually produces:\n\n```sh\nnpm run build      # the static site, into system/site/out/\nnpm run preview    # serve exactly those bytes\n```\n\nThere is no `start` script, and that is not an omission: the site is a static\nexport, so nothing serves it at runtime. `npm run preview` is `node:http` and\nnothing else — no dependency, no network fetch — so it works offline and behind\na firewall, like the build itself.\n\nIt binds loopback, so it is reachable from this machine only. To open the built\nsite from a container published with `-p`, a cloud dev box, or a phone on the\nsame wifi, name the address on the command line — `preview` is plain `node` and\ndoes not read `.env`:\n\n```sh\nKSOR_PREVIEW_HOST=0.0.0.0 npm run preview\n```\n\n---\n\n## Serving to agents\n\n**This is the point of the whole thing.** The record's other surface is an MCP\nserver: the same knowledge, cited, with a measured floor under which it\ndeclines. An agent connected to it answers from your governed documents or says\nthe record does not cover the question — and never quietly fills the gap from\nits training.\n\nIt asks for two things `npm run dev` does not: a Postgres store with pgvector, and\nan embedding provider key. Three steps, and the order is load-bearing — the\ncommand block is last because it needs both of the things above it.\n\n### 1. Nothing to configure\n\n`instance.md` already names the VARIABLE holding your DSN — never the DSN\nitself:\n\n```yaml\ndatabase:\n  dsn_env: KSOR_DB_URL\n```\n\nThat is the whole required config. `embedding:` defaults to Gemini at 1536\ndimensions, and `retrieval:` is written for you by step 3's `calibrate`. Change\nthe variable name here only if you want a different one.\n\n### 2. Get a database — your agent can do this one\n\n`.mcp.json` at the repo root declares the MCP servers this project may reach.\nIt ships with two, and both are yours to keep or delete — it is your file:\n\n| server                           | what it is                                                                                       |\n| -------------------------------- | ------------------------------------------------------------------------------------------------ |\n| `Neon`                           | Neon's own hosted MCP server, for provisioning the Postgres the agent surface needs (step 2)     |\n| `agentfactory-system-of-record`  | a KSoR record Panaversity operates, served over MCP — an example of the surface you are building |\n\nThe second is **not** your record and is not needed to run this project. It is\nread-only and it answers about the Agent Factory curriculum, not about your\nknowledge. Delete the entry if you would rather your agent not have it; nothing\nhere depends on it.\n\n**Before you connect Neon, know what you are granting.** The Neon MCP server\nacts on your Neon *account*, not on one database: an agent holding it can create\nand delete projects and branches. Point it at an account you are willing to let\nan agent change, review the plan it shows you before approving, and read Neon's\nown documentation on the server's scopes and permissions rather than taking this\nparagraph as the whole of it.\n\nWith it connected, ask your coding agent:\n\n> Using the Neon MCP server, create a project called `<your-record>` and enable\n> the pgvector extension on it. Then create a branch called `dev`, and save that\n> branch's connection string to `.env` as `KSOR_DB_URL`. Never print my API key.\n> Show me the plan before you run anything.\n\nPrefer the OAuth flow. `.mcp.json` is committed and carries no secret; an API\nkey pasted into it would be.\n\nAny Postgres with pgvector works — Neon is the path that has an MCP server, not\na requirement. Locally:\n`docker run -e POSTGRES_PASSWORD=x -p 5432:5432 pgvector/pgvector:pg17`.\n\n**`GEMINI_API_KEY` is the one step no agent can do for you** — no vendor mints\nan API key over a protocol. Get it from\n[aistudio.google.com/apikey](https://aistudio.google.com/apikey) and paste it.\n**The free tier costs nothing and is enough**: embedding input on\n`gemini-embedding-001` is free of charge, and a first corpus is a few thousand\ntokens. This is a signup, not a bill.\n\n### 3. Fill in the environment\n\n```sh\ncp .env.example .env\n```\n\nThen set `KSOR_DB_URL`, `GEMINI_API_KEY` and `KSOR_AUTH=disabled-local`.\n\n**Point `KSOR_DB_URL` at a hosted Postgres now if you intend to deploy** — the\nsame one your host will use. [Neon](https://neon.com) is what this project's own\ndocs are measured against; pgvector is on every plan including the free one, and\nyou enable it once per database with `CREATE EXTENSION IF NOT EXISTS vector;`.\nAnything with pgvector works. [Quick deployment](#quick-deployment) explains why\none shared database saves you a step.\n\n`ksor` reads `.env` automatically — there is nothing to export, and where a\nrefusal tells you to _export_ a variable, putting it in `.env` is the same\nthing. `KSOR_AUTH=disabled-local` is required for a local run: serve refuses to\nboot unauthenticated on purpose, so a server is never open by accident.\n\n### 4. Bring it up\n\n```sh\nnpm run provision  # once: apply the schema, authorize ingest\nnpm run refresh    # build, ingest the record, collect retired generations\nnpx ksor calibrate --instance instance.md   # measure the floor; paste the printed block into instance.md\nnpm run serve      # the MCP server, with the gate already on\n```\n\n`npm run provision` runs once — it applies the schema (or migrates it forward) and\nauthorizes ingest, the two privileged acts that should not happen on every boot.\nAfter that, `npm run refresh` publishes what you have edited and `npm run serve` runs\nthe server. They are separate because **publishing is an act, not a side effect\nof starting a process.**\n\n**`calibrate` is what makes \"not in this corpus\" a real answer**, and it belongs\nhere rather than later: it needs an ingested corpus but no running server, and\nthe floor is read when the door boots — so measuring before `npm run serve` means\nthe door comes up gated the first time. It prints a `retrieval:` block for THIS\ncorpus in THIS embedding space; paste it into `instance.md` exactly as printed.\nNever copy a floor from another corpus.\n\nIt also prints its own caveat, and it is worth reading: the probes it writes are\nderived from your passages, so they share vocabulary a real question will not.\nThe floor it reports is an upper bound on separation until you check it against\nquestions the corpus did not write (`--queries-file`).\n\nA rerun on an unchanged record costs nothing: no new generation, no embedding,\nno rows. Edit a document and the next run picks up exactly that change.\n\n`AGENTS.md` → \"Serving to agents\" is the full runbook; your coding agent reads\nit first. A public bind needs a configured SSO door rather than\n`disabled-local` — see [The agent surface deploys\nseparately](#the-agent-surface-deploys-separately). Any other operation is\n`npx ksor <verb>`.\n\n### Test the door with an actual agent\n\nThe MCP door is meant to be read by agents, so check it with one rather than\nwith `curl`. With `npm run serve` running, **add** an entry to the `.mcp.json` you\nalready have — alongside `Neon`, not in place of it:\n\n```json\n    \"test-record\": {\n      \"type\": \"http\",\n      \"url\": \"http://127.0.0.1:8080/mcp\"\n    }\n```\n\n**If you skipped `calibrate`, expect answers where this test wants refusals** —\nthe gate is off until a floor is measured, which the server says at boot\n(`abstain OFF`) and in every search envelope (`gate: \"off\"`). That is honest,\nnot broken: a floor nobody measured would be a number pretending to be a\nguarantee.\n\nOpen a new session of your coding agent, confirm it lists the server, then ask\nit three questions **in this order** — the order is the test:\n\n1. Something the record covers, **phrased in words the document never uses**.\n   Retrieval is semantic, so this should still find it, and every answer should\n   arrive with a citation.\n2. Something **adjacent but not covered** — your record's own subject area, a\n   question it genuinely does not answer. It should decline.\n3. Something far outside the record. It should decline, and must not answer from\n   its own knowledge.\n\nQuestion 2 is the one that matters. Anything can answer questions it has the\ntext for; refusing a plausible near-miss is the property that makes a system of\nrecord worth trusting, and it is the one that breaks quietly.\n\nKeep the `test-record` entry or remove it — it points at loopback and holds no\nsecret either way. Do not delete `.mcp.json` itself: it is where `Neon` is\ndeclared, and step 2 above needs it.\n\n---\n\n## Quick deployment\n\nBoth surfaces on one domain, in about ten minutes:\n\n1. **Push the repository to GitHub.**\n2. **Import it in Vercel**, then set **Framework Preset** to **`Services`** and\n   **Root Directory** to `./`. The preset is not cosmetic: Vercel's own guide\n   says a project builds as services only when the preset is `Services` AND\n   `vercel.json` carries a `services` key, and that \"if either is missing,\n   Vercel falls back to its default framework detection and ignores your\n   services configuration\" — which is the silent 404 below. No file in this\n   repository can set it for you.\n\n   As for Root Directory: Vercel\n   auto-fills it with `system/site`, because that is where it finds a framework\n   — and the build then reads `system/site/vercel.json`, which does not exist,\n   and fails with `Project framework is set to \"services\", but no services are\ndeclared`. The services ARE declared, in `vercel.json` at the repo root,\n   which is the only place they can be: one builds the site, the other builds a\n   container from the root `Dockerfile`.\n3. **Set three environment variables** in Vercel: `KSOR_DB_URL`,\n   `GEMINI_API_KEY`, and `KSOR_AUTH=disabled-public`.\n4. **Check it actually serves**, before you tell anyone the URL. A Ready\n   deployment that answers 404 everywhere looks identical to a good one from\n   the dashboard:\n\n   ```sh\n   B=https://your-record.vercel.app\n   curl -o /dev/null -w '%{http_code}\\n' \"$B/\"           # expect 200\n   curl -o /dev/null -w '%{http_code}\\n' \"$B/llms.txt\"   # expect 200\n   curl -sI \"$B/mcp\" | head -1                           # expect 405\n   ```\n\n   `/mcp` answering 405 is the door refusing a GET — that is how you know it is\n   routed at all, and a 404 there means the `services` block was ignored.\n\nThree things catch people here. Two are the system being deliberate; the first\nis not, and it is the one that fails without saying so:\n\n- **A deployment can report Ready and serve nothing.** The build succeeds,\n  Vercel collects nothing, and the deployment takes your domain and answers\n  `404: NOT_FOUND` everywhere — with one build-log line as the only signal:\n  `WARNING! Build output contains no \"functions\" or \"static\" directory`. That\n  warning is the FALLBACK collector finding nothing, which is what step 2's\n  preset exists to prevent — check it first. One measurement of ours disagrees\n  with the vendor's rule and is recorded in\n  `node_modules/@panaversity/ksor/docs/deploying.md`; the honest state is that\n  the preset is necessary by the vendor's documentation and has once appeared\n  not to be. If you hit this, the fallback is the\n  classic-keys form in `node_modules/@panaversity/ksor/docs/deploying.md` — read\n  it there rather than guessing, because it **moves the door off your domain**\n  and `KSOR_MCP_RESOURCE_URL` and your SSO API Identifier both have to move with\n  it.\n- **`disabled-local` will not deploy.** The container sets `$PORT`, so the door\n  binds `0.0.0.0` — a PUBLIC bind — and refuses that value by design, saying so\n  in as many words. `disabled-public` is you saying you know the door is\n  reachable from outside itself. It belongs in Vercel's environment, not in your\n  `.env`, so your local `npm run serve` keeps its loopback posture. It is a\n  starting posture, not a destination: [secure it\n  properly](#audiences-decide-what-a-build-contains) with the SSO variables once\n  the thing is up.\n- **Point `KSOR_DB_URL` at a Postgres your host can reach.** Deploying does not\n  publish — the door serves whatever generation is already in the database. If\n  your DSN pointed at a database on your laptop, the site comes up fine and the\n  door comes up empty. Use one hosted Postgres for both and the `npm run refresh`\n  you already ran is the generation Vercel serves.\n\nIf you only want readers served, the site is a static export with no database in\nthe loop: deploy with no environment variables at all and add the door later.\n\n---\n\n## Make the record yours: replace the starters\n\n**Five starter documents publish on the first build.** They ship\n`status: stable`, so `npm run dev` and `npm run build` both give you a working record\nstraight away — pages, a sidebar, a `/llms.txt` an agent can read — instead of\nan empty shelf. They are approved by `ksor-starter/0.0.58`: the tool\nthat wrote them, named as a producer rather than as a person, because no person\nreviewed a word of it. That is what the trust tier _unverified_ on every one of\nthose pages says, and it is true.\n\nThey also describe KSoR, not your organisation — and a record that describes the\nwrong thing describes it on every surface. So replace them, in this order:\n\n1. **Run the intake interview.** Ask your coding agent for it. Three questions —\n   what this record is the final word on, what sits just outside it, and who may\n   approve or withdraw a document — then it writes `instance.md` with you and\n   replaces the `human:you` placeholder in `.ksor/governance.yaml` with your\n   real handle.\n2. **Write and approve at least one document of your own** — ask your agent\n   to add it (the `add-sources` skill), read it on `npm run dev`, then approve it.\n   A record is never empty: delete all five starters before this and the next\n   build refuses `ksor-record-empty` and writes nothing.\n3. **Delete each starter document** as your own knowledge arrives.\n4. **When the last one is gone, delete `ksor-starter/0.0.58` from\n   `approval_authorities` in `.ksor/governance.yaml`.** Nothing of yours should\n   be approved by a tool.\n\nIf you did the hello-world tutorial first, your document is approved by\n`human:you`. The interview replaces that placeholder with your real handle —\nand re-attributes every act already recorded under it to you in the same\nchange, because it is the same person. A policy that no longer names\n`human:you` beside a document that still cites it refuses\n`ksor-approver-unauthorised`.\n\n---\n\n## Writing knowledge\n\n### A new document publishes nothing\n\nA new document is `status: draft`, and `npm run build` admits a draft to no surface\nat all: no page, no sidebar row, no `/llms.txt` entry, nothing for an agent to\nread. `npm run dev` shows it, marked — the preview is where drafts live.\n\n### Publishing adds two keys\n\nBeside `status: stable`, name what produced the text and who approved it. Both,\nor `npm run check` refuses the document:\n\n```yaml\nstatus: stable\ngenerated: { by: \"human:you\", at: 2026-01-31T09:00:00Z }\nksor:\n  audience: [public] # already there — every document carries it, drafts too\n  approval: { by: \"human:you\", at: 2026-01-31T09:00:00Z }\n```\n\n`generated` is **provenance**: it names whatever produced the text — a person,\nor the agent that drafted it — and nothing has to authorise it.\n\n`approval.by` is **authority**, so it must name an actor `.ksor/governance.yaml`\nlists, and its `at` may not be earlier than `generated.at` — the text that was\napproved has to be the text that was written. That act is yours.\n\nRun `npm run check` before you commit. It runs the rules as a program, and every\nfailure it reports says what is wrong, why the rule exists, and how to fix it.\n\n### Names instead of handles\n\nThe record stores actors as identifiers — `human:you`, `team:legal-ops` — and a\npage would otherwise lead with the slug: \"Owner · human:bashiraziz\". Put the\nname beside the identifier in `.ksor/people.yaml` and pages print that instead:\n\n```yaml\npeople:\n  \"human:bashiraziz\": Bashir Aziz\n  \"human:ciso\": Ayesha Khan\n```\n\nKeyed by the identifier exactly as the record stores it, quoted because it\ncontains a colon. There is no rule that turns a name into a handle — `ciso` is\nnobody's squashed full name — so both are written down, and an actor with no\nentry renders exactly as stored rather than being guessed at.\n\n**This is presentation, and nothing else.** It grants no authority: who may\napprove or withdraw is `.ksor/governance.yaml`, and the two files are not\nchecked against each other, because someone who leaves the authority list is\nstill the recorded approver of everything they approved. Optional — a record\nthat declares no names reads exactly as it did before.\n\n### Presenting a document\n\nAsk your coding agent for slides and it writes them, from the document, into the\nrecord:\n\n```\nmake slides for knowledge/expenses/approvals.md\n```\n\nYour agent reads the document whole, writes the deck into\n`knowledge/expenses/approvals.slides.yaml`, checks every claim and every number\nback against the document, and tells you what it left out because the document\ndid not support it — which is usually how you find out a document has a gap. The\ndeck then renders on that document's page, straight after its introduction:\nclick through it inline, or **Present** for fullscreen. Presenter notes stay off\nthe screen.\n\nThe slides live in the record, so they are reviewed in the same pull request as\nthe document, versioned with it, and withdrawn when it is withdrawn. There is no\nthird party and no link to rot. If you already keep a deck in Google Slides,\nCanva or SlideShare you can point at it instead — `slides.url:` rather than\n`deck:` — and the page will offer it as a link with a frame the reader loads on\nclick, so nothing is requested from the host until somebody asks.\n\n### Summarising a document\n\nLong documents get a **Summary** tab beside their own words, written the same\nway:\n\n```\nsummarise knowledge/expenses/approvals.md\n```\n\nYour agent reads the document whole, writes\n`knowledge/expenses/approvals.summary.md`, and checks every line back against\nthe document — every number, every rule, and every `##` section, because a\nsummary that covers the opening and trails off is worse than none: a reader who\nused it believes they have the whole document. It reports what it left out\nbecause the document did not support it.\n\nThe summary is part of its document, not a document of its own: no route, no\nsidebar row, no line in `llms.txt`, and it takes its governance from its parent.\nAsk for one only where there is something to compress — under about two screens,\na summary that restates the page teaches readers the tab is not worth opening,\nand a good agent will say so rather than write one.\n\n---\n\n## Deploying\n\n[Quick deployment](#quick-deployment) covers the common path. This is the rest\nof it.\n\n### The site\n\n`npm run build` writes HTML, JS and CSS to `system/site/out/` — about 2 MB with\nzero host-specific dependencies. Anything that can serve files can serve it, and\n`npm run preview` serves exactly those bytes locally.\n\n**`npm run build` runs `ksor build` first.** It generates every `index.md`, runs\nthe record checker, and writes `build.lock.json` — the committed record of what\nwas published, from which commit, with which toolchain — and only then builds\nthe site. A checker refusal stops the build before anything is written.\n\n- **Vercel** — the shipped `vercel.json` deploys from the repo root, builds with\n  `npm run build`, and serves `system/site/out/`. It also declares the MCP **door**\n  as a second service built from the shipped `Dockerfile`, so `/mcp` and the\n  site share one domain.\n- **GitHub Pages, nginx, S3, anything static** — run `npm run build` and upload\n  `system/site/out/`. Hosted under a sub-path (like `user.github.io/repo`)?\n  Build with `KSOR_BASE_PATH=/repo npm run build`.\n\n**Verify any deploy** the same way: the home page, one document page and\n`/llms.txt` load, and each names the documents this record has approved. On a\nrecord whose documents are all still drafts, the home page and `/llms.txt` come\nup empty and there is no document page at all — which is the correct answer, not\na broken deploy. Approve a document and rebuild to see it change.\n\n### Takedowns reach the site through a committed file\n\nTakedowns reach the site through `.ksor/takedowns.yaml`, the committed ledger —\na file in this repository, so the site build needs no database access at all.\n\nThat is deliberate. The act that withdraws a document is one merged commit, and\nboth surfaces read it: the door refuses immediately, the site at its next build.\nMerge the ledger entry, rebuild, redeploy.\n\n**A withdrawal that arrives on a clock works the same way, and that one has to\nbe scheduled.** `stale_after` and `ksor.effective_from` are evaluated once per\nbuild, at the instant that build ran, and the answer is written into\n`system/site/out/` — static files cannot re-decide themselves. So a document\nwhose `stale_after` passes after your last build keeps appearing in `/llms.txt`\nand in its markdown twin, while `ksor serve` — a process, evaluating per request\n— already refuses it. `ksor build` prints the next instant at which this\nhappens. Nothing here rebuilds for you: `validate.yml` runs on pull requests and\n`vercel.json` declares no cron. **If this record uses either key, add a scheduled\nrebuild and redeploy.**\n\n### The agent surface deploys separately\n\n`Dockerfile` and `.dockerignore` at the repo root build it, and they name no\nhost — the same image runs on Cloud Run, Fly, Render, ECS, Kubernetes or a VPS:\n\n```sh\ndocker build -t my-record .\ndocker run --rm -p 8080:80 --env-file .env \\\n  -e KSOR_AUTH=disabled-public my-record\n```\n\n**That last flag is not boilerplate, and it is not a workaround.** The image\nsets `$PORT`, so the door binds `0.0.0.0` — a PUBLIC bind — and the\n`KSOR_AUTH=disabled-local` your `.env` carries refuses there by design. Your\nlaptop is not the exception: a container really is reachable from outside\nitself, and `disabled-public` is you saying you know that. It goes on the\ncommand rather than into `.env` so your ordinary `npm run serve` keeps the loopback\nposture — and a real deployment sets it (or, better, the SSO variables) in the\nhost's environment, since `.dockerignore` keeps `.env` out of the image\nentirely.\n\nOne thing surprises people: **deploying does not publish.** The door serves\nwhatever generation is already in the database, so a first deploy with no\n`npm run refresh` serves an empty record. Publishing is a step you run — from your\nmachine or from CI — and it is deliberately not something a booting container\ndoes. The full walkthrough, including what a cold start costs and where ingest\nbelongs, is in `node_modules/@panaversity/ksor/docs/deploying.md`.\n\n### Audiences decide what a build contains\n\nIf `.ksor/governance.yaml` registers audiences, what you deploy is a **viewer**.\nPlain `npm run build` builds for `[public]` — safe for any host.\n`KSOR_AUDIENCE=public,<audience> npm run build` — a comma list that must always\ninclude `public` — builds for a wider viewer, and that build carries an \"— not\nfor publication\" label because it must never reach a public host: put it behind\naccess control you already trust (VPN, SSO proxy, authenticated host). The tiers\ngovern what a build contains; where each build may be served is yours to\nenforce.\n\nThe site can also show a **sign-in control** that names the reader in the\nnavbar. It is off until you set three variables (see `.env.example`), and it\nnames people rather than keeping them out — a static export cannot gate itself,\nso it is worth having on a record already behind one of the answers above, and\nis not a substitute for them. Setup and the honest limits:\n`node_modules/@panaversity/ksor/docs/deploying.md`.\n\n---\n\n## Reference\n\n### Commands\n\n| Command                 | What it does                                                                                      | When                             |\n| ----------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------- |\n| `npm install`          | fetches dependencies and the pinned `ksor` tool                                                   | first — then commit the lockfile |\n| `npm run dev`              | the site at `:3000`, drafts visible and marked                                                    | while you write                  |\n| `npm run check`            | runs the record checker as a program                                                              | before every commit              |\n| `npm run build`            | `ksor build` (index pages, checker, `build.lock.json`), then the static site to `system/site/out/` | before a deploy                  |\n| `npm run preview`          | serves `system/site/out/` — there is no `start`, the site is a static export                      | to check a build                 |\n| `npm run provision`        | applies the schema, authorizes ingest                                                             | once, for the agent surface      |\n| `npm run refresh`          | builds, ingests the record, collects retired generations                                          | every time you publish           |\n| `npm run serve`            | the MCP server on `:8080`                                                                         | to run the door locally          |\n| `npx ksor <verb>` | everything else — `calibrate`, `takedown`, …                                                      | as needed                        |\n\n### The files, explained\n\nNothing here is decoration, and the dotfiles are not ceremony — each one is a\ndifferent coding agent's way of finding the same working contract.\n\n**The record and its authority**\n\n| Entry                   | What it is                                                                                                                                                                                                                                                                                                                                |\n| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `knowledge/`            | **the record** — your governed markdown. The product; everything else serves it.                                                                                                                                                                                                                                                          |\n| `instance.md`           | what this record is authoritative for; its `name:` is the identity every surface publishes and its `title:` the display title every page leads with (both read at server/build start — restart `npm run dev` after changing either). Its BODY is the agent surface's system prompt — `ksor serve` wires it into the MCP server's instructions. |\n| `.ksor/governance.yaml` | **the root of authority** — which audiences exist, who may approve a document, who may take one down. Committed; every governance act is checked against it.                                                                                                                                                                               |\n| `.ksor/takedowns.yaml`  | the takedown ledger: every withdrawal and every lift, append-only and committed, so the site honours a takedown with no database in the loop. It appears at your first `ksor takedown` — an empty ledger would assert an act nobody performed.                                                                                              |\n| `build.lock.json`       | what the last `ksor build` published — the corpus, the commit, the toolchain — and what every machine surface stamps. Committed; written by `ksor build`, never by hand.                                                                                                                                                                   |\n\n**The system that serves it**\n\n| Entry                         | What it is                                                                                                                                                                                                                                  |\n| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `system/`                     | the code that serves the record: the site today, more as you need it.                                                                                                                                                                       |\n| `package.json`                | the surface commands — `npm run dev` (the site) and `npm run provision` / `npm run refresh` / `npm run serve` (the agent surface) — plus `npm run build` / `npm run preview` / `npm run check`, the pinned `@panaversity/ksor` tool and the workspace layout.      |\n| `Dockerfile`, `.dockerignore` | how the agent surface reaches a host. The Dockerfile names no host; `vercel.json` points at it rather than replacing it, so moving hosts is a redeploy.                                                                                      |\n| `vercel.json`                 | one domain, two services — the static site and the MCP door. Delete it if you deploy elsewhere.                                                                                                                                              |\n| `.env.example`                | the variables the served rung needs; copy to `.env` (gitignored) and fill in.                                                                                                                                                               |\n\n\n| Entry               | What it is                                                                                                                                        |\n| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `.npmrc`            | dependency install scripts are denied; the comment inside discloses the one protection this scaffold lacks (a 48-hour quarantine on new releases). |\n| `package-lock.json` | the exact dependency versions — written by your FIRST install; commit it, it is the reason two machines build the same site.                       |\n\n\n**The working contract**\n\n| Entry                            | What it is                                                                                                                                                                                              |\n| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `AGENTS.md`                      | the working contract every coding agent reads first — the rules for writing knowledge here.                                                                                                             |\n| `CLAUDE.md`                      | one line, pointing at `AGENTS.md`. Claude Code looks for this filename, not that one.                                                                                                                   |\n| `.agents/skills/`                | the agent kit: `intake-interview` (define the record with you), `add-sources` (turn files, or what you know, into governed documents), `format-checker` (the rules, as a program).                          |\n| `.claude/skills/`                | byte-identical copies of the kit — Claude Code discovers skills only here. The checker enforces the mirror, so the two cannot drift.                                                                     |\n| `.gemini/settings.json`          | points Gemini CLI at `AGENTS.md`; Gemini does not read that filename on its own.                                                                                                                        |\n| `.github/workflows/validate.yml` | your CI: runs the same checker on every pull request and push to main.                                                                                                                                  |\n| `.gitattributes`                 | markdown is checked out byte-stable on every platform, so the same commit hashes the same everywhere.                                                                                                   |\n| `.gitignore`                     | keeps build output, `node_modules/`, and `.env` out of the record's history — and negates three paths inside `.ksor/`, because the policy, the ledger and the phone book ARE the record.                  |\n\n`format-checker` deliberately contains a program, `check.mjs`, and not only\nprose: rules that are only written down cannot refuse anything.\n\nEverything here is yours to change. The kit exists so that any coding agent can\noperate this project without being taught it first.\n\n### When something refuses you\n\nThis project refuses loudly and on purpose. Most of what looks like a failure is\na rule doing its job — and every refusal names its own fix, so this table is a\nmap rather than a substitute.\n\n| What you see                                                         | What it means                                                                                              | What to do                                                                             |\n| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |\n| `npm run check` refuses a document                                      | `status: stable` without both `generated` and `ksor.approval`, or an approval earlier than the text it approves | add both keys; approval cannot precede what it approves                                |\n| `start` — missing script                                             | there is none: the site is a static export, so nothing serves it at runtime                                | `npm run preview`, or upload the folder                                                   |\n| `npm run preview` exits `3`                                             | no `system/site/out/` yet, `PORT` is not a port number, the port is taken, or `KSOR_PREVIEW_HOST` cannot be bound — it says which | build first; or set a free `PORT` (`dev` uses 3000 too). `preview` binds loopback; set `KSOR_PREVIEW_HOST` to reach it from a container or another device |\n| `npm run serve` refuses to boot                                         | it will not run unauthenticated by accident                                                                | `KSOR_AUTH=disabled-local` in `.env` for a loopback run                                |\n| the deployed or containerised door refuses with `disabled-local`     | it binds `0.0.0.0` — a public bind                                                                         | `KSOR_AUTH=disabled-public` in the host environment, or configure the SSO variables    |\n| the agent answers questions 2 and 3 instead of declining             | no floor is measured, so the gate is off (`abstain OFF`, `gate: \"off\"`) — step 3's `calibrate` was skipped  | `npx ksor calibrate --instance instance.md`, paste the block, restart             |\n| a deployed door serves an empty record                               | deploying does not publish — and a laptop DSN is unreachable from the host                                 | point both at one hosted Postgres, then `npm run refresh`                                 |\n| the home page and `/llms.txt` are empty                              | every document is still a draft — correct, not broken                                                      | approve one and rebuild                                                                |\n| `ksor-record-empty`                                                  | every document was deleted — a record is never empty, so nothing was written                               | add one document of your own (or restore one from git) before deleting the last starter |\n| `ksor-approver-unauthorised`                                         | a document is approved by an actor `.ksor/governance.yaml` no longer names — usually `human:you` after the interview | re-attribute the approval to your handle, or restore the actor to `approval_authorities` |\n| a new document never appears on the built site                       | drafts reach no built surface at all                                                                       | publish it — `status: stable` plus both governance keys                                |\n| an expired document still shows on the site but not through the door | the static build evaluated `stale_after` at build time                                                     | rebuild and redeploy; schedule a rebuild if you use it                                 |\n| Vercel: `no services are declared`                                   | Root Directory was auto-filled with `system/site`                                                          | set it to `./`                                                                         |\n\n### Dependencies and advisories\n\nAn audit of this scaffold reports vulnerabilities in `next`, and will keep doing\nso: a framework that large always has open advisories against whatever version\nyou have pinned.\n\n\n`npm install` prints the count at the end of every install, so you meet it\nbefore you have run anything, next to an invitation to run\n`npm audit fix --force`.\n\n\n**Never let an audit tool raise the pin for you.** It moves off the version this\nscaffold was built and tested against, and that pin is the whole reason two\nmachines produce the same site. Bump it deliberately instead — take the newer\npin a newer `ksor init` emits, or raise it yourself and re-run `npm run build`.\n\nIt also reads worse than it is, for one structural reason worth knowing: **this\nsite is a static export.** `npm run build` writes HTML, JS and CSS to\n`system/site/out/`, and no framework server ever runs in front of your readers —\nno middleware, no server actions, no rewrites, no image optimizer. Most\nframework advisories describe exactly those request paths, so they have nothing\nhere to reach.\n\nTwo things that argument does NOT cover, and you should treat as real: an\nadvisory in the **build** toolchain, which does run, on your machine and in your\nCI; and any advisory at all if you later add a served route and stop exporting.\nRead what an advisory affects before deciding it is inert — the static export is\na reason, not a blanket.\n\n---\n\n## Ownership\n\nEverything here is yours. The scaffold was generated by\n[ksor](https://github.com/panaversity/ksor) (version in `instance.md`) and is\ngranted without attribution or licence obligations; your knowledge was never\nanyone else's to license.\n",
  "bytes": 43815,
  "sha": "7c3ced91da9b01d0fc3019d7b0072c7941fcc7953ff35d161491be5bfaee9122",
  "repo_slug": "awais68/ksor",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_awais68_ksor_knowledge_index_md_e20208a6/readme"
}