Environments policy — dev / stg / prd
Policy — Environments (dev / stg / prd)
*hy this policy exists.*During development, web pages are published at public internet addresses for review and iteration. Without an access control layer, those pages can be indexed by search engines, scraped by AI training crawlers, and shared (intentionally or accidentally) before they're ready. Branding, naming, copy, and visual identity are still in flux during dev — premature exposure creates SEO debt, brand contamination, and reputation risk that's expensive to undo later.
This policy defines what environment a deployment belongs to, and what access controls apply at each level.
Scope
Applies to *very web surface published from the Koder Stack to a public internet address*
- Landing pages (product, sector, area, institutional, catalog, spec)
- Web apps (admin, dashboards, consoles, Hub client, etc.)
- Documentation sites
- Developer-facing portals
- API gateway pages (auth flows, callbacks)
- Status / observability dashboards
- Any subdomain of
koder.devor any other Koder-controlled domain
Does *ot*apply to:
- Local dev (localhost, .local, .lan)
- VPN-only internal services not bound to public IPs
- Email-only services
Environment classification
Three environments per meta/context/infrastructure/environments.md:
| Environment | Code | Purpose | Domain pattern | Audience |
|---|---|---|---|---|
| *evelopment* | dev |
Active development, iteration on namingdesigncopy | dev.<slug>.koder.dev |
Author + invited reviewers |
| *taging* | stg |
Pre-launch validation, stakeholder review | stg.<slug>.koder.dev |
Internal team + selected externals |
| *roduction* | prd |
Public launch | <slug>.koder.dev (no env prefix) |
Public |
Every Koder Jet vhost MUST declare environment = "dev" | "stg" | "prd" in its sites.toml block. Vhosts without explicit environment declaration default to *dev`*(fail-safe — most restrictive).
Environment codes (3-letter canonical)
The codes dev / stg / prd are the *nly*accepted environment identifiers across the Koder Stack. Always 3 letters, always lowercase. Used uniformly in:
| Surface | Format |
|---|---|
| URL prefix (dev/stg only) | dev.<slug>.koder.dev, stg.<slug>.koder.dev |
sites.toml environment field |
"dev", "stg", "prd" |
| Env vars | KODER_ENV=dev, KODER_ENV=stg, KODER_ENV=prd |
| Branch names, log fields, métricas, dashboards | dev, stg, prd |
| Hostnames / LXC names | s.<slug>-dev, s.<slug>-stg, s.<slug>-prd (or no suffix for prd) |
*orbidden spellings:*staging (use stg), production / prod (use prd), development (use dev). Spelled-out forms are acceptable only in human-readable prose (e.g., headings, narrative description) — never in machine-consumed fields.
Production is the only env without a URL prefix — prd appears in configlabelsenv vars but never in the address bar.
Access control rules
Dev environment
| Layer | Required? | Mechanism |
|---|---|---|
| *uth gate (email approval flow)* | *equired* | Inbound IP must be in 24h whitelist OR trigger email approval flow to admin (see § Email approval flow) |
*noindex / nofollow`* |
*equired* | <meta name="robots" content="noindex, nofollow"> + X-Robots-Tag: noindex, nofollow header + robots.txt Disallow: / |
| *itemap* | *orbidden* | sitemap.xml MUST NOT be served from dev |
| *dev.` subdomain prefix* | Strongly recommended | URL itself signals environment in any screenshot that includes the address bar |
| *isible UI banner* | *orbidden by default* | Banner pollutes design evaluation, the primary activity in dev. Activatable per-vhost only with explicit justification (e.g., demo to unaligned external stakeholder) |
| *emantic markup* | Recommended | <meta name="koder-env" content="dev"> and X-Koder-Environment: dev header for tooling |
| *eofencing* | Optional | Pre-filter at gate: block IPs outside Brazil before triggering email flow (reduces approval spam from bot scans) |
Staging environment
| Layer | Required? | Mechanism |
|---|---|---|
| *uth gate* | *equired* | Same email approval flow as dev, but with longer TTL (7 days) and more permissive whitelist (allow Koder ID |
*noindex / nofollow`* |
*equired* | Same as dev |
| *stg.` subdomain prefix* | Required | Mandatory in stg |
| *isible UI banner* | Optional | More acceptable here than dev (review activity is less about pure visual evaluation, more about end |
Prod environment
| Layer | Required? | Mechanism |
|---|---|---|
| *uth gate* | *orbidden by default* | Prod is public. Opt-in only for specific endpoints that need auth (admin panels, etc. — those use Koder ID directly, not the email approval flow) |
| *noindex`* | *orbidden by default* | Prod opts into indexing. Per-page noindex allowed for legitimate reasons (login pages, admin) |
| *itemap* | Required | sitemap.xml published per spec |
| *TTPS* | Required | Per policies/security.kmd |
Email approval flow (dev/stg)
When a request comes in from a non-whitelisted IP to a dev/stg surface:
1. Visitor → kora.dev.koder.dev (any path)
↓ Koder Jet auth gate intercepts
2. Gate responds with 403 + access-request page:
"Access pending. An approval request has been sent to the
site administrator. Reload in 30s to retry."
↓ Gate enqueues approval request
3. Email sent to configured admin (default: rpm32510@gmail.com):
Subject: "[Koder Access] kora.dev.koder.dev"
Body (English):
"Access requested.
URL: https://kora.dev.koder.dev/<path>
Origin IP: 177.x.x.x (Geo: BR/SP)
User-Agent: <ua>
Timestamp: 2026-05-02 14:30 BRT
[Review request →]"
The link goes to confirm.koder.dev/req/<token> — does NOT auto-approve.
↓
4. Admin clicks link on phone/desktop → loads confirmation page (auth via Koder ID).
Page shows request details + two buttons:
[✅ APPROVE — 24h] [❌ DENY]
Buttons are POST requests with CSRF token. Form action is idempotent
per-token (cannot re-vote on same request).
↓
5. On APPROVE: gate adds origin IP to whitelist with TTL = 24h (dev) or
7d (stg). Visitor's next request passes.
On DENY: visitor's next request continues to see 403.
↓
6. After TTL expires, IP is removed from whitelist automatically.Why no clickable buttons in the email itself
Email scanners (Gmail, Bitdefender, Microsoft Defender, etc.) *refetch all links to scan for malware* If the email contained "Yes/No" buttons as GETrequest links, the scanner would autotrigger them before the user sees the email. Approval would happen 100% automatically — defeating the purpose.
Approval MUST happen via a POST request from a deliberate human click on a confirmation page that is itself behind authentication. The email provides only a single neutral link to that confirmation page.
This pattern matches GitHub's permission requests, Vercel's preview deployment auth, AWS account access requests, etc.
Rate limiting (anti-spam)
- Per origin IP: max 1 approval-request email per 10 minutes
- Per admin inbox: max 10 pending requests at any time (additional
requests get auto-deferred message, not email)
- Per token: single-use; once approved or denied, token is consumed
- Scanner protection: confirmation page requires human interaction
(Koder ID session + CSRF token + POST)
Geofencing (recommended for dev)
Pre-filter at gate: requests from outside Brazil get a passive 403 without triggering the email flow. This prevents bot-scan noise from flooding the admin's inbox.
Configurable per-vhost. Default ON for dev, OFF for stg.
Permanent whitelist (admin convenience)
Per-vhost configuration may declare permanent IPs (admin's office, home network) that bypass the approval flow:
[sites."kora.dev.koder.dev"]
environment = "dev"
permanent_whitelist = ["177.123.45.67/32", "192.168.1.0/24"]These IPs never trigger the email flow. They're effectively pre-approved.
Multi-approver model
V1 implementation: single approver per vhost (default rpm32510@gmail.com).
V2 (planned): approver can be any account with admin role on the relevant Koder ID tenant. Email goes to all admins; first to approve wins.
Implementation components
Per policies/self-hosted-first.kmd, every component is a Koder Stack self-hosted module:
| Component | Role |
|---|---|
*oder Jet*(infra/net/jet/) |
Auth gate middleware; intercepts requests, manages whitelist TTL, serves access-pending page |
| *ervicesfoundationreporter*or *mail* | Sends approval-request emails |
| *oder ID*(already in production) | Authenticates admin on the confirmation page |
*V store*(infra/data/ — kdb-next or similar) |
Stores whitelist entries with TTL, pending request tokens |
| *oder Eye*(observability) | Logs approval/deny events for audit |
Auto-flip on environment promotion
Environment is declared in koder.toml [deployment.environment] and the Koder Jet vhost [sites.<host>] environment field. Promoting a module from dev → stg → prd is a config change, not a code change:
environment = "dev"→ auth gate ON, noindex ON, banner OFFenvironment = "stg"→ auth gate ON (lighter), noindex ONenvironment = "prd"→ auth gate OFF (default), noindex OFF (default)
CI/CD pipeline MUST refuse to deploy a build whose koder.toml env doesn't match the target Koder Jet vhost env (prevents accidental dev build going to prod vhost or vice versa).
Daily audit
/k-housekeep includes a daily audit that:
- Walks all Koder Jet vhosts in
s.forge:/etc/koder-jet/sites.toml - For each, verifies:
environmentfield is declared- dev/stg vhosts have auth gate enabled
- dev/stg vhosts return
noindexheaders (HTTP HEAD probe) - prod vhosts do NOT have noindex (positive check)
- sitemap.xml is absent from dev/stg
- sitemap.xml is present from prod
- Reports drift to
meta/context/handoff/CURRENT.mdfor next session
Exceptions (whitelisted)
A vhost may opt out of any rule with explicit justification in koder.toml:
[deployment]
environment = "dev"
exceptions = ["banner_required"] # if visible banner is needed for a demo
exceptions_justification = "Demo to external stakeholder 2026-05-15"
exceptions_expire = "2026-05-20"Exceptions autoexpire and require rejustification.
Defaults summary (the 4 scope questions, answered)
- *omain scope*— gate applies to dev/stg only by default. Prod
stays public. Specific prod URLs may opt into auth (admin panels, etc.) using Koder ID directly, not the email approval flow.
- *ulti-approver eventual*— V1 single approver (
rpm32510@gmail.com).V2 plans Koder ID admin role expansion.
- *ermanent whitelist*— supported per-vhost via
permanent_whitelistin sites.toml; bypasses email flow.
- *eofencing*— optional, configurable per-vhost; default ON for dev
(block non-Brazil to reduce inbox spam from scanners), OFF for stg.
Cross-references
meta/context/infrastructure/environments.md— physical/networktopology of the 3 environments
policies/web-server.kmd— Koder Jet is the official serverpolicies/security.kmd— TLS/HTTPS requirements (apply at all envs)policies/self-hosted-first.kmd— implementation must use Koder Stackcomponents
Migration plan
Pre-existing public dev surfaces (everything currently at *.koder.dev that is in fact development-stage):
- Inventory current vhosts in
s.forge:/etc/koder-jet/sites.toml - Classify each: actually
dev / actuallystg / actually-prd - For misclassified
asprodbutreally-dev: addenvironment = "dev"andmigrate to
dev.<slug>.koder.devsubdomain; deploy auth gate - For prod that should stay prod: confirm they're truly launchable; add
environment = "prd"declaration - Update DNS where subdomain changes are required
Tracked in implementation ticket projects/koder-stack/backlog/in-progress/119-environments-policy-implementation.md (orchestration) + infra/net/jet/backlog/in-progress/106-auth-gate-feature.md (gate feature) + jet#107..#114 (technical breakdown).
Lifecycle of the email-approval gate
The emailapproval flow is a *tacklifecycle-bound mechanism* not a permanent architectural fixture. It is heavily used during the current acceleration phase of the Koder Stack — a phase characterized by:
- Most vhosts under
*.koder.devbeing in active iteration on naming,design, copy, brand
- Premature exposure carrying real SEO / brand contamination cost
- Few vhosts having reached public-launch maturity
As more components reach homologação and graduate to environment =
"prd", the gate's footprint shrinks naturally — no policy change required, just per-vhost reclassification. The expected trajectory:
| Stack phase | Typical vhost mix | Gate usage |
|---|---|---|
| *cceleration*(current — through homologação) | mostly dev/stg | heavy; default for any new vhost |
| *ost-homologação* | mixed devstgprd | moderate; only vhosts on uniterated surfaces |
| *ature stack* | mostly prod | dormant; gate code stays available for ad-hoc dev surfaces, debug endpoints, internal APIs that need explicit approval |
The mechanism is kept available indefinitely (the auth-gate code is a generally useful capability — same mechanism can protect debug endpoints, internal APIs, stg copies of prd surfaces, etc.), but its *efaultonforalldev-vhosts*behavior is tied to this phase. A future revision of this policy may remove the devdefaultrequired posture once the dev surface footprint shrinks below a threshold worth auditing.
*alibration trigger:*when the user signals "Koder Stack is in homologação", revisit this section and consider:
- Whether the daily audit in
/k-housekeepshould still flag missinggates as drift, or downgrade to advisory
- Whether the
environment = "prd"default for unspecified vhostsshould be reversed (currently fail-safe; may become noise later)
- Whether to deprecate the email-approval flow in favor of pure Koder
ID auth on a per-vhost basis
Do not recalibrate unilaterally — wait for the user's explicit signal, analogous to the /k-go Modo D recalibration trigger documented in the slash command.
Status
*raft — 20260502.*Ratification gated by the implementation ticket landing (Koder Jet auth gate + Kmail integration + audit script). Until ratified, this policy is *dvisory*— vhosts SHOULD comply but won't be audited automatically. After ratification, daily audits in /k-housekeep flag drift.