Skip to main content

0050 REPL-driven namespace gates

The workflow implementation is Clojure (gwbuild.gate.*), using the existing Foundation code.manage, code.test and repository clj-kondo hooks. It is not a second generator, a shell/Python harness, or a Hara migration. Delivery is tracked in task #107.

Mandatory order

GateOwned behaviorEvidence required before advancing
:commongwdb.common.type-*, fn-*, data-*Direct JDBC PostgreSQL behavior: rows, constraints, defaults, extension reuse and cleanup; function results, branches and errors
:fngwdb.fn.<actor>.*Common dependencies already passed; direct JDBC calls testing actor identity, authorization, state transitions, invalid input and failures
:buildgwbuild contract and rendering ownersMetadata/IR, auth policies, argument/return contracts, naming, rejection cases and deterministic rendering
:rpcGenerated gwdb.rpc.<actor>.*Regenerate through gwbuild, compare checked-in output and a second render; invoke the real wrapper in PostgreSQL, including permission/error behavior
:linkGenerated gwlink.backbone.rpc, gwlink.backbone.viewRegeneration parity/idempotence; route/view IDs, schemas, bindings, notifications and errors
:backboneRemote adapter, attach and selected headless modelReal model/adapter behavior: pending/success/failure, subscriptions, cache updates and teardown

Platform presentation and browser E2E come after the headless contract. The namespace gate does not claim to run browser E2E, PostgREST, GoTrue, or deployment validation. Add those checks for the actual slice; do not substitute them for direct domain tests.

Neither source nor test helpers in :common/:fn may depend transitively on generated RPC, gwlink, the backbone, or gwbuild. Common also cannot depend on actor functions. Do not bootstrap domain tests with main/sql/full.sql, generated RPC SQL, or gwdb.test.api-helper. Use l/rt:setup for the source dependency closure. SQL emission, catalog presence, pointer existence and type-only checks are useful supplements, never behavioral certification.

REPL-first authoring and scaffolding

Run from backend/. Discover the existing REPL with clj-nrepl-eval --discover-ports; connect only to a server belonging to this checkout. Start a project-configured Leiningen REPL on an unused loopback port if needed. Preserve the project's JVM module-open options. Do not stop or restart another developer's REPL.

  1. Inspect the implementation, grammar, neighboring tests, registries and linked issue. Select one namespace/slice and inspect lein gate plan NAMESPACE.

  2. Evaluate the candidate in the REPL. Do not load higher-layer helpers to make a lower-layer test pass. For exact multiline source, use load-string with the source as a string: automatic delimiter repair must not silently change it.

  3. Save the stable source, then generate its test inventory with code.manage:

    (require '[code.manage :as manage])
    (manage/scaffold '[gwdb.fn.super.super-manage-user] {:write false})
    (manage/scaffold '[gwdb.fn.super.super-manage-user] {:write true})
  4. Hand-write assertions from each function's contract in the generated, path-matched test file. Every function/macro needs a ^{:refer ns/symbol} fact. A generated TODO or (some? pointer) => true is unfinished work.

  5. Prove a meaningful assertion fails with deliberately wrong behavior or an incorrect expected result. Restore the correct candidate, reload, and prove it passes. Record red/green evidence in the issue/PR. Scaffolding is an inventory, not automatic test authorship.

  6. Require empty manage/missing, manage/incomplete, and manage/unchecked results for the namespace. No :no-test/:unchecked exemptions for new gated behavior. Then run the namespace gate from the REPL or CLI.

  7. Only after the current gate passes should work move to the next layer. Never edit generated wrappers or bindings as a workaround.

The gate structurally rejects common weak-test patterns and then runs the real suite. Static analysis cannot prove that every assertion is meaningful: review the semantic contract and red/green evidence as well.

Direct PostgreSQL test shape

The source-matched test declares l/script- :postgres, not an RPC/HTTP helper:

(ns gwdb.common.fn-example-test
(:use code.test)
(:require [lang.core :as l]
[scaffold.supabase.gw-dev :as gw-dev]))

(l/script- :postgres
{:runtime :jdbc.client
:config (gw-dev/test-gw-config)
:require [[gwdb.common.fn-example :as d]]})

(fact:global
{:setup [(l/rt:restart) (l/rt:setup :postgres)]
:teardown [(l/rt:teardown :postgres) (l/rt:stop)]})

^{:refer gwdb.common.fn-example/double-value}
(fact "evaluates database behavior including boundaries"
(d/double-value 21) => 42
(d/double-value 0) => 0
(d/double-value -2) => -4)

This is a template; use the actual domain contract, fixtures and inverse/cleanup assertions. Do not run this against the ordinary checkout's scaffold config: that config can refer to the shared development stack. Run it via the gate, which replaces only the snapshot's infrastructure scaffold with an owned JDBC adapter. Domain source and tests are copied unchanged. Shared-stack start, stop, migration and HTTP helpers are deliberately unavailable.

Each Clojure namespace should keep only host-library requires (lang.core, test helpers, and similar Clojure infrastructure) in its ns :require. A Postgres namespace declared in an l/script, l/script-, or l/script+ :require must not be repeated in the namespace declaration; the gate reports :gate/redundant-script-require and names the import to remove. This keeps the Hara module link as the single owner of Postgres aliases while avoiding a second Clojure-side declaration. JavaScript and Lua imports remain host-visible when the Clojure test/source itself calls those namespaces.

Commands and evidence

lein gate plan gwdb.fn.super.super-manage-user --through backbone
lein gate check gwdb.common.type-user --through common
lein gate check gwdb.fn.super.super-manage-user --through fn
lein gate check gwdb.fn.super.super-manage-user --through build
lein rpc super/super-manage-user
lein gate check gwdb.fn.super.super-manage-user --through rpc
lein link
lein gate check gwdb.fn.super.super-manage-user --through link
lein gate check gwdb.fn.super.super-manage-user --through backbone
lein gate status gwdb.fn.super.super-manage-user
lein gate changed

lein link writes monolithic modules, so every changed affected slice must have an RPC receipt first; one slice's receipt cannot authorize the others. Similarly, SQL generation requires affected slices through :link. The source and actor registries remain the owners of target discovery. A common namespace checked through a later stage includes dependent registered actor targets.

backend/gates.edn owns stage classification, forbidden dependencies, the generator owner list, tool/image pins, and downstream pipeline namespaces. enrolled keeps selected slices gated even when their Git diff is clean. Changed sources, corresponding tests and their transitive local helpers are in scope. Changes to a generated wrapper map back to its source owner. Manifest, project, resource and kondo changes are treated as generator-wide changes; generator-owner changes affect all registered targets. Add the slice's actual headless model dependencies when expanding the default pipeline.

The runner stops at the first failed or blocked prerequisite and reports later entries as pending. It requires nonzero executed assertions, zero failures, exceptions and timeouts. A clean diff is :not-required, never a passing test. Missing source/test files, tool drift, unavailable Docker, legacy weak suites, and stale generated output are not success.

Receipts live in ignored backend/.gate-state/. They include stage results and a conservative whole-backend fingerprint of source/tests, resources, manifest, kondo hooks/version, JVM options and dependency file metadata. Any relevant change invalidates evidence; changes during a check reject the result. Receipts are local workflow evidence, not signed security attestations. Deleting them resets gate state to pending. Do not commit receipts or add bypass flags.

Isolation, cleanup and CI

Each check uses an owned temporary snapshot and a fresh JVM. PostgreSQL stages create a fresh, random, labeled container using the pinned locally installed image, an ephemeral loopback port and an ephemeral password. No application volumes or configuration are mounted/copied. PostgreSQL readiness checks TCP, not the initialization server's temporary Unix socket. The stage tears down its container on success or failure; a parent-owned ledger also enables cleanup after worker timeout. Snapshot teardown is ownership-checked and idempotent. Do not log JDBC passwords or return raw database exception objects in receipts.

The required native clj-kondo version is in gates.edn; the runner uses the existing repository hooks, not a globally disabled linter. Install/pre-pull the pinned PostgreSQL image explicitly; checks never pull an unpinned image.

Central CI remains authoritative (.github/workflows/build.yml dispatches to greenways-ai/greenways-ci). On a checkout of the tested head SHA, the central backend job should set CI=true and GW_GATE_BASE to the event's actual base commit, then invoke lein gate changed before generation. A CI invocation without GW_GATE_BASE fails closed. Local defaults compare the worktree with HEAD, including untracked and deleted Clojure files. Do not use a clean HEAD diff to certify a committed change.

The central CI job/branch protection is a separate integration surface; this repository implementation does not claim that remote job has been updated or run. Preserve existing CI contexts and the delivery architecture in epic #74.