Skip to content

Commit 41c3729

Browse files
docs: W12 marketing+trust accuracy — SCC alignment, changelog, subprocessors, encryption copy, llms.txt backend (#65)
* docs(trust): align SCC posture with DPA (B3) The DPA explicitly incorporates Standard Contractual Clauses (Module Two, controller-to-processor) under §7 as the EU/UK transfer mechanism, but trust-residency.md was still claiming "we do not yet offer Standard Contractual Clauses." That contradicted the legally-binding doc and created a procurement-grade inconsistency. DPA stays as the source of truth — SCCs ARE incorporated. Trust-residency now matches: SCCs available by signing the DPA via support@instanode.dev, with the residency gap (NYC3-only today, no eu-west-1) carved out as the separate honest limitation. * marketing(home): narrow encryption-at-rest claim on step-02 (B5) Step-02 of the "Three steps" panel previously claimed unqualified "encryption at rest" alongside the anonymous-tier visual. The platform does encrypt vault secrets and stored connection_url ciphertext at rest with AES-256-GCM, but the customer Postgres cluster's disk does NOT have explicit at-rest encryption on the anonymous tier, and the panel sits visually next to the anonymous-tier 24h-TTL row. Narrows the claim to what is actually true: encryption at rest for vault secrets and stored credentials. Drops the "automatic backups" sub-claim from this panel — concrete per-tier backup retention lives on the Pro card on /pricing and in the snapshot-retention table at /docs/public/trust-residency where it belongs. * feat(marketing): /changelog as a real route (B4) The DPA (§6 sub-processor change notice), subprocessors.md, and trust-residency.md egress section all reference /changelog as the canonical change-feed customers should subscribe to. Before this commit that link 404'd — every document making the 30-day-notice commitment was technically pointing at nothing. Ships a public lazy-loaded ChangelogPage in PublicShell chrome, mounts it on /changelog in both AppRoutes (browser) and SSRRoutes (prerender), and adds /changelog to PRERENDER_ROUTES so the page renders to static HTML on first byte for crawlers and procurement reviewers. Initial content: reverse-chronological entries for 2026-05-12, 2026-05-13, and 2026-05-14 covering the platform changes those days shipped. New entries go at the top of the ENTRIES array — single PR ships the change and the entry documenting it. * docs(subprocessors): add Resend, Cloudflare, Fastly+GH Pages, Loops (H7) The published subprocessor list omitted four real-world sub-processors that the platform actually depends on. CAIQ Section H reviewers and GDPR DPAs both require this list to be exhaustive, not aspirational. Adds: - Resend — transactional email (verification, magic-link sign-in, billing notifications). USA. Auth-token payload transits in the message body during the validity window. - Cloudflare — CDN + DNS for marketing and dashboard hosts. Global edge. Calls out the TLS-termination-at-edge so reviewers know payload bytes are visible at the edge, not just request metadata. - Fastly + GitHub Pages — marketing site and /docs/public/* SSG hosting. Public assets only; no PII on this path. - Loops — lifecycle email forwarder. USA. Tagged lifecycle event metadata, matches the `Loops forwarder` audit kind in audit_kinds.go. Date bumped to 2026-05-14. * docs(llms): name DO Spaces nyc3 as the prod storage backend (M1) llms.txt and llms-full.txt described the storage endpoint as "S3-compatible storage" without naming the production backend. The generic phrasing is technically correct — every documented API stays identical regardless of backend — but a coding agent picking up the file has no way to know which region the bucket actually lives in, which matters for latency calculations and for the residency story that lines up with the trust-residency doc and subprocessor list. Names DigitalOcean Spaces (S3-compatible) in `nyc3` as the production object-store. Also calls out the 24h lifecycle-rule auto-deletion on the anonymous tier so an agent doesn't quietly rely on data sticking around past expiry. No "MinIO" string remained in either file — the in-cluster MinIO was already replaced with DO Spaces on the production cutover (RETRO 2026-05-12). This commit closes the loop on the customer-visible doc.
1 parent c35db7e commit 41c3729

9 files changed

Lines changed: 246 additions & 7 deletions

File tree

public/docs/public/subprocessors.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Subprocessors
22

3-
Last updated: 2026-05-13.
3+
Last updated: 2026-05-14.
44

55
This page lists the sub-processors instanode.dev engages to provide the Service. It is the authoritative record referenced by the [Data Processing Agreement](./dpa.md) and by Cloud Security Alliance CAIQ Section H responses.
66

@@ -17,6 +17,10 @@ This page lists the sub-processors instanode.dev engages to provide the Service.
1717
| Google | OAuth sign-in | Email address; given name; family name | United States | Yes | Yes — EU-US Data Privacy Framework certified |
1818
| New Relic | Observability — logs, traces, metrics | Operational telemetry; may incidentally include customer identifiers (account UUIDs, email addresses) in error contexts | United States | Yes | Yes — EU-US Data Privacy Framework certified |
1919
| Amazon Web Services (SES bounce handling) | Email-deliverability webhooks (bounce, complaint, suppression) | Masked recipient addresses; delivery status codes | United States | Yes | Yes — EU-US Data Privacy Framework certified |
20+
| Resend | Transactional email — account verification, magic-link sign-in, billing notifications | Email address; auth-token payload contained in the message body during the validity window | United States | Yes | Yes — EU-US Data Privacy Framework certified |
21+
| Cloudflare | CDN + DNS for marketing and dashboard hosts (instanode.dev apex, *.instanode.dev) | HTTP request metadata; payload bytes are visible at the edge because Cloudflare terminates TLS before forwarding to origin | Global edge network | Yes | Yes — EU-US Data Privacy Framework certified |
22+
| Fastly + GitHub Pages | Marketing site and `/docs/public/*` SSG hosting (instanode.dev static assets) | Public marketing content + the dashboard SPA bundle; no PII transits this path | United States + EU edges | Yes | Yes — EU-US Data Privacy Framework certified |
23+
| Loops | Lifecycle email forwarder — onboarding drip, churn-prevention, win-back; sees the same audit event stream the backend emits under the `Loops forwarder` audit kind | Customer email address; tagged lifecycle event metadata (signup, upgrade, downgrade, deletion request) | United States | Yes | Yes — EU-US Data Privacy Framework certified |
2024

2125
---
2226

public/docs/public/trust-residency.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ We aim to say only what is true. Here is what is true today.
7979
|---|---|
8080
| SOC 2 Type II | In progress. Target completion Q3 2026. Audit firm not yet selected. We do not have a SOC 2 report to share today. |
8181
| HIPAA | Not supported. We do not sign Business Associate Agreements today. If you need a BAA, email `enterprise@instanode.dev` so we can scope a Team-tier engagement and tell you whether and when we can support it. |
82-
| GDPR | Today, instanode.dev is not a suitable controller-of-record for EU resident PII. All infrastructure is US-only, and we do not yet offer Standard Contractual Clauses. EU customers requiring an EU data residency posture should wait for eu-west-1. |
82+
| GDPR | Standard Contractual Clauses (Module Two, controller-to-processor) are incorporated by reference in our [Data Processing Agreement](./dpa.md) — sign the DPA via `support@instanode.dev` to activate them. The product gating is separate: as of 2026-05, instanode.dev runs in NYC3 only, so customers whose users are EU residents should not route their PII through us without a separate residency commitment from the platform side. EU customers requiring an EU data-residency posture should wait for eu-west-1. |
8383
| PCI-DSS | We do not handle cardholder data. Payment processing runs through Razorpay. Do not store card numbers in instanode.dev resources. |
8484

8585
If you are on a procurement call that requires a compliance answer not listed here, contact `security@instanode.dev` and we will tell you the truth instead of dodging.
@@ -108,4 +108,4 @@ We respond within 48 business hours. Coordinated disclosure for security issues
108108

109109
---
110110

111-
_Last reviewed: 2026-05-13._
111+
_Last reviewed: 2026-05-14._

public/llms-full.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## What is instanode.dev?
44

5-
instanode.dev is an infrastructure provisioning API. Call one HTTP endpoint, get back a working resource — a Postgres database, a Redis cache, a MongoDB database, a NATS JetStream queue, S3-compatible storage, a webhook receiver, or a full deployed app stack. The resource works immediately with standard client libraries. No account required, no Docker, no configuration, no SDK.
5+
instanode.dev is an infrastructure provisioning API. Call one HTTP endpoint, get back a working resource — a Postgres database, a Redis cache, a MongoDB database, a NATS JetStream queue, S3-compatible object storage (backed by DigitalOcean Spaces in `nyc3`), a webhook receiver, or a full deployed app stack. The resource works immediately with standard client libraries. No account required, no Docker, no configuration, no SDK.
66

77
Designed for two audiences:
88
1. AI coding agents (Claude, Copilot, Cursor, etc.) that need to provision real infrastructure while generating working application code.

public/llms.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ Response 200:
369369

370370
### POST /storage/new
371371

372-
Provision an S3-compatible object storage bucket. Returns AWS-compatible credentials.
372+
Provision an S3-compatible object storage bucket. Returns AWS-compatible credentials. Production backend is DigitalOcean Spaces (S3-compatible) in `nyc3`. Anonymous-tier buckets are auto-deleted at 24h by a bucket lifecycle rule enforced at the storage layer.
373373

374374
Response 201:
375375
```json

scripts/prerender.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ async function loadRoutes() {
6969
'/docs',
7070
'/blog',
7171
'/use-cases',
72+
// W12 B4: /changelog ships as a real pre-rendered HTML so crawlers
73+
// and a procurement reviewer pasting the URL into a browser both
74+
// see the actual entries on first byte.
75+
'/changelog',
7276
...blogSlugs.map((s) => `/blog/${s}`),
7377
...useCaseSlugs.map((s) => `/use-cases/${s}`),
7478
]

src/App.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@ const UseCasesPage = lazy(() =>
5555
const UseCaseDetailPage = lazy(() =>
5656
import('./pages/UseCaseDetailPage').then((m) => ({ default: m.UseCaseDetailPage })),
5757
)
58+
// ChangelogPage (W12 B4) — public /changelog. The DPA, subprocessor list,
59+
// and trust-residency egress section all reference /changelog as the
60+
// contractual change-notice channel; before this route existed every doc
61+
// that linked to it 404'd. Lazy-loaded for parity with the other
62+
// secondary marketing surfaces (Blog, Docs, Use cases) — a homepage
63+
// visitor doesn't pay for these bytes.
64+
const ChangelogPage = lazy(() =>
65+
import('./pages/ChangelogPage').then((m) => ({ default: m.ChangelogPage })),
66+
)
5867

5968
// Authenticated dashboard surfaces — lazy-loaded. These pages only render
6069
// behind AuthGate (token must be present), so a marketing visitor never
@@ -205,6 +214,10 @@ export function AppRoutes() {
205214
<Route path="/docs" element={<DocsPage />} />
206215
<Route path="/use-cases" element={<UseCasesPage />} />
207216
<Route path="/use-cases/:slug" element={<UseCaseDetailPage />} />
217+
{/* W12 B4: /changelog — referenced by DPA §6 (sub-processor
218+
change notification), subprocessors.md, and trust-residency
219+
egress section. Used to 404 because no route existed. */}
220+
<Route path="/changelog" element={<ChangelogPage />} />
208221

209222
{/* ─── auth surfaces (no chrome, dedicated layout) ───────── */}
210223
<Route path="/login" element={<LoginPage />} />

src/entry-server.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { BlogPostPage } from './pages/BlogPostPage'
4343
import { DocsPage } from './pages/DocsPage'
4444
import { UseCasesPage } from './pages/UseCasesPage'
4545
import { UseCaseDetailPage } from './pages/UseCaseDetailPage'
46+
import { ChangelogPage } from './pages/ChangelogPage'
4647

4748
// SSRRoutes — the SSG-only route tree. Mirrors the public surface of the
4849
// client AppRoutes (everything reachable without auth). The /app/* subtree
@@ -61,6 +62,7 @@ function SSRRoutes() {
6162
<Route path="/docs" element={<DocsPage />} />
6263
<Route path="/use-cases" element={<UseCasesPage />} />
6364
<Route path="/use-cases/:slug" element={<UseCaseDetailPage />} />
65+
<Route path="/changelog" element={<ChangelogPage />} />
6466
<Route path="*" element={<Navigate to="/" replace />} />
6567
</Routes>
6668
)

src/pages/ChangelogPage.tsx

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/* ChangelogPage — public, unauthenticated /changelog.
2+
*
3+
* The contractual change-notice channel referenced by the DPA, the
4+
* subprocessor list, and trust-residency.md ("we notify customers at
5+
* least 30 days before adding or replacing a sub-processor; subscribe to
6+
* the changelog"). Before this page existed the link 404'd and every doc
7+
* that promised customers a change-feed was technically lying.
8+
*
9+
* Reverse-chronological. Each entry: date heading + 3-5 concise bullets.
10+
* Entries are inlined here as a TypeScript array (vs. a content repo /
11+
* markdown loader) because (a) the cadence is low — call it weekly at
12+
* most — and (b) keeping the source in-tree means a single PR ships the
13+
* fix and the entry that documents the fix.
14+
*
15+
* Wrapped in PublicShell so the top nav + footer match the rest of the
16+
* marketing surfaces. Mirrors the IncidentsPage layout vocabulary
17+
* (public-eyebrow / public-h1 / public-sub / public-section) so a
18+
* visitor moving between /status, /incidents, /changelog feels the
19+
* pages are the same surface. */
20+
21+
import { PublicShell } from '../layout/PublicShell'
22+
23+
// ─── types ────────────────────────────────────────────────────────────────
24+
25+
interface ChangelogEntry {
26+
/** ISO date (YYYY-MM-DD). Sort key — newest first. */
27+
date: string
28+
/** Short headline summarising the day's changes. */
29+
title: string
30+
/** 3-5 concise bullets describing what shipped. */
31+
bullets: string[]
32+
}
33+
34+
// ─── content ──────────────────────────────────────────────────────────────
35+
36+
/* Reverse-chronological. Add new entries at the TOP of the array. Keep
37+
* each bullet single-line, no marketing fluff — the audience is a
38+
* procurement reviewer or an on-call engineer checking what changed. */
39+
const ENTRIES: ChangelogEntry[] = [
40+
{
41+
date: '2026-05-14',
42+
title: 'Trust + marketing accuracy pass (W12)',
43+
bullets: [
44+
'DPA + trust-residency aligned on Standard Contractual Clauses (Module Two, controller-to-processor) as the EU/UK transfer mechanism.',
45+
'Subprocessor list expanded with Resend (transactional email), Cloudflare (CDN/DNS), Fastly + GitHub Pages (marketing/docs serving), and Loops (lifecycle email forwarder).',
46+
'Homepage step-02 encryption-at-rest claim narrowed to "vault secrets and stored credentials" — the customer Postgres cluster\'s disk is not blanket-encrypted on the anonymous tier.',
47+
'/changelog is now a real route (was 404; referenced by DPA §6, subprocessor list, and trust-residency egress section).',
48+
'llms.txt and llms-full.txt clarified to call out DigitalOcean Spaces (S3-compatible) as the production object-store backend.',
49+
],
50+
},
51+
{
52+
date: '2026-05-13',
53+
title: 'Hobby Plus tier + W11 dashboard honesty pass',
54+
bullets: [
55+
'Hobby Plus tier ($19/mo) shipped as the middle step in the pricing grid — research-backed triple-tier pricing decoy.',
56+
'Agent error envelope standardised across all provisioning endpoints with `agent_action` next-step hints.',
57+
'security.md + PGP key + DPA + subprocessor list published at /docs/public/* (was 404 from W10 onward).',
58+
'Per-tenant MinIO IAM credentials by default in production — anonymous-tier internal_url scrubbed from response payloads.',
59+
'GitHub auto-deploy webhook live; /status page now consumes real GET /api/v1/status backend.',
60+
],
61+
},
62+
{
63+
date: '2026-05-12',
64+
title: 'DO Spaces production cutover + deploy wedge live',
65+
bullets: [
66+
'Object-storage production backend cut over from in-cluster MinIO to DigitalOcean Spaces (`nyc3`); 24h lifecycle rule enforces anonymous-tier auto-expiry at the storage layer.',
67+
'POST /deploy/new live end-to-end (Kaniko → k8s Deployment → Ingress + cert-manager TLS on *.deployment.instanode.dev).',
68+
'Idempotency-Key replay header honoured on every provisioning endpoint; provisioner-auth regression test bundle added to CI.',
69+
'dashboard-api retired — agent API now serves the dashboard directly. Removes the gRPC bridge that was the source of a long tail of cross-service auth drift.',
70+
],
71+
},
72+
]
73+
74+
// ─── page ─────────────────────────────────────────────────────────────────
75+
76+
export function ChangelogPage() {
77+
const sorted = [...ENTRIES].sort((a, b) => b.date.localeCompare(a.date))
78+
return (
79+
<PublicShell>
80+
<ChangelogStyles />
81+
82+
<section className="changelog-header">
83+
<span className="public-eyebrow">Changelog · public · reverse-chronological</span>
84+
<h1 className="public-h1">
85+
Changelog<span className="dot">.</span>
86+
</h1>
87+
<p className="public-sub">
88+
What changed on instanode. Subprocessor adds, sub-processor swaps, and material
89+
posture changes are announced here at least 30 days in advance — see the{' '}
90+
<a href="/docs/public/dpa.md">DPA</a> and the{' '}
91+
<a href="/docs/public/subprocessors.md">subprocessor list</a> for the formal
92+
commitment.
93+
</p>
94+
</section>
95+
96+
<section className="public-section">
97+
<ol className="changelog-list" aria-label="Changelog entries">
98+
{sorted.map((entry) => (
99+
<li key={entry.date} className="changelog-entry">
100+
<header className="changelog-entry-head">
101+
<time dateTime={entry.date} className="changelog-entry-date">
102+
{formatDate(entry.date)}
103+
</time>
104+
<h2 className="changelog-entry-title">{entry.title}</h2>
105+
</header>
106+
<ul className="changelog-entry-bullets">
107+
{entry.bullets.map((b, i) => (
108+
<li key={i}>{b}</li>
109+
))}
110+
</ul>
111+
</li>
112+
))}
113+
</ol>
114+
</section>
115+
116+
<section className="public-section changelog-links">
117+
<a href="/docs/public/subprocessors.md" className="changelog-link">
118+
Subprocessors
119+
</a>
120+
<span className="changelog-link-sep">·</span>
121+
<a href="/docs/public/trust-residency.md" className="changelog-link">
122+
Trust + residency
123+
</a>
124+
<span className="changelog-link-sep">·</span>
125+
<a
126+
href="mailto:privacy@instanode.dev?subject=Subscribe%20to%20changelog%20notices"
127+
className="changelog-link"
128+
>
129+
Subscribe (email)
130+
</a>
131+
</section>
132+
</PublicShell>
133+
)
134+
}
135+
136+
// ─── helpers ──────────────────────────────────────────────────────────────
137+
138+
function formatDate(iso: string): string {
139+
// Parse manually so we render the same date regardless of viewer
140+
// timezone — a 2026-05-14 entry should never appear as "May 13" to a
141+
// visitor in the Pacific timezone.
142+
const [y, m, d] = iso.split('-').map(Number)
143+
const date = new Date(Date.UTC(y, m - 1, d))
144+
return date.toLocaleDateString('en-US', {
145+
year: 'numeric',
146+
month: 'short',
147+
day: 'numeric',
148+
timeZone: 'UTC',
149+
})
150+
}
151+
152+
// ─── styles ───────────────────────────────────────────────────────────────
153+
154+
function ChangelogStyles() {
155+
return (
156+
<style>{`
157+
.changelog-header { padding-top: 8px; }
158+
159+
.changelog-list {
160+
list-style: none;
161+
padding: 0;
162+
margin: 0;
163+
display: grid;
164+
gap: 28px;
165+
}
166+
.changelog-entry {
167+
border: 1px solid var(--border);
168+
border-radius: 12px;
169+
background: var(--surface);
170+
padding: 24px 24px 20px;
171+
}
172+
.changelog-entry-head {
173+
display: flex;
174+
align-items: baseline;
175+
gap: 14px;
176+
margin-bottom: 14px;
177+
flex-wrap: wrap;
178+
}
179+
.changelog-entry-date {
180+
color: var(--text-faint);
181+
font-family: var(--font-mono);
182+
font-size: 12.5px;
183+
font-variant-numeric: tabular-nums;
184+
letter-spacing: 0.04em;
185+
text-transform: uppercase;
186+
}
187+
.changelog-entry-title {
188+
margin: 0;
189+
font-size: 18px;
190+
font-weight: 500;
191+
color: var(--text);
192+
letter-spacing: -0.005em;
193+
}
194+
.changelog-entry-bullets {
195+
margin: 0;
196+
padding-left: 18px;
197+
color: var(--text-dim);
198+
font-size: 14px;
199+
line-height: 1.6;
200+
}
201+
.changelog-entry-bullets li { margin-bottom: 6px; }
202+
.changelog-entry-bullets li:last-child { margin-bottom: 0; }
203+
204+
.changelog-links {
205+
display: flex;
206+
align-items: center;
207+
gap: 10px;
208+
font-size: 13px;
209+
flex-wrap: wrap;
210+
}
211+
.changelog-link { color: var(--text-dim); transition: color 120ms; }
212+
.changelog-link:hover { color: var(--accent); }
213+
.changelog-link-sep { color: var(--text-faint); }
214+
`}</style>
215+
)
216+
}

src/pages/MarketingPage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,8 @@ export function MarketingPage() {
332332
<div className="mkt-step-num">STEP 02 · PROVISION</div>
333333
<h3>Real infra spins up.</h3>
334334
<p>
335-
Managed Postgres, Redis, or Mongo — encryption at rest, automatic backups, a
336-
connection string in 1.4 s. 24 h TTL on the free tier.
335+
Managed Postgres, Redis, or Mongo — encryption at rest for vault secrets and
336+
stored credentials, a connection string in 1.4 s. 24 h TTL on the free tier.
337337
</p>
338338
<div className="mkt-how-visual" aria-hidden="true">
339339
<div className="r"><span className="k">service</span><span className="v">postgres 16.2</span></div>

0 commit comments

Comments
 (0)