Community access, agent scopes, RFC states, and moderation gates.
ZergLang community and RFC governance
Status: launch policy and implementation target. The launch website is read-only; native posting is enabled only after its authentication, abuse, and moderation gates pass.
The public community is readable without an account. People authenticate with a dedicated Zerg SSO client to create topics, reply, react, or subscribe. Automation uses scoped agent tokens rather than browser sessions.
Initial agent scopes are:
forum:readfor topics, posts, and RFC status;forum:writefor topic and reply creation; andrfc:proposefor creating an RFC proposal.
No scope grants moderation or the ability to accept an RFC. Consortium moderators own those actions and every moderation or state transition is recorded in an immutable audit trail.
RFC lifecycle
discussion -> proposed -> accepted -> implemented
`-> rejected
Authors may revise discussion drafts and proposed RFCs through versioned edits. Acceptance freezes the accepted revision. Later corrections create a new revision or follow-up RFC rather than rewriting history.
Safety gates for native posting
Before writes are enabled, the implementation must have behavioral coverage for session rotation, PKCE, CSRF, scoped-token authorization, account and IP rate limits, Markdown sanitization, safe links and attachments, edit history, moderation actions, subscription controls, and audit-log completeness. Content is stored in Fly Managed Postgres; it is never stored only in a browser or ephemeral application filesystem.
Candidate benchmark code and benchmark evidence are not community uploads.
They follow the signed publication path described in
benchmark-publication.md.
Local implementation progress: community Markdown v1
site/lib/content.ts exposes an explicit community-v1 rendering profile for
future native posts. Omitting the profile retains the documentation renderer;
unknown profiles reject. No current route accepts community writes and this
library does not create accounts, sessions, uploads, or persisted posts.
The profile admits at most 32,768 UTF-8 bytes, inclusive, before parsing. Raw
HTML is displayed as text. Fenced code retains its text, including examples of
unsafe URI schemes; sanitization never deletes substrings from code or prose.
There are no author-controlled IDs, inline styles, scripts, media elements, or
automatically loaded images. Markdown images remain text/manual links, not
uploads. Ordinary emphasis, lists, tables, headings and code blocks remain
available; typography and bare-address linkification are disabled. Code classes
are limited to language- followed by ASCII letters, digits, underscores and
hyphens.
Clickable destinations are credential-free absolute HTTPS URLs, root-relative
same-origin paths, or fragments. Other schemes, path-relative destinations,
protocol-relative destinations, malformed hosts, control characters, spaces,
backslashes and ambiguous/nested percent escapes reject as links while their
labels remain readable. Destination validation runs before and after Markdown
normalization; HTML serialization uses a separate tag/attribute/scheme allowlist.
Links carry ugc nofollow noopener noreferrer and cannot set a target or handler.
This is a rendering policy, not a claim that an external HTTPS site is trustworthy.
The implementation reuses installed Markdown and sanitizer dependencies. The sanitizer’s server-side trust guidance still applies: future persistence and read routes must run the selected profile server-side against the authoritative revision. A browser preview cannot supply trusted HTML. No live fetch, upload handling, malicious-file scanning, download headers, attachment ownership, or storage quota is implemented by this profile.
Local implementation progress: versioned topics
site/server/utils/community-store.ts and site/db/community/001-topics.sql
provide an offline-tested PostgreSQL storage library for stable external
issuer/subject identities, author-owned topics, append-only source revisions,
and database-generated creation/edit audit events. Edits require the expected
revision; competing writers produce one committed edit and one conflict.
Reads render the authoritative Markdown with community-v1. Identity registration
never accepts an incoming moderator role. Disabled principals cannot create or
edit topics. Topic pointers, revision authors and audit events are checked
together, including when a database client attempts an unaudited revision insert.
This is not an authentication layer or live community API. It trusts only the identity and actor IDs supplied by a future verified server-side layer, and no current website route constructs the store or accepts writes. The schema is not applied automatically and no deployment, client, moderator, or account is created. History triggers protect normal application SQL; a database owner capable of changing schema or disabling triggers remains a trusted administrator, not an adversary against whom cryptographic immutability is claimed.
Local implementation progress: request policy
site/server/utils/community-access.ts implements the server-side decision on an
already-resolved actor. Anonymous public reads use GET/HEAD; authenticated agent
requests need the exact forum:read, forum:write, or rfc:propose scope. No
combination, duplicate, wildcard, extension scope, or added role claim grants an
agent moderation or RFC acceptance. Only a verified human moderator can receive
those action grants. Disabled actors and unknown actions/roles fail closed.
Browser mutations require a state-changing HTTP method, the exact configured HTTPS Origin, and a raw session-specific CSRF token whose SHA-256 matches the server-held hash using a constant-time byte comparison. This follows the synchronizer-token pattern; the origin is deployment configuration, never selected from a request’s Host or forwarded headers. Token generation, durable session storage, cookies and HTTP adapters are not supplied by this policy. Roles and actor kinds must come from the trusted resolver, not request JSON, and the route must choose its own action. A policy grant is not an RFC state transition or an audit event.
Local implementation progress: durable credentials
site/server/utils/community-credentials.ts and the separate
002-credentials.sql migration provide local, real-Postgres-tested session and
agent-token persistence. Random 32-byte bearer secrets use distinct session and
agent prefixes; only their SHA-256 hashes are stored. A browser session has a
separate random CSRF token, stored server-side so an authenticated page reload
can recover its synchronizer token. The resolver supplies its hash to the request
policy. The CSRF token alone does not authenticate a request.
Issuance requires an already trusted server-side identity/management decision. It does not accept an SSO assertion or make an agent authorized to mint tokens. Supplied malformed, unknown, revoked, expired or wrong-kind credentials reject; they never turn into anonymous identities. Resolved roles and disabled status come from the current local principal, not bearer claims. Agent scopes are a canonical explicit subset of the three listed scopes, with no moderator role.
The local profile requires an explicit lifetime of 1–28,800 seconds for a session or 1–2,592,000 seconds for an agent token. Session rotation generates new bearer and CSRF secrets, atomically revokes the old credential, and preserves its absolute expiry. Two concurrent rotations have one winner. Issuance/revocation audit events and credential authority fields are protected against ordinary SQL rewrites. These controls do not protect against a schema-changing database owner.
These are backend libraries, not live login or posting endpoints. The unmounted HTTP adapter below supplies cookie handling, credential selection, CSRF bootstrap and human-only token management. Automatic privilege-change rotation, idle-session policy and production integration remain open; no production account or secret is created.
Local implementation progress: SSO handshake
site/server/utils/community-sso.ts and 003-sso.sql add a library-only adapter
for Zerg’s confidential-client authorization-code profile. Configuration pins
the issuer, HTTPS endpoints, exact callback, dedicated client credentials and
flow-encryption key. There is no discovery from request data or reuse of another
application’s client. This is an opaque-token exchange followed by trusted
userinfo retrieval, not a generic OIDC ID-token/JWT verifier.
Ten-minute login flows bind browser state to an S256 PKCE verifier. The database stores only a state hash and authenticated encrypted verifier; consumed flows cannot be reopened by the application role. State/cookie mismatch rejects before exchange; consumed, expired, tampered or configuration-mismatched flows reject. A failed or ambiguous exchange needs a fresh login. Provider requests have per-request deadlines and bounded JSON bodies, with redirects refused.
Verified issuer/subject pairs identify local members. Email changes never link accounts; upstream administrator claims never grant community authority. The adapter issues an eight-hour local session and rechecks disabled principals before issuance. Its local identity-provider and PostgreSQL tests do not establish production compatibility or consent/moderator approval.
HTTP login/callback routes, secure browser-cookie handling and live client provisioning are not supplied by this library. The separate request-limits and ephemeral-retention libraries below are also not connected to the public site.
Local request budgets and ephemeral retention
site/server/utils/community-limits.ts and 004-limits.sql provide a durable
PostgreSQL admission limiter. Explicit, copied server policy defines per-action
global, IP and optional verified-account budgets; there are no live defaults.
All budgets are checked and charged together. A denied attempt changes no
counters, and database/lock failures fail closed. Successful admission charges
an attempt even when the subsequent action fails; it is not an exactly-once
business transaction.
IPv4-mapped IPv6 shares the IPv4 bucket. Native IPv6 is grouped by /64 to limit address rotation within that prefix; shared networks can therefore share a budget. Input must come from trusted connection/proxy resolution, not arbitrary forwarding headers. Account IDs must come from a verified local principal, never request-body claims. Stored bucket keys use HMAC; the raw addresses and principal IDs are not stored in counters. Replicas must share the secret and policy; changing either starts different budgets and must be coordinated operationally.
Fixed windows begin at the first admitted request for each bucket and use database time after locking. This is not a sliding-window, distributed-attack, ingress-memory or infrastructure spending safeguard. Production thresholds, proxy trust, ingress/concurrency controls and deployed HTTP enforcement remain integration work. The unmounted adapter below bounds each parsed JSON body.
A separate maintenance library deletes only expired sign-in flows or counters, in bounded batches that skip locked rows. It does not delete posts, credentials or audit history. No scheduler is enabled or production record removed here. Retention is an operational requirement: expiry alone does not reclaim storage. See verification and operational boundaries.
Local HTTP composition: unmounted
site/server/utils/community-http.ts composes the real store, credentials,
SSO, request policy and limiter behind a Request/Response interface. No Nuxt
route mounts it and no database pool is created at site startup. All endpoint,
origin, secret and admission policy configuration is explicit. The host must
resolve a trusted peer address separately; arbitrary forwarded headers are
ignored. An ingress budget precedes credential lookup, with separate action
budgets and verified-account limits on mutations/credential management.
Anonymous topic reads remain available. Supplied malformed/revoked credentials reject instead of silently becoming anonymous. Browser session cookies and agent Bearer tokens cannot be mixed or interchanged. Human mutations use the existing exact-Origin/session-CSRF policy; agents require exact scopes and cannot access browser session or token-management endpoints. Topic edits retain author ownership and revision conflicts. Request JSON cannot supply an actor ID or trusted HTML.
Sign-in initiation requires same-origin POST and a custom request header; single-use callbacks bind state to a secure browser cookie and always return to the configured community page. Existing authenticated browsers must log out before starting another sign-in. Session/flow cookies are host-only, Secure, HttpOnly and SameSite=Lax, permitting the top-level cross-site SSO callback. Rotation preserves absolute expiry; logout revokes server-side. Session recovery rejects invalid credentials with 401 and clears the stale cookie. The future browser must coordinate concurrent credential-changing/recovery responses. Responses are non-cacheable and suppress referrers; tokens are never accepted from query strings. Raw JSON bodies are bounded to 65,536 bytes and a two-second read deadline, with strict UTF-8 and object-field validation. These are not host-level connection or total memory/concurrency limits.
Local implementation progress: discussion replies
The store and unmounted HTTP adapter now support topic-linked replies, their
author-owned versioned edits, and bounded public discussion/revision/audit pages.
005-posts.sql adds the separate reply schema. A topic’s owner cannot edit someone
else’s reply. Disabled principals cannot write; browser mutations require the
existing Origin/CSRF proof and agents need forum:write. Supplied credentials
still need forum:read for reads, including history.
PostgreSQL assigns a topic-local creation cursor under a lock, so concurrent creators cannot publish out-of-order cursors. Edits do not move replies or change their topic, owner or creation order. Revisions and database-generated audit events commit together and reject ordinary SQL rewrites. Audit IDs remain exact decimal strings but are sorted numerically, including across digit boundaries. Default pages contain at most 50 entries; explicit limits are 1–100. These are bounded live reads, not a frozen multi-page export. No topic directory/search, reaction, deletion, moderation action or attachment is supplied by this slice.
Local implementation progress: RFC proposals
006-proposals.sql, the store and the unmounted HTTP adapter support
topic-linked RFC proposals. Any active author may create a discussion draft and
version its title/Markdown; owning the topic does not grant edit authority over
someone else’s proposal. A proposal’s revision counts content snapshots;
version advances on every edit or lifecycle transition. Both edits and decisions
require the observed expectedVersion, so a stale request cannot accept content
or state it did not observe. Concurrent edits/decisions have one committed winner.
Only the author can move discussion to proposed. Verified human local moderators
can accept or reject a proposed RFC and mark an accepted RFC implemented. The
store and database recheck the active local principal/role; the HTTP policy also
rejects agents regardless of their underlying principal’s moderator role.
rfc:propose grants draft creation, author edits and proposing, not decisions.
Public GET/HEAD remains anonymous; supplied agent credentials need forum:read.
Browser mutations retain Origin/CSRF enforcement and shared write budgets.
Acceptance pins acceptedRevision to the current immutable content revision.
Accepted, implemented and rejected proposals do not admit further content edits
or reopening. Corrections use a separate follow-up proposal in the discussion;
this slice does not implement an amendment lifecycle. Every creation, content edit
and transition has an append-only, database-generated audit event containing its
actor, version, revision, prior revision/status and frozen revision. No-op,
skipped, unaudited, cross-owner or misattributed changes reject. Normal application
SQL cannot assign roles, rewrite history or fabricate audit events. Schema-changing
database owners remain trusted administrators.
Topic-local proposal lists and revision/audit histories use the same bounded
live-page contract as replies. A locked topic assigns creation order; changing
content/status does not reorder a proposal. Current reads render authoritative
Markdown with community-v1, not incoming HTML. No configuration, migration,
role assignment or route is applied at site startup. Local moderator assignments
in tests are not consortium acceptance, production grants or go-live approval.
Local implementation progress: private subscriptions
007-subscriptions.sql, the store and the unmounted HTTP adapter add a durable
topic-follow preference owned by the verified human account. There is no public
subscriber directory, account-ID request parameter or moderator override. Agent
tokens cannot read or change these personal settings; their existing scopes are
unchanged. All preference reads require a current human session. Mutations also
require the existing Origin/CSRF proof and shared account/IP write budget.
An untouched topic defaults to unsubscribed, version zero, without a stored row. First opt-in creates version one; actual on/off changes advance the version and append a database-generated audit event. Repeating the same desired state changes neither version nor history. Opt-out retains the preference and its immutable history; it is not account/data deletion. Different concurrent intents are serialized, with the last committed preference taking effect. These are not optimistic-version requests or an exactly-once notification protocol.
Private account pages include both active and opted-out preferences, in stable account-local first-opt-in order. Defaults/limits are 50/1–100, with exact decimal cursors. A topic-specific private audit page records on/off actions and versions. The current active principal is rechecked on all reads and writes. SQL constraints, triggers and narrow grants preserve ownership and history; trusted application code must still pass the verified actor, and schema-changing owners remain trusted.
This stores topic-follow controls only. It does not deliver email, create an inbox, run a notification job, notify proposal participants or implement independent per-RFC subscriptions. No preferences, browser UI or route are activated on the public website. A future delivery design must honor the stored opt-out and have separate tests and deployment approval.
Local implementation progress: audited hide and restore
008-moderation.sql, the store and the unmounted HTTP adapter add local human
moderator hide/restore decisions for topics, replies and RFC proposals. The current
local role and disabled status are checked inside each storage transaction; agent
tokens cannot make decisions or read private moderation reviews, even when their
underlying principal has a moderator role. Browser decisions also require the
existing exact-Origin/session-CSRF proof and shared account/IP write budget.
Each decision requires the observed moderation expectedVersion, a desired
boolean hidden value, and a nonblank reason of at most 500 Unicode code points
without ASCII control characters or lone surrogates. Untouched content is visible at version zero. The
first hide creates version one; each subsequent hide/restore advances the version
and appends a database-generated audit event containing the actor, reason and
resulting visibility. Stale decisions and no-op transitions conflict rather than
adding duplicate history. The moderation version is separate from content/RFC
versions. Role assignments in tests are fixture-only, not consortium approval.
Hidden content returns the same public not-found response as absent content, including its public revision/audit history. Hiding a topic also hides its reply and RFC lists, direct child reads and child histories; author edits, new replies, new RFCs and RFC lifecycle transitions are unavailable while hidden. A child can also be hidden independently. Restoring a topic does not restore independently hidden children, and restoring a child does not expose it under a hidden topic. List queries filter visibility before applying the page limit. Restored entries retain their original creation cursor; these remain live pages, not a historical snapshot or a guarantee that an already-passed cursor sees a later restoration.
Private moderator review returns the authoritative current content with its own
moderation state, and a separate bounded private audit page supplies decision
history. The child’s own hidden flag is not the inherited topic state; moderators
can review the parent separately. Public endpoints never bypass visibility merely
because a moderator session is supplied. Reasons and moderator-review responses
are not public. Original source revisions, original edit/lifecycle audits and
accepted RFC revision pointers are retained unchanged. Hide is neither physical
deletion nor a legal erasure mechanism; restore does not reopen a frozen RFC.
Topic subscription settings and their account-private history remain readable while the topic is hidden. Existing followers can opt out, including via the unmounted HTTP adapter; attempting an opt-in while hidden returns not found. Remembered preferences expose only the already-owned topic ID and preference, not hidden content or moderation reasons. There is still no notification delivery.
Public reads lock the content pointer before checking committed visibility. Moderation serializes on that same pointer, including first decisions; parent topic locks precede child locks. Database triggers also lock direct SQL decisions and enforce active moderator attribution, sequential versions and immutable audit history. Ordinary SQL cannot rewrite original content via a moderation decision. The application SQL role remains a trusted boundary: the server must supply the verified actor and apply human-only HTTP policy; this is not per-user SQL RLS or protection against a schema-changing database administrator. See the separate verification record for grant and concurrency tests.
The remaining work at this stage included browser UI and mounted/deployed SSO/session/CSRF integration, attachment delivery, and operational moderation workflows. Those require their own public behavioral and local identity-provider/Postgres tests. ZER5-14 continues to own independent production registration, deployment, moderator/acceptance ownership and go-live decisions.
Local implementation progress: immutable text attachments
009-attachments.sql and the unmounted store/HTTP adapter implement a deliberately
limited attachment profile. Authors can attach UTF-8 text to their own visible
topics, replies or RFC proposals. Each file has 1–32,768 UTF-8 bytes; tabs, carriage
returns and newlines are preserved, while other ASCII controls, DEL and lone
surrogates reject. The basename has 1–80 ASCII letters/digits/hyphens/underscores,
starts with a letter or digit, and ends in lowercase .txt. Path separators,
additional extensions, whitespace and user-supplied MIME types are not accepted.
Text is not normalized or rendered as HTML.
The adapter accepts exactly filename, text and expectedVersion in its
existing bounded JSON body. The complete encoded request must also fit the
existing 65,536-byte transport limit, so heavily JSON-escaped text may reach that
limit before reaching the text-byte limit. There is no multipart, arbitrary
binary, image, archive or remote-URL ingestion path. This is not a malware scanner
and makes no claim that downloaded text is safe to execute manually. No uploaded
content is executed, fetched, expanded or automatically previewed by this path.
Candidate benchmark programs/evidence remain outside community uploads and retain
their separate publication process.
Upload authorization uses the verified active principal, content ownership and
the observed current version: topic/reply revision, or RFC lifecycle version.
An RFC upload is allowed only during discussion/proposed status; acceptance,
implementation and rejection freeze new uploads as well as source edits. A
moderator cannot upload on another author’s behalf. Browser uploads require the
existing session/Origin/CSRF proof; agent uploads need forum:write for topics or
replies, or rfc:propose for RFCs. Token reads require forum:read; unauthenticated
public reads remain allowed. All routes share the existing ingress and read/write
budgets rather than introducing a bypass or a new authority-bearing scope.
The attachment row itself is its append-only upload record: it retains a stable ID, target kind/ID, uploader, content revision, observed version, target-local order, original constrained filename, stored bytes and a database-generated SHA-256 digest/byte count. The database also records creation time. Content revision foreign keys pin provenance; no edit, delete or replacement endpoint is provided. The record cannot be updated/deleted/truncated through normal application SQL. It is independent of the unchanged content-edit and moderation audit streams. Listings include uploads from retained earlier revisions, with explicit provenance; they do not silently retarget old uploads to revised prose.
Each target has eight lifetime upload slots across all its revisions; each author has 128 lifetime slots across all targets. At the maximum individual file size, these bound retained payload bytes to 256 KiB per target and 4 MiB per author, excluding database/index overhead. Hidden files still consume slots; retries are new uploads, not content-digest deduplication. Quota exhaustion returns conflict and requires a future explicitly designed retention/quota workflow—there is no automatic deletion, reset or hidden capacity increase. These are not a global deployment storage cap or a replacement for production capacity planning.
Account and target locks serialize quota checks, provenance and creation. Direct
SQL inserts receive the same ownership/version/frozen/visibility/quota checks;
generated fields and target-local positions are not trusted request claims. The
server-supplied actor remains a trusted application boundary, not per-user SQL RLS.
The separate migration grants no runtime privileges; an application needs SELECT
and INSERT only on target_kind, target_id, author_id, revision, version,
filename, content, plus the existing content/principal read/locking privileges.
Schema-changing administrators remain trusted, as for the existing history tables.
The local adapter’s paths are:
GET/HEAD/POST /api/community/v1/{topics|posts|proposals}/:id/attachments;GET/HEAD /api/community/v1/attachments/:idfor the download.
Listing uses exclusive target-local decimal after cursors and the existing
50-default/1–100 explicit page bounds; a target can hold at most eight entries.
Download responses use application/octet-stream, forced attachment disposition
with a server-added community- filename prefix, exact byte length, nosniff,
no-store/no-referrer, restrictive CSP and no permissive CORS. HEAD has the same
headers without bytes. Unknown and hidden attachments return public not found.
Hiding either the owner content or its parent topic also blocks listing, downloading
and uploading, including for moderator sessions on public routes. Restore reveals
the same retained bytes. There is no independent per-file hide action or private
hidden-file preview; moderators hide the containing item.
No migration, upload directory, object-store connection, production route or posting control is activated. The local browser workflow verification below extends this adapter’s integration evidence. Production SSO registration, deployment, consortium ownership and go-live remain separately gated by ZER5-14.
Local browser workflow verification
The separate site/browser/ suite drives Chromium against the real unmounted
adapter, PostgreSQL and a local HTTPS identity provider. The community, provider
and attacking document have different .test sites mapped to loopback, so this
checks actual cross-site browser behavior rather than manually supplying a
Cookie or Origin header. The application uses narrow SQL grants; test-only owner
provisioning grants a local moderator for decision checks, never upstream role
claims or an agent token.
Coverage includes the S256 callback, host-only/HttpOnly/Lax cookies, rejected cross-site writes, CSRF failure, sequential session recovery/rotation/logout, revoked-cookie cleanup, stolen callbacks, provider/challenge failure and a fresh retry. Discussion checks create/edit replies, render server-sanitized Markdown in a browser document, follow/unfollow, download exact attachment bytes with the forced filename, and hide/restore content. RFC acceptance remains human-only and freezes the accepted source; independent anonymous readers observe stored content and audit history. Tests do not automate executing any uploaded text.
This is fixture-level browser integration, not the native website UI. It does
not implement a directory/search, concurrent-tab/session-response coordination,
operational moderation staffing, notification delivery or a mounted endpoint.
The existing public /community page remains informational. Self-signed fixture
certificate errors are ignored only in disposable browser contexts; public PKI,
live provider compatibility, production ingress/proxy behavior and Firefox/WebKit
are not verified here. No production approval is inferred from these tests.