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
| Gate | Owned behavior | Evidence required before advancing |
|---|---|---|
:common | gwdb.common.type-*, fn-*, data-* | Direct JDBC PostgreSQL behavior: rows, constraints, defaults, extension reuse and cleanup; function results, branches and errors |
:fn | gwdb.fn.<actor>.* | Common dependencies already passed; direct JDBC calls testing actor identity, authorization, state transitions, invalid input and failures |
:build | gwbuild contract and rendering owners | Metadata/IR, auth policies, argument/return contracts, naming, rejection cases and deterministic rendering |
:rpc | Generated gwdb.rpc.<actor>.* | Regenerate through gwbuild, compare checked-in output and a second render; invoke the real wrapper in PostgreSQL, including permission/error behavior |
:link | Generated gwlink.backbone.rpc, gwlink.backbone.view | Regeneration parity/idempotence; route/view IDs, schemas, bindings, notifications and errors |
:backbone | Remote adapter, attach and selected headless model | Real 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.
-
Inspect the implementation, grammar, neighboring tests, registries and linked issue. Select one namespace/slice and inspect
lein gate plan NAMESPACE. -
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-stringwith the source as a string: automatic delimiter repair must not silently change it. -
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}) -
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) => trueis unfinished work. -
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.
-
Require empty
manage/missing,manage/incomplete, andmanage/uncheckedresults for the namespace. No:no-test/:uncheckedexemptions for new gated behavior. Then run the namespace gate from the REPL or CLI. -
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.