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.dev or 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 IDauthenticated users without perIP approval)
*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 endtoend flow validation)

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)

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 OFF
  • environment = "stg" → auth gate ON (lighter), noindex ON
  • environment = "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:

  1. Walks all Koder Jet vhosts in s.forge:/etc/koder-jet/sites.toml
  2. For each, verifies:
    • environment field is declared
    • dev/stg vhosts have auth gate enabled
    • dev/stg vhosts return noindex headers (HTTP HEAD probe)
    • prod vhosts do NOT have noindex (positive check)
    • sitemap.xml is absent from dev/stg
    • sitemap.xml is present from prod
  3. Reports drift to meta/context/handoff/CURRENT.md for 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)

  1. *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.

  2. *ulti-approver eventual*— V1 single approver (rpm32510@gmail.com).

    V2 plans Koder ID admin role expansion.

  3. *ermanent whitelist*— supported per-vhost via permanent_whitelist

    in sites.toml; bypasses email flow.

  4. *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/network

    topology of the 3 environments

  • policies/web-server.kmd — Koder Jet is the official server
  • policies/security.kmd — TLS/HTTPS requirements (apply at all envs)
  • policies/self-hosted-first.kmd — implementation must use Koder Stack

    components

Migration plan

Pre-existing public dev surfaces (everything currently at *.koder.dev that is in fact development-stage):

  1. Inventory current vhosts in s.forge:/etc/koder-jet/sites.toml
  2. Classify each: actuallydev / actuallystg / actually-prd
  3. For misclassifiedasprodbutreally-dev: add environment = "dev" and

    migrate to dev.<slug>.koder.dev subdomain; deploy auth gate

  4. For prod that should stay prod: confirm they're truly launchable; add

    environment = "prd" declaration

  5. 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.dev being 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-housekeep should still flag missing

    gates as drift, or downgrade to advisory

  • Whether the environment = "prd" default for unspecified vhosts

    should 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.

Source: ../home/koder/dev/koder/meta/docs/stack/policies/environments.kmd