sahin.io

All skills and agents

private-data-boundary

The owner's private investment data and where it may never go. Invested amounts, invest dates, realized/unrealized/current value, [redacted]/TVPI/DPI, per-company or per-deal [redacted] (one general portfolio [redacted] is allowed, from data/portfolio-performance.yaml), carry, fund or entity names, share class, [redacted]-or-cap, round terms, and links to his AngelList portfolio must never be in a committed file, a build, a deploy target or another repo. The one exception is angel-investments-performance, which is always kept. Use before any data, Airtable, AngelList, sync, import, deploy, cleanup or cross-repo work, and whenever a file or page might carry deal data.

Source file
.claude/skills/private-data-boundary/SKILL.md

Private data boundary

The rule

The Airtable Manus view's columns are fixed. Only its public columns (data/airtable-manus-sync.json publicFields: name, market, exit status, latest market cap, one-liner, website, acquired by, category, sub-category) may be written to any file, in any repo, or to any deploy target.

Never write, and never pull from AngelList or another Airtable view:

  • invested amounts and invest dates
  • realized, unrealized and current value
  • [redacted], TVPI, DPI, RVPI
  • per-company or per-deal IRR, and any second IRR figure (see below)
  • carry, fund names, investment entities
  • share class, valuation-or-cap, [redacted] and other terms

No AngelList links (owner, 2026-10-11): no angellistUrl key and no value, logo included, that links to his AngelList portfolio. Airtable's "Angellist URL" column is never read.

IRR: one general figure only

Owner, 2026-10-11: "IRR is ok, general IRR is ok, only one IRR is allowed, which is general." The portfolio-wide IRR may appear on the public site as one figure. It lives in exactly one place, data/portfolio-performance.yaml (generalIrr), read through shared/portfolioPerformance.ts; copy in content/ and page code uses the {irr} placeholder and the label "IRR". Never write the number itself anywhere else, never a per-company or per-deal IRR, and never a second IRR figure (industry benchmarks included). server/general-irr-single-source.test.ts fails on a hard-coded percentage next to "IRR" or the literal figure outside the source file. The llms files and data downloads still carry no IRR at all (RESTRICTED_PERFORMANCE_TERMS in server/publicContentPolicy.ts).

The one exception

Always keep angel-investments-performance (owner, 2026-10-11). It is the private Angel Simulator (~/Code/sahin/angel-investments-performance, private repo sahin/angel-investments-performance) and holds this data on purpose. Never delete or archive it, never rewrite its history, never scrub its data, including during privacy cleanups. A user-level PreToolUse hook (~/.claude/hooks/keep-angel-investments-performance.sh) blocks those commands.

Enforcement

  • server/private-deal-data.test.ts: fails on any deal field in a committed JSON file, any AngelList portfolio link in any committed file, and any of the retired private exports coming back.
  • pnpm privacy:bundle (scripts/validate-public-bundle.mjs): the same checks on every shipped file in dist/cloudflare.
  • Field list and patterns: scripts/private-deal-fields.mjs. Never loosen them.
  • server/general-irr-single-source.test.ts: the general IRR figure exists only in data/portfolio-performance.yaml; no second IRR figure anywhere.
  • Agent: private-data-auditor sweeps the repo, build, live site, sibling repos and Cloudflare Pages projects (read-only).

Before data, sync, deploy or cleanup work

Check every place the data lands, not just this repo:

  • sibling repos under ~/Code/sahin/ (angel-investments, web-angel-investments-*), but leave angel-investments-performance alone
  • every Cloudflare Pages project in account 9ea45491…. Old projects keep serving files, and every deployment keeps its own URL.
  • Cloudflare caches hashed assets for a year (immutable). A file removed from the build can stay reachable until it is purged.

When a shipped bundle carried private data

  1. Remove the data at its source and rebuild (the bundle gets a new hash).
  2. Add the old path to REVOKED_PUBLIC_ASSETS in server/publicRouting.ts. The worker then answers it with a 404 before Cloudflare's asset cache is consulted. server/cloudflare-worker.test.ts proves this.
  3. Ship it. Every deploy runs scripts/cloudflare-purge.ts, which purges each revoked bundle from both sahin.io cache layers and checks it answers 404. If one is still served, it purges the whole zone and checks again. The run fails if that doesn't work, or if the CLOUDFLARE_API_TOKEN secret lacks the Zone > Cache Purge permission on sahin.io.
  4. Older deployments keep their own pages.dev URLs and old worker. Only deleting those deployments removes the file there.

History

On 2026-10-11 an old Manus dashboard (angel-portfolio-dashboard.pages.dev, repo sahin/angel-investments) was found serving these fields. The project was deleted and the files purged from that repo's history. portfolio-data-airtable.json, scripts/airtable-raw-export.json and 285 AngelList links were removed from this repo (6e74d0e, ad0d60c).