Slip — implementation spec (1–2 day MVP)
Product: Slip
Date: 18 September 2026
Decision: DECISION.md
Theme: ../design/virtus-theme.md
Bind: 127.0.0.1:3100 (PM2). Public via nginx 80/443 later — do not edit nginx in this build.
Database: Postgres on localhost (127.0.0.1:5432).
Auth: email + password (bcrypt). Magic-link is a day-3 add if SMTP exists.
Not in scope: customer MCP reverse proxy, LLM key paste, token wallet, GitHub App, quantum, traces, SSO, LiveKit.
The MVP must be a working permit engine, not a brochure: interactive playground + signup + dashboard + at least one enforceable permit API (POST /v1/decide).
1. Product name and positioning
| Copy | |
|---|---|
| Name | Slip |
| H1 | Slip. See what the agent can do. Stop it. Prove it. |
| Sub | Permission slips for coding agents. English rules in, signed receipt out. We never take your model keys. |
| One-sentence test | A public blast-radius card and a signed decision receipt for your coding agent, from English rules, without becoming your MCP gateway, your LLM vendor, your IdP, or your auditor. |
| Kicker | Not a gateway · Not an auditor · Not your LLM bill |
Do not say: CryptoBook, MCP gateway, observability, control plane, AgentOps, AI Act compliant, SOC 2, quantum, 100% secure, “proves the work was done,” “makes you compliant.”
Do say: We enforce your rules on your tools. Enforcement of real GitHub/Slack/filesystem servers runs next to your agent. This site stores hashes and decisions, not chats, not keys. Receipts are logs, not legal opinions. US only, 18+.
Wordmark is text “Slip” (700–900, -0.02em) plus the Virtus live mark (design / Virtus logo.webp). Do not redraw the mark in pink.
2. What “real working core” means (acceptance)
A reviewer with no account can, in under 60 seconds:
- Open
/. - Click Example: GitHub MCP.
- See a blast card: red tools (
delete_repo,secret-delete, …), counts, one-line blast radius. - Leave the prefilled English:
never delete; ask before git_push or merge; at most 20 tool calls this session. - Click Run demo agent — “archive old repos”.
- Watch a live tape of real
POST /v1/decideresults:list_reposALLOW,delete_repoDENY,git_pushASK, call counter3 / 20. - Open a receipt page
/r/{id}that verifies a signature in the browser.
A signed-in user can:
- Register (email + password, 18+ checkbox, US-only notice).
- Save the policy, mint a project token, call
POST /v1/decidewith curl and getdenyfordelete_file. - See that deny in the dashboard inbox.
- Copy an unwatermarked receipt URL on Indie (watermarked on Free).
If step 6 is a hardcoded CSS animation that never hits the permit engine, the build has failed.
3. Architecture (non-negotiable)
Browser ──► nginx (later) ──► 127.0.0.1:3100 Node (or Python)
│
├─ Playground UI (static + small JS)
├─ POST /v1/compile English → YAML (deterministic)
├─ POST /v1/decide PDP {allow|deny|ask} + receipt event
├─ GET /mcp/demo Toy MCP (tools we own, no customer auth)
└─ Postgres policies, users, hashed tokens, receipts
| Piece | Where | Allowed to see |
|---|---|---|
| Policy compiler | This VPS | English + YAML. No keys, no prompts. |
PDP /v1/decide |
This VPS | tool, args_hash, call_count, policy_id, project token. |
Toy MCP /mcp/demo |
This VPS | Fake in-memory repo tools. No customer secrets. |
| PEP sidecar | User machine, later | Real tools, real keys. Document the curl contract; do not build a full IDE plugin in 2 days. |
| Receipt ledger | This VPS | Tool name, decision, args hash, policy hash, signature. Never prompt bodies or raw args. |
Forbidden routes:
POST /v1/proxythat forwards to a user-supplied MCP URL.- Any field labeled “OpenAI / Anthropic / IBM API key.”
- Storing
Authorizationfrom customer upstreams. - Session replay, prompt search, “upload full JSONL and we keep it.”
Spend: cap tool-call counts and named denies. A UI “USD estimate” is allowed only as usd_estimate the client sent, labeled “reported by your sidecar — not your provider invoice.” Default playground: no dollar ticker.
4. Stack
| Layer | Choice | Why |
|---|---|---|
| Runtime | Node 20+ (already on box) | One process, PM2, matches other Virtus apps. Python FastAPI is acceptable if the builder is faster in it. One language. |
| HTTP | Express or Fastify (Node) / FastAPI (Python) | Host + JSON API + static. |
| DB | Postgres 127.0.0.1:5432, database slip |
Already running. SQLite only if Postgres role creation is blocked — prefer Postgres. |
| Auth | email + password, bcrypt cost 12, session cookie HttpOnly; Secure; SameSite=Lax |
1–2 day reliability. |
| Signing | Ed25519 (server key in env SLIP_SIGNING_SEED). Public key on /r/ pages |
Receipt verify in-browser with noble-ed25519 or WebCrypto if possible; else server /v1/receipts/:id/verify. |
| Hash | SHA-256 hex | args_hash = sha256(canonical_json(args)). |
| none in v1 | Password reset = “email us” stub. | |
| Payments | stub | Plans in DB (free / indie / studio). Stripe Checkout is day-3. Pricing page shows prices; “Start free” → signup. Do not fake a charged card. |
| Process | PM2 name slip, HOST=127.0.0.1, PORT=3100 |
Cluster not required for MVP. |
| Files | /root/cryptobook-next/app/ |
Do not modify /root/cryptobook (old social). |
Env file /root/cryptobook-next/app/.env (never commit secrets):
HOST=127.0.0.1
PORT=3100
DATABASE_URL=postgres://slip:SLIP_PASSWORD@127.0.0.1:5432/slip
SESSION_SECRET=...
SLIP_SIGNING_SEED=... # 32-byte hex
PUBLIC_BASE=https://cryptobook.space
Create Postgres role/db slip locally. App listens only on 127.0.0.1.
5. Pages
Virtus chrome on every page: design/virtus-theme.md tokens, paste the :root block, data-theme boot script (cryptobook:themeMode, auto 07:00–18:59 light else dark), 64px nav, #050505 footer. Dark default canvas #000. Primary CTA = 135° #e91e8a → #ff6ec7, white label. Cards: glass + 1px hairline + radius 14–18px. Font: Inter, system-ui. No cyan UI, no TMC greens.
5.1 Marketing nav
Left: Virtus mark 40–50px + wordmark Slip.
Links: Product (/#playground), Pricing (/pricing), Docs (/docs), Legal (/legal).
Right: theme cycle, Sign in, primary Open playground.
Footer: Offices/Contact not required. Columns: Product, Legal (Terms, Privacy, Legal), “Slip is not CryptoBook social. Not legal advice. Not a compliance certification.” © 2026. No fake SOC2 badges, no fake logos, no fake testimonials.
5.2 / Homepage
- Hero — centered. Kicker pill with pink dot:
PERMISSION SLIPS FOR AGENTS. H1 as in §1, one word (StoporSlip) in#e91e8a. Two CTAs: primary Try the playground, ghost See a receipt. Trust line:We never take your model keys. US only · 18+. - Playground (
#playground) — token.card+--shadow-glow. This is the product. Spec in §7. - How it works — 3 glass cards: (1) Paste or use the example catalog. (2) English → YAML you can edit. (3) Decide API + receipt you can forward.
- What we are not — short list: not Permit.io, not Langfuse, not your LLM vendor, not an auditor.
- Pricing strip — $0 / $19 / $49 with link to
/pricing. - No logo marquee of customers we do not have.
5.3 /c/:publicId Blast card (public, no login)
Shareable card. Tool rows classified destructive | exfil | write | read | other. Raw tool names always visible; color labels are an opinion. Schema hash per tool. Buttons: Copy link, Use these tools in the playground. Watermark on free anonymous cards: Slip free.
5.4 /r/:publicId Receipt (public, no login)
Human page + JSON toggle.
- Policy hash + English snapshot (or YAML).
- Table: time, tool, decision (ALLOW/DENY/ASK), args hash (8-char prefix), rule id.
- Call count vs cap.
- Ed25519 signature + verify button (green “signature matches” / red “tampered”).
- Disclaimer box (see §11).
- Free: watermark
Slip free. Indie/Studio: clean.
Tamper demo: a “mutate JSON” control that flips verify to red. This is the 60s proof.
5.5 /pricing
Three glass cards. No $29. No annual 50% off hopium.
| Plan | Price | Includes |
|---|---|---|
| Free | $0 | 1 saved policy, 500 decide-calls/day, 7-day inbox, watermarked receipts/cards, playground forever |
| Indie | $19/mo | Unlimited personal policies, 90-day inbox, unwatermarked receipts, webhook URL (stub ok) |
| Studio | $49/mo | 5 seats (enforce 1 seat in v1, show “coming”), ask inbox, CSV export |
CTA on all: Start free → /signup. Copy: “Stripe in a later cut. Plans are recorded on the account; nothing is charged today.” Honest.
5.6 /signup and /login
Email, password, confirm. Required checkboxes: I am 18 or older, I am not using this from the EU/EEA/UK (honor-system in v1; geo-block is nginx later). Link to /legal. No OAuth. No social.
On success: session cookie, redirect /app.
5.7 /app Dashboard (auth)
Sidebar (glass, active = pink wash): Overview, Policies, Inbox, Receipts, Token, Docs.
Overview: counts (policies, decide-calls today, denials 7d), last 5 inbox rows, copy-paste curl for /v1/decide.
Empty state: “Run the homepage playground, then Save policy.”
5.8 /app/policies
List + create. Editor: English textarea, Compile, YAML panel (editable, source of truth), Save. Show public_id. Button Run on toy agent → playground with this policy.
5.9 /app/inbox
Table of recent decide events for this user: time, policy, tool, decision. Filter deny/ask. No raw args.
5.10 /app/token
Project token, shown once on create (slip_live_…). Store only SHA-256 hash. Prefix slip_live_ + 8 chars displayed. Rotate invalidates old hash. Scopes: decide + fetch_policy.
5.11 /docs
One page:
- What Slip is / is not.
POST /v1/decidecontract + curl.agent-permit.yamlschema.- Honest stdio note: local MCP servers never hit us unless you run a sidecar; v1 sidecar = curl in a 20-line example.
- Toy MCP URL for Cursor:
http://127.0.0.1:3100/mcp/demois localhost-only; when nginx exists,https://cryptobook.space/mcp/demowith the demo policy. Label: toy tools, not your GitHub. - Known clients: “curl works today. Cursor/Claude Desktop: point at toy MCP for the demo only.”
5.12 /legal, /legal/terms, /legal/privacy
See §11. Linked from footer.
5.13 /healthz
{"ok": true, "service": "slip"} for PM2/nginx later.
6. API
All JSON. CORS: same-origin plus POST /v1/decide and GET /v1/receipts/:id open (public verify). Rate-limit /v1/decide 60/min/IP anonymous, 600/min/token.
6.1 POST /v1/compile
Auth optional.
{ "english": "never delete; ask before git_push; at most 20 tool calls this session" }
Response:
{
"yaml": "version: 1\ncaps:\n max_calls: 20\nrules:\n - id: r1\n action: deny\n tools: [delete_file, delete_repo, rm]\n - id: r2\n action: ask\n tools: [git_push, merge_pr, gh_pr_merge]\n - id: r3\n action: allow\n tools: [\"*\"]\n",
"policy": { "version": 1, "caps": { "max_calls": 20 }, "rules": [] }
}
Compiler is deterministic, no LLM. See §8. Do not call OpenAI.
6.2 POST /v1/decide — the enforceable permit API
This is the MVP. It must actually allow/deny.
Headers: Authorization: Bearer slip_live_… or playground session header X-Slip-Playground: <ephemeral id> (homepage demo).
{
"policy_id": "pol_…",
"tool": "delete_repo",
"args_hash": "sha256-hex-or-empty",
"call_index": 4,
"usd_estimate": null
}
Response:
{
"decision": "deny",
"rule_id": "r1",
"reason": "matched deny tools",
"receipt_id": "rct_…",
"receipt_url": "/r/hp_7f3a…",
"calls": { "used": 4, "cap": 20 },
"sig": "base64"
}
decision ∈ allow | deny | ask.
If call_index or server-side counter ≥ caps.max_calls → deny with rule_id: cap.
Anonymous playground: create an ephemeral policy + receipt chain in memory or a playground_sessions table TTL 2h. After 2h, receipts remain if we persisted hashes; otherwise only signed-in receipts last.
429 when Free plan exceeds 500 calls/day.
Never echo args. If a client sends args by mistake, hash and drop.
6.3 POST /v1/playground/run
Auth optional. Server-side scripted agent for the demo so the tape cannot desync.
{ "english": "…", "scenario": "archive_old_repos" }
Runs the canned tool sequence in §7 against /v1/decide internally. Returns { card, events[], receipt_id }.
6.4 GET /v1/receipts/:publicId
Public. Metadata + events (tool, decision, args_hash, ts) + sig + pubkey.
6.5 GET /v1/cards/:publicId
Public blast card JSON.
6.6 Auth
POST /v1/auth/signup{ email, password, age18, not_eu }POST /v1/auth/loginPOST /v1/auth/logoutGET /v1/me
6.7 Policies (auth)
GET /v1/policiesPOST /v1/policies{ name, english, yaml }GET /v1/policies/:idPUT /v1/policies/:idDELETE /v1/policies/:id
6.8 Token (auth)
POST /v1/tokens→{ token, prefix }onceDELETE /v1/tokens/:id
6.9 Inbox (auth)
GET /v1/inbox?decision=deny
6.10 Toy MCP POST /mcp/demo (and GET SSE if easy)
Streamable HTTP or simple JSON-RPC MCP subset:
initializetools/list— the toy catalog in §7, filtered by the demo policy (denied tools omitted from list and still denied if called — belt and suspenders).tools/call— executes in-memory fake repo only, after internal/v1/decide. If deny, return MCP errornot allowed by Slip policy. If ask, return errorapproval required(no pending-payload store on server).
This is how the playground is a real MCP, not a slide. It is not a generic upstream proxy.
7. Playground behavior
Layout, one card, two columns on desktop, stacked on mobile.
Left
- Catalog source: tabs Example: GitHub MCP | Example: Filesystem | Paste tools/list JSON.
- Do not fetch arbitrary user URLs from this VPS (SSRF). Paste JSON only.
- Tool list renders immediately as a mini blast card (red/pink/muted rows).
Right
- English textarea, prefilled for the example.
- Button Compile → YAML panel under it (read/write). YAML wins if both present.
- Button Run demo agent (primary).
- Live tape: rows appear as decide returns. ALLOW muted, DENY
#ef4444text, ASK#ff6ec7. Countern / cap. - ASK in the playground: inline Approve / Deny buttons that call decide again with a one-shot override flag
playground_override: allow|denyrecorded on the receipt asask_resolved. Do not send real payloads to the server — toy args are{ "name": "demo-repo" }hashed.
After run
- Blast card preview + Share card →
/c/{id}. - Receipt preview + Open receipt →
/r/{id}. - Save to account (if logged in) or Sign up to keep this policy.
Example catalog (pin in code)
GitHub-like (names only; we do not call GitHub):
list_repos, get_repo, list_files, read_file, create_pr, git_push, merge_pr, delete_repo, delete_file, secret-delete, add_collaborator.
Filesystem-like: ls, read_file, write_file, delete_file, rm.
Classification heuristic (deterministic):
- destructive: name matches
/delete|destroy|remove|rm|drop_|force_push|secret-delete/i - exfil:
/read_file|cat|get_secret|download|email|send/i - write:
/write|create|push|merge|update|put/i - else read/other
Blast one-liner template: can {top destructive tools} ({n} destructive of {total} tools).
Scenario archive_old_repos (scripted)
list_repos→ allowget_repo→ allowdelete_repo→ deny (rule)git_push→ asklist_files→ allow
Stop. Show “demo agent halted on deny/ask. This is a simulation on toy tools, not your account.”
Filesystem scenario clean_up_this_repo: ls allow, delete_file deny, write_file allow, rm deny.
8. Policy engine
YAML schema (agent-permit.yaml)
version: 1
caps:
max_calls: 20 # per receipt/session
rules: # first match wins
- id: r1
action: deny # deny | ask | allow
tools: [delete_file, delete_repo, rm]
- id: r2
action: ask
tools: [git_push, merge_pr]
- id: r3
action: allow
tools: ["*"]
No CEL in v1. Tool match: exact name or glob delete_* or *.
Default if no match: deny (fail closed).
English → YAML (no LLM)
Ordered regex extracts; remaining sentence ignored.
| Pattern | Effect |
|---|---|
at most N (tool )?calls / max N calls |
caps.max_calls = N |
never delete / don't delete / do not delete |
deny delete_*, rm, secret-delete |
ask before (X or Y or …) |
ask those tool aliases |
allow only A, B |
allow those; implicit deny * at end |
deny X |
deny X |
Aliases: push → git_push, merge → merge_pr, delete → delete_file + delete_repo.
Always append allow: ["*"] last unless allow only was used.
Show YAML beside English. User edit of YAML is what the engine runs. Label: YAML is the source of truth. English is a compiler.
9. Data model (Postgres)
CREATE TABLE users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email citext UNIQUE NOT NULL,
password_hash text NOT NULL,
plan text NOT NULL DEFAULT 'free', -- free | indie | studio
age18 boolean NOT NULL,
not_eu boolean NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE policies (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES users(id),
public_id text UNIQUE NOT NULL,
name text NOT NULL,
english text NOT NULL,
yaml text NOT NULL,
policy_hash text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE project_tokens (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id),
prefix text NOT NULL,
token_hash text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
revoked_at timestamptz
);
CREATE TABLE receipts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
public_id text UNIQUE NOT NULL,
user_id uuid REFERENCES users(id),
policy_id uuid REFERENCES policies(id),
policy_hash text NOT NULL,
event_count int NOT NULL DEFAULT 0,
deny_count int NOT NULL DEFAULT 0,
call_cap int,
watermarked boolean NOT NULL DEFAULT true,
pubkey text NOT NULL,
sig text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE receipt_events (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
receipt_id uuid NOT NULL REFERENCES receipts(id) ON DELETE CASCADE,
seq int NOT NULL,
tool text NOT NULL,
args_hash text,
decision text NOT NULL, -- allow | deny | ask
rule_id text,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE blast_cards (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
public_id text UNIQUE NOT NULL,
source text NOT NULL, -- example_github | example_fs | paste
tools_json jsonb NOT NULL, -- names + class + schema_hash only
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE playground_sessions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
yaml text NOT NULL,
call_count int NOT NULL DEFAULT 0,
expires_at timestamptz NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
Indexes: receipts.public_id, blast_cards.public_id, receipt_events(receipt_id, seq), project_tokens.token_hash.
Do not add columns for api_key, prompt, args_json, raw_body.
Logging: structured JSON. Redact authorization, cookie, password, token, sk-. Do not log request bodies on /v1/decide.
10. Receipt signature
Canonical event: seq|tool|decision|args_hash|policy_hash.
Merkle: sha256 of \n-joined canonical events (fine for <100 events; no need for a real tree in v1).
Sign merkle_root with Ed25519. Store sig + server pubkey on the receipt.
Public page: recompute root from visible events, verify. If the user edits the JSON in the textarea, verify fails.
Obsigna-compatible export is not required in 2 days. JSON on /r/{id}.json is enough.
11. Legal page outline
Not legal advice. Counsel before public marketing. Pages can be one /legal with anchors if faster.
/legal (product posture)
- Slip is software that compiles your rules and returns allow/deny/ask.
- Not a law firm, not an auditor, not a SOC 2 / ISO / HIPAA / AI Act / NIST certification.
- Receipts are logs of policy decisions, not proof that work was done, not court-ready opinions, not employment/credit scores.
- We do not make hiring, credit, housing, education, healthcare, or law-enforcement decisions.
- US only. 18+. Not directed at children. No school accounts.
- EU/EEA/UK: do not use (geo-block forthcoming). Art. 50 hygiene: the playground demo agent is AI-scripted; we disclose it is not a human reviewer.
- Ninth Circuit Amazon v. Perplexity, No. 26-1444 (4 Aug 2026): agents act for the user. We do not log into third-party sites. After an
allow, you are the actor. - No tokens, no stored value, no escrow, no prepaid LLM credits. SaaS subscription (when billed) is for software.
- Contact: use the existing Virtus/site contact email if one exists; else
root@is not a public address — putlegal@cryptobook.spaceas a mailbox to create later.
/legal/terms
- Account, acceptable use (no scanning third-party systems without authorization, no using receipts as consumer reports).
- Plans, no charge in v1, we can delete playground data after TTL.
- Limitation of liability, “as is,” no warranty the policy will catch all dangerous calls.
- Termination, governing law (pick: Germany seat of VPS or US state of the operator — do not invent a US entity. State: “operator of this site; governing law to be confirmed by counsel; VPS in Germany.” Honest > fake Delaware).
- We may refuse templates for employment/credit/education admissions.
/legal/privacy
- Data: email, password hash, policy text, tool names, args hashes, decisions, IP in nginx logs (when proxied).
- Not collected: LLM API keys, prompt bodies, raw tool arguments, payment PANs, children’s data.
- No sale of personal information. No training models on user content (we do not have prompt bodies).
- Retention: Free inbox 7 days; Indie 90 days; playground sessions 2 hours.
- Cookies: session only.
- Contact / deletion request.
On-page disclaimers (required chrome)
Homepage footer + every /r/ + /c/:
Not legal advice. Not a compliance certification. Not an auditor. This is a log of rules you wrote. US only · 18+.
Playground: This demo uses toy tools we host. It does not access your GitHub, disk, or LLM account.
12. Pricing (product, not Stripe)
| Plan | Price | Enforcement in v1 |
|---|---|---|
| Free | $0 | 1 policy, 500 /v1/decide / day / user-or-IP, 7-day inbox, watermark |
| Indie | $19/mo | Unmetered decide (still rate-limit 600/min), 90-day inbox, no watermark. Set users.plan='indie' via a /app debug toggle or SQL for demo until Stripe exists. Do not hide a fake “Pay” that charges nothing without saying so. |
| Studio | $49/mo | Same as Indie for v1 plus CSV export. Seats = 1 with “5 seats — next cut” note. |
Paywall is unwatermarked receipt + 90-day inbox, not “more events” as the headline. Free already feels the deny.
No $199 Pro. No SOC2. No SSO.
13. Virtus theme — builder checklist
Copy from design/virtus-theme.md. Do not invent a palette.
- Paste
:root/[data-theme="light"]CSS (brand#e91e8a/#b5156b/#ff6ec7, bg#000/#050505). - Head script sets
data-themefromcryptobook:themeModebefore paint (auto 07–19 light). - Body
--gradient-bg, Inter/system-ui. - Logos:
/root/cryptobook-next/design/virtus-logo.svg(dark),virtus-logo-light.svg(light), prefer livelogo.webpfrom Virtus assets for favicon/nav if copied intoapp/public/. - Nav 64px fixed, transparent → frosted on scroll.
- Primary pill/gradient buttons, white label, pink glow.
- Cards glass + hairline + 14–18px radius.
- Inputs 14px radius, pink focus ring.
- Footer
#050505. prefers-reduced-motionkills animation.- Light mode smoke-test: no white-on-white text; primary still pink gradient.
Playground sits in a token .card with --shadow-glow. No 3D AI orb.
14. nginx / PM2 (for ops later — do not apply now)
# pm2
HOST=127.0.0.1 PORT=3100 pm2 start server.js --name slip
Intended nginx (ops, not this task):
server {
listen 443 ssl http2;
server_name cryptobook.space www.cryptobook.space cryptobook.com www.cryptobook.com;
# existing cert paths for .space; .com cert after DNS cutover
location /.well-known/acme-challenge/ { root /var/www/letsencrypt; }
location / {
proxy_pass http://127.0.0.1:3100;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Do not add a location ~ /\. that 404s ACME. Do not open port 3100 on the WAN. Do not stop the old :3000 cluster until nginx no longer points at it.
Until nginx is cut, verify locally:
curl -sS http://127.0.0.1:3100/healthz
curl -sS http://127.0.0.1:3100/v1/decide -H 'content-type: application/json' -d '{"tool":"delete_repo","policy_id":"playground"}'
15. Day-1 / Day-2 split
Day 1
- Scaffold app, Postgres schema, Virtus CSS, pages shell.
- Compiler + PDP +
/v1/decide+ playground tape on canned scenario. /c/and/r/with signature verify./healthz, PM2 file, bind 127.0.0.1:3100.
Day 2
- Signup/login, dashboard, save policy, project token, curl path.
- Toy MCP
/mcp/demofiltered by policy. - Inbox, watermarks, plan flag, legal pages, docs, pricing honesty.
- Rate limits, redaction, playground TTL.
Explicitly not day 1–2: Stripe, magic link, GitHub App, customer MCP wrap, sidecar binary, Telegram ASK, CSV of loops, light-mode pixel-perfect beyond tokens, EU geo-block (document it).
16. Definition of done
- [ ]
127.0.0.1:3100serves Slip with Virtus chrome. - [ ] Homepage playground runs a demo that calls the permit engine and produces a deny.
- [ ]
POST /v1/decidedeniesdelete_repounder the default English rules, with a signed receipt. - [ ]
/r/{id}verify goes red if JSON is mutated. - [ ] Signup/login + dashboard shows that deny in inbox.
- [ ] Project token curl works.
- [ ] No LLM API key field exists anywhere.
- [ ] No customer-URL fetch from the server.
- [ ] Legal disclaimers present.
- [ ] Product name is Slip; CryptoBook is not in the H1.
- [ ] nginx and UFW untouched.
If those boxes are ticked, the pivot has a real core. Everything else is a later cut.