Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .coverage-gates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ include:
- internal/
- pkg/

# Build-time code generators (run via `go generate`, validated by the
# docs-gen freshness test, not by line coverage) — excluded like cmd/.
exclude:
- internal/config/gen
- internal/service/gen

packages:
internal/app: 85
internal/config: 88
Expand Down
21 changes: 20 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -322,13 +322,32 @@ jobs:
docker logs "$cid"
exit 1

# ---------------------------------------------------------------------------
# 8. Docs drift — docs may not name an env/RPC/metric/path that does not
# exist, and the generated reference JSON must be up to date.
# ---------------------------------------------------------------------------
docs-drift:
name: Docs drift
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7

- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: true

- name: go test ./internal/docscheck/...
run: go test -count=1 ./internal/docscheck/...

# ---------------------------------------------------------------------------
# Aggregate gate — easy single required check on `main`.
# ---------------------------------------------------------------------------
ci:
name: CI
runs-on: ubuntu-latest
needs: [lint, tidy, vuln, test, smoke, integration, fuzz, docker]
needs: [lint, tidy, vuln, test, smoke, integration, fuzz, docker, docs-drift]
if: always()
steps:
- name: All required jobs succeeded
Expand Down
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,15 @@ fuzz: ## Fuzz smoke — runs each fuzz target with seed corpus + 15s fuzzing

CONNECT_OPENAPI_VERSION ?= v0.18.0

.PHONY: docs-gen
docs-gen: ## Regenerate the docs reference JSON (config + audit/metrics) from code
$(GO) generate ./internal/config/... ./internal/service/...
@echo "==> docs reference JSON regenerated → docs-site/src/data/generated"

.PHONY: docs-drift
docs-drift: ## Check docs name only real identifiers + generated JSON is fresh
$(GO) test -count=1 ./internal/docscheck/...

.PHONY: proto
proto: ## Regenerate Go stubs + OpenAPI + proto reference from proto
$(BUF) generate
Expand Down
54 changes: 54 additions & 0 deletions docs-site/src/data/generated/audit-events.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"events": [
{
"name": "create_project",
"kind": "AdminAction",
"const": "AdminActionCreateProject",
"description": "AdminActionCreateProject records a CreateProject mutation."
},
{
"name": "delete",
"kind": "TupleOpKind",
"const": "TupleOpDelete",
"description": "TupleOpDelete is a revocation (a tuple was removed)."
},
{
"name": "insert",
"kind": "TupleOpKind",
"const": "TupleOpInsert",
"description": "TupleOpInsert is a grant (a tuple was written)."
},
{
"name": "update_project",
"kind": "AdminAction",
"const": "AdminActionUpdateProject",
"description": "AdminActionUpdateProject records an UpdateProject mutation."
}
],
"metrics": [
{
"name": "authz_batchcheck_items",
"description": "Items per BatchCheck request."
},
{
"name": "authz_check_decisions_total",
"description": "Authorization Check/CheckSet/BatchCheck decisions by namespace, relation, and outcome."
},
{
"name": "authz_check_duration_seconds",
"description": "Authorization decision RPC latency in seconds."
},
{
"name": "authz_decision_errors_total",
"description": "Authorization decision RPC errors (validation + internal)."
},
{
"name": "authz_eval_backstop_total",
"description": "Engine per-request safety backstops that fired, by reason: depth/cycle (graceful fail-closed deny) or budget (read-budget exhausted, ResourceExhausted error). A rising rate signals an abusive tenant or a misconfigured deep/cyclic model."
},
{
"name": "authz_region_refused_total",
"description": "Requests refused because the project's pinned data region differs from this instance's region (data-residency fail-closed)."
}
]
}
139 changes: 139 additions & 0 deletions docs-site/src/data/generated/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
[
{
"name": "GATEWAY_ADMIN_API_SECRET",
"type": "string",
"default": "",
"required": false,
"description": "AdminAPISecret gates the AdminService (project configuration), presented as the `X-Admin-Secret` header. Empty disables the admin RPCs entirely.",
"constraint": "must be a high-entropy value of at least 32 characters"
},
{
"name": "GATEWAY_ADMIN_RATE_LIMIT_PER_MINUTE",
"type": "int",
"default": "30",
"required": false,
"description": "AdminRateLimitPerMinute throttles the admin API per caller (online brute-force protection); non-positive disables the limiter."
},
{
"name": "GATEWAY_ALLOWED_ORIGINS",
"type": "csv",
"default": "",
"required": false,
"description": "AllowedOrigins is the CORS allow-list of browser origins permitted to call the API. Empty allows none."
},
{
"name": "GATEWAY_AUDIT_LOG",
"type": "bool",
"default": "false",
"required": false,
"description": "AuditLog enables an async, append-only audit log of every relation-tuple change and admin mutation (to the structured logger). Default false."
},
{
"name": "GATEWAY_CONNECT_PORT",
"type": "int",
"default": "8080",
"required": false,
"description": "ConnectPort is the Connect/HTTP listener serving JSON, gRPC, and gRPC-Web RPCs."
},
{
"name": "GATEWAY_DATA_REGION",
"type": "string",
"default": "",
"required": false,
"description": "DataRegion is the region this instance serves: it refuses to operate on a project pinned to a different data_region (fail closed). Empty (default) is region-agnostic — serves every project, today's behavior."
},
{
"name": "GATEWAY_DECISION_LOG",
"type": "bool",
"default": "false",
"required": false,
"description": "DecisionLog enables an async, append-only audit log of every Check/ CheckSet decision (to the structured logger). Default false."
},
{
"name": "GATEWAY_DEFAULT_PROJECT_ID",
"type": "string",
"default": "default",
"required": false,
"description": "DefaultProjectID is the project shard used for any request whose project_id is empty."
},
{
"name": "GATEWAY_DEFAULT_TENANT_ID",
"type": "string",
"default": "",
"required": false,
"description": "DefaultTenantID is the tenant pinned for requests that omit tenant_id (the project's default tenant). Empty is the conventional default."
},
{
"name": "GATEWAY_HTTP_MAX_BODY_BYTES",
"type": "int",
"default": "1048576",
"required": false,
"description": "HTTPMaxBodyBytes is the maximum accepted request body, in bytes. Must be positive.",
"constraint": "must be positive"
},
{
"name": "GATEWAY_MAX_BATCH_CHECK_ITEMS",
"type": "int",
"default": "1000",
"required": false,
"description": "MaxBatchCheckItems caps the number of items in a single BatchCheck request, bounding per-request cost."
},
{
"name": "GATEWAY_MAX_CHECK_READS",
"type": "int",
"default": "5000",
"required": false,
"description": "MaxCheckReads caps the number of store reads (tuple lookups) a single Check/CheckSet/Expand/ListObjects evaluation may perform, bounding the per-request cost a pathological cyclic/branching graph can inflict. Non-positive uses the service default.",
"constraint": "when set, must be \u003e= 100 (use 0/negative for the default)"
},
{
"name": "GATEWAY_MAX_EXPAND_NODES",
"type": "int",
"default": "10000",
"required": false,
"description": "MaxExpandNodes caps the size of an Expand result tree, bounding the response a single cheap request can amplify into."
},
{
"name": "GATEWAY_MAX_LIST_OBJECTS",
"type": "int",
"default": "1000",
"required": false,
"description": "MaxListObjects caps the candidate set a single ListObjects call scans, bounding its full-scan + per-object Check cost."
},
{
"name": "GATEWAY_METRICS_PORT",
"type": "int",
"default": "9090",
"required": false,
"description": "MetricsPort serves the Prometheus /metrics endpoint and the health probes."
},
{
"name": "GATEWAY_POSTGRES_AUTO_MIGRATE",
"type": "bool",
"default": "false",
"required": false,
"description": "PostgresAutoMigrate runs the expand migration on boot when true. It defaults to FALSE: migrations are a deliberate operator step (out-of-band `workspace migrate`, an init container, or a migrate Job), so a large existing DB's first deploy can never livelock on a bounded CONCURRENTLY build inside the boot window. Opt in (true) only for small/dev DBs."
},
{
"name": "GATEWAY_POSTGRES_DSN",
"type": "string",
"default": "",
"required": false,
"description": "PostgresDSN selects the postgres driver when set; empty uses memory."
},
{
"name": "GATEWAY_SERVICE_AUTH_TOKENS",
"type": "csv",
"default": "",
"required": false,
"description": "ServiceAuthTokens are the accepted service credentials presented by calling backends as `Authorization: Bearer \u003ctoken\u003e`. This is an internal service authenticated service-to-service (not by end-user tokens); the user is passed as data in the request. Empty disables the requirement — trust the network/mesh — and the service logs a warning."
},
{
"name": "GATEWAY_TENANT_RATE_LIMIT_PER_MINUTE",
"type": "int",
"default": "0",
"required": false,
"description": "TenantRateLimitPerMinute throttles authz data-plane RPCs per (project, tenant); non-positive (the default) disables the limiter.",
"constraint": "when enabled, must be \u003e= 60 (use 0 to disable)"
}
]
10 changes: 7 additions & 3 deletions docs-site/src/pages/docs/deployment/docker.astro
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ docker compose down -v # stop and wipe the postgres volume`}
title="docker compose"
/>
<p>
With <code>GATEWAY_POSTGRES_AUTO_MIGRATE=true</code> (the default) the
schema is applied on first connect, so the stack is ready as soon as
Postgres is healthy.
Migrations are a deliberate operator step:
<code>GATEWAY_POSTGRES_AUTO_MIGRATE</code> defaults to <code>false</code>,
so apply the schema explicitly with <code>workspace migrate</code> (an init
container or a one-shot job) before serving, or set it to <code>true</code>
for a small dev database to migrate on first connect. See
<a href={`${base}docs/installation/configuration`}>Configuration</a> for the
full knob reference.
</p>

<h2>A Compose snippet</h2>
Expand Down
Loading
Loading