No vars found in this namespace.
ClojureScript-only AST op handlers. Mirrors skeptic.analysis.annotate.jvm:
each handler recurses children via the step trampoline and assigns a :type
derived from the cljs :tag (translated via av/cljs-tag->type), falling
back to Dyn when the tag is any or unrecognized.
ClojureScript-only AST op handlers. Mirrors `skeptic.analysis.annotate.jvm`: each handler recurses children via the step trampoline and assigns a `:type` derived from the cljs `:tag` (translated via `av/cljs-tag->type`), falling back to `Dyn` when the tag is `any` or unrecognized.
Recursive AST-node prune. Drops slots that no skeptic reader consumes and that would otherwise root very large per-node payloads through the locals env and analyzer-state references on deep cljs ASTs.
Removed: :env everywhere; :info everywhere except cljs :var ops,
where it is reduced to {:name ... :meta ...} (the call-symbol path at
annotate/api.clj:42 plus the Var-metadata path at
schema/collect/cljs.clj:94 / malli_spec/collect/cljs.clj:57).
Recurses through the analyzer's :children
slot the same way as the cljs intake walker; AST shape is otherwise
preserved so every existing reader of :init / :binding-init /
:fn-binding-node / :meta continues to see the fields it expects.
Recursive AST-node prune. Drops slots that no skeptic reader consumes
and that would otherwise root very large per-node payloads through the
locals env and analyzer-state references on deep cljs ASTs.
Removed: `:env` everywhere; `:info` everywhere except cljs `:var` ops,
where it is reduced to `{:name ... :meta ...}` (the call-symbol path at
`annotate/api.clj:42` plus the Var-metadata path at
`schema/collect/cljs.clj:94` / `malli_spec/collect/cljs.clj:57`).
Recurses through the analyzer's `:children`
slot the same way as the cljs intake walker; AST shape is otherwise
preserved so every existing reader of `:init` / `:binding-init` /
`:fn-binding-node` / `:meta` continues to see the fields it expects.Trampoline for the annotate helpers.
End state (Phase 7): every annotate helper has signature
(Ctx, AnnotatedNode) -> Step, where Step is the sum type below.
[:done annotated-node]
[:call helper-fn ctx node k]
run is a loop over a heap-allocated stack of continuations. During
the Phase 2-6 migration window, some helpers still return plain
AnnotatedNode values; step? / normalize plus the [:done v]
auto-wrap branch inside run carry those through. All three are
migration-window cruft and are deleted in Phase 7 contraction.
Trampoline for the annotate helpers.
End state (Phase 7): every annotate helper has signature
`(Ctx, AnnotatedNode) -> Step`, where `Step` is the sum type below.
[:done annotated-node]
[:call helper-fn ctx node k]
`run` is a loop over a heap-allocated stack of continuations. During
the Phase 2-6 migration window, some helpers still return plain
AnnotatedNode values; `step?` / `normalize` plus the [:done v]
auto-wrap branch inside `run` carry those through. All three are
migration-window cruft and are deleted in Phase 7 contraction.Shared tools.analyzer child listing and preorder traversal. Delegates to clojure.tools.analyzer.ast so all callers stay aligned.
Shared tools.analyzer child listing and preorder traversal. Delegates to clojure.tools.analyzer.ast so all callers stay aligned.
Per-form descriptor extraction for Plumatic source forms stored in the
project-wide form-refs map, keyed by qualified symbol (built once in
skeptic.checking.pipeline/project-state, threaded through bridge ctx).
Pipeline stores prepared descriptors, not raw forms. Each descriptor carries
the exact source namespace, alias map, and known declaration qsyms used to
resolve schema-reference symbols deterministically.
Descriptor shapes: :defn → {:kind :defn :output-form form :arglists {k {:input-forms [...]}}} :def → {:kind :def :schema-form form} :defschema → {:kind :defschema :schema-form form} :schema-source → {:kind :schema-source :schema-form form}
Prepared descriptors add: :source-env → {:ns ns-sym :aliases {alias target-ns} :ref-qsyms #{qsym}}
Per-form descriptor extraction for Plumatic source forms stored in the
project-wide form-refs map, keyed by qualified symbol (built once in
`skeptic.checking.pipeline/project-state`, threaded through bridge ctx).
Pipeline stores prepared descriptors, not raw forms. Each descriptor carries
the exact source namespace, alias map, and known declaration qsyms used to
resolve schema-reference symbols deterministically.
Descriptor shapes:
:defn → {:kind :defn :output-form form :arglists {k {:input-forms [...]}}}
:def → {:kind :def :schema-form form}
:defschema → {:kind :defschema :schema-form form}
:schema-source → {:kind :schema-source :schema-form form}
Prepared descriptors add:
:source-env → {:ns ns-sym :aliases {alias target-ns} :ref-qsyms #{qsym}}Host-side wrappers around the worker's class oracle. Every :class slot on
the host carries an opaque handle: an integer for bootstrap host-runtime
classes (interned at connect-time via intern-host-classes!), or a UUID
string for project classes (minted by the worker on resolve-class-sym).
Host NEVER calls Class/forName, .isAssignableFrom, instance?, or
class-equality on a project class. Every relation routes through the
worker via class-rel.
Host-side wrappers around the worker's class oracle. Every `:class` slot on the host carries an opaque handle: an integer for bootstrap host-runtime classes (interned at connect-time via `intern-host-classes!`), or a UUID string for project classes (minted by the worker on `resolve-class-sym`). Host NEVER calls `Class/forName`, `.isAssignableFrom`, `instance?`, or class-equality on a project class. Every relation routes through the worker via `class-rel`.
Native typing for clojure.lang.Numbers static calls and selected clojure.core invokes. Toolchain reference (Clojure 1.11.1, tools.analyzer.jvm 1.2.3):
Native typing for clojure.lang.Numbers static calls and selected clojure.core invokes. Toolchain reference (Clojure 1.11.1, tools.analyzer.jvm 1.2.3): - Numbers: inc/dec argc 1; add/multiply argc 2 (n-ary +/* is nested static calls); minus argc 1 or 2; isPos/isNeg argc 1 -> bool. - Invoke (native-fn-dict): (+) (+ x) (*) (* x); str; format; even? odd?; inc when not inlined. - Literal (+ 1 2 3) never yields top-level :invoke; test 3-arg + with ((resolve '+) 1 2 3).
Shared registry of clojure.core predicates that Skeptic can type. Two consumers:
Shared registry of clojure.core predicates that Skeptic can type.
Two consumers:
- native_fns: each predicate is typed Dyn -> Bool when invoked
- bridge admission: (s/pred f) and Malli's bare-predicate-as-schema
both convert to the predicate's witness type.JDK 9+/Leiningen-bootclasspath workaround for clojure.instant.
Leiningen's launcher appends leiningen-<v>-standalone.jar to the JVM's
-Xbootclasspath/a: (unless LEIN_USE_BOOTCLASSPATH=no is set in the
environment — which we cannot require of end users). Clojure ends up
loaded by the JVM bootstrap classloader. When any code transitively
requires clojure.instant, its AOT-compiled <clinit> pushes
clojure.lang.Compiler/LOADER = (.getClassLoader thisLoadingFn) = null
(bootstrap). RT.classForNameNonLoading("java.sql.Timestamp") then
calls Class.forName("java.sql.Timestamp", false, null) against the
bootstrap loader. On JDK 9+, java.sql is defined to
PlatformClassLoader, NOT bootstrap, so the call raises
ClassNotFoundException. The four sibling java.util.* imports just
before it succeed because java.util lives in java.base, which IS in
bootstrap.
Skeptic transitively pulls clojure.instant via cljs.analyzer.api →
cljs.tagged-literals → cljs.instant → clojure.instant, so this triggers
at plugin-load time and blocks the skeptic task from running at all.
Fix: clojure.lang.RT/classForName bytecode short-circuits to
DynamicClassLoader/findInMemoryClass BEFORE invoking
Class.forName(name, false, null) when the passed loader is not itself a
DynamicClassLoader. That short-circuit reads
DynamicClassLoader/classCache, a static ConcurrentHashMap normally
used by Clojure's in-memory class generation. We pre-populate it with
"java.sql.Timestamp" → java.sql.Timestamp.class, then eagerly
(require 'clojure.instant). Its <clinit> runs immediately, hits our
cache entry through the short-circuit, and never falls through to the
broken Class.forName(name, false, null) path. The namespace is
registered in *loaded-libs*, so the later transitive require from
cljs.instant is a no-op and the unsafe <clinit> does not re-run.
This namespace is intentionally required FIRST by
skeptic.cljs.analyzer-driver — the earliest namespace on the cljs
intake chain — so the cache is populated before any cljs require can
drag clojure.instant in.
JDK 9+/Leiningen-bootclasspath workaround for `clojure.instant`.
Leiningen's launcher appends `leiningen-<v>-standalone.jar` to the JVM's
`-Xbootclasspath/a:` (unless LEIN_USE_BOOTCLASSPATH=no is set in the
environment — which we cannot require of end users). Clojure ends up
loaded by the JVM bootstrap classloader. When any code transitively
requires `clojure.instant`, its AOT-compiled `<clinit>` pushes
`clojure.lang.Compiler/LOADER = (.getClassLoader thisLoadingFn) = null`
(bootstrap). `RT.classForNameNonLoading("java.sql.Timestamp")` then
calls `Class.forName("java.sql.Timestamp", false, null)` against the
bootstrap loader. On JDK 9+, `java.sql` is defined to
`PlatformClassLoader`, NOT bootstrap, so the call raises
`ClassNotFoundException`. The four sibling `java.util.*` imports just
before it succeed because `java.util` lives in `java.base`, which IS in
bootstrap.
Skeptic transitively pulls clojure.instant via cljs.analyzer.api →
cljs.tagged-literals → cljs.instant → clojure.instant, so this triggers
at plugin-load time and blocks the `skeptic` task from running at all.
Fix: `clojure.lang.RT/classForName` bytecode short-circuits to
`DynamicClassLoader/findInMemoryClass` BEFORE invoking
`Class.forName(name, false, null)` when the passed loader is not itself a
`DynamicClassLoader`. That short-circuit reads
`DynamicClassLoader/classCache`, a static `ConcurrentHashMap` normally
used by Clojure's in-memory class generation. We pre-populate it with
`"java.sql.Timestamp" → java.sql.Timestamp.class`, then eagerly
`(require 'clojure.instant)`. Its `<clinit>` runs immediately, hits our
cache entry through the short-circuit, and never falls through to the
broken `Class.forName(name, false, null)` path. The namespace is
registered in `*loaded-libs*`, so the later transitive require from
cljs.instant is a no-op and the unsafe `<clinit>` does not re-run.
This namespace is intentionally required FIRST by
`skeptic.cljs.analyzer-driver` — the earliest namespace on the cljs
intake chain — so the cache is populated before any cljs require can
drag clojure.instant in.No vars found in this namespace.
Source discovery for cljs/cljc files in a deps.edn project. Reuses
skeptic.cli.paths/discover-paths to obtain the project's resolved
source paths from clojure.tools.deps/create-basis, then walks them
for cljs/cljc files. discover-paths returns paths verbatim from the
deps.edn (relative strings like "src"); they are absolutized against
root here before walking.
Source discovery for cljs/cljc files in a deps.edn project. Reuses `skeptic.cli.paths/discover-paths` to obtain the project's resolved source paths from `clojure.tools.deps/create-basis`, then walks them for cljs/cljc files. `discover-paths` returns paths verbatim from the deps.edn (relative strings like "src"); they are absolutized against `root` here before walking.
Walk a collection of source roots and partition the matching files into cljs and cljc buckets. Paths that do not exist are skipped silently; paths that point at a regular file are included if their extension matches.
Walk a collection of source roots and partition the matching files into cljs and cljc buckets. Paths that do not exist are skipped silently; paths that point at a regular file are included if their extension matches.
Source discovery for cljs/cljc files in a Leiningen project. Reads :source-paths and :test-paths from the project map, plus any cljsbuild build :source-paths if present, and walks them for cljs/cljc files.
Source discovery for cljs/cljc files in a Leiningen project. Reads :source-paths and :test-paths from the project map, plus any cljsbuild build :source-paths if present, and walks them for cljs/cljc files.
Source discovery for cljs/cljc files in a shadow-cljs project. Reads
shadow-cljs.edn at root as plain EDN and walks the top-level
:source-paths key for cljs/cljc files.
Source discovery for cljs/cljc files in a shadow-cljs project. Reads shadow-cljs.edn at `root` as plain EDN and walks the top-level :source-paths key for cljs/cljc files.
Legacy deps.edn-side entrypoint for Skeptic.
Hermetic Clojure CLI execution is exposed through skeptic.tool/check
and clj -T:skeptic check. clojure -M:skeptic starts from the client
project's runtime classpath, so it is intentionally unsupported.
Legacy deps.edn-side entrypoint for Skeptic. Hermetic Clojure CLI execution is exposed through `skeptic.tool/check` and `clj -T:skeptic check`. `clojure -M:skeptic` starts from the client project's runtime classpath, so it is intentionally unsupported.
Shared CLI option vector and parser for the Leiningen plugin (leiningen.skeptic) and the legacy deps.edn -M entrypoint.
Hermetic deps.edn execution uses the Clojure CLI tool API
(skeptic.tool/check) with an EDN arg map instead of argv parsing.
Keys produced here land directly in the opts map consumed by
skeptic.core/check-project.
Shared CLI option vector and parser for the Leiningen plugin (leiningen.skeptic) and the legacy deps.edn -M entrypoint. Hermetic deps.edn execution uses the Clojure CLI tool API (`skeptic.tool/check`) with an EDN arg map instead of argv parsing. Keys produced here land directly in the opts map consumed by skeptic.core/check-project.
Source-path discovery for the deps.edn entrypoint. Reads the project's deps.edn through the official tools.deps API and returns the merged :paths vector for the given alias selection. The Leiningen plugin does not use this; it gets paths from the lein project map.
clojure.tools.deps is resolved lazily inside create-basis rather than
required at namespace load. That keeps its heavyweight transitive graph
(maven-resolver, maven-core, cognitect-aws, jetty) off any classpath that
only loads this namespace without calling it -- specifically the Leiningen
plugin classloader, which loads this ns transitively (via skeptic.cli.main)
during self-analysis but never invokes deps.edn path discovery.
Eligibility filter: a discovered .clj/.cljc/.cljs file is sent to the worker iff every dep symbol in its ns-form's :require/:require-macros/ :use/:use-macros clauses is either a namespace defined by another in-project source file OR is resolvable as a classpath resource against a URLClassLoader built from the basis's full classpath. Rejection is closed under transitive project-internal requires: when G is rejected, every project file whose ns-form transitively requires G is also rejected. Rejected files never reach the worker and produce a :unresolvable-deps failure entry that becomes a ns-discovery-warning.
Source-path discovery for the deps.edn entrypoint. Reads the project's deps.edn through the official tools.deps API and returns the merged :paths vector for the given alias selection. The Leiningen plugin does not use this; it gets paths from the lein project map. `clojure.tools.deps` is resolved lazily inside `create-basis` rather than required at namespace load. That keeps its heavyweight transitive graph (maven-resolver, maven-core, cognitect-aws, jetty) off any classpath that only loads this namespace without calling it -- specifically the Leiningen plugin classloader, which loads this ns transitively (via skeptic.cli.main) during self-analysis but never invokes deps.edn path discovery. Eligibility filter: a discovered .clj/.cljc/.cljs file is sent to the worker iff every dep symbol in its ns-form's :require/:require-macros/ :use/:use-macros clauses is either a namespace defined by another in-project source file OR is resolvable as a classpath resource against a URLClassLoader built from the basis's full classpath. Rejection is closed under transitive project-internal requires: when G is rejected, every project file whose ns-form transitively requires G is also rejected. Rejected files never reach the worker and produce a :unresolvable-deps failure entry that becomes a ns-discovery-warning.
Single-form cljs analysis entrypoints via a file-local cljs compiler state.
analyze-form analyzes one already-read cljs form against a supplied ns AST
inside a non-leaking compiler state, used by the schema/malli collectors to
type a post-macroexpansion declaration body. Whole-file analysis lives on the
worker (skeptic.worker.analyzer-cljs); the checker never analyzes cljs
source files through this namespace.
cljs ASTs carry :type on :binding/:fn-method nodes that conflicts
with skeptic's :type slot (SemanticType), and :binding nodes lack
:form. analyze-form strips and synthesizes via normalize-cljs-node
so the skeptic annotate pipeline starts from a clean shape.
Single-form cljs analysis entrypoints via a file-local cljs compiler state. `analyze-form` analyzes one already-read cljs form against a supplied ns AST inside a non-leaking compiler state, used by the schema/malli collectors to type a post-macroexpansion declaration body. Whole-file analysis lives on the worker (`skeptic.worker.analyzer-cljs`); the checker never analyzes cljs source files through this namespace. cljs ASTs carry `:type` on `:binding`/`:fn-method` nodes that conflicts with skeptic's `:type` slot (SemanticType), and `:binding` nodes lack `:form`. `analyze-form` strips and synthesizes via `normalize-cljs-node` so the skeptic annotate pipeline starts from a clean shape.
sci-sandboxed interpretation of post-macroexpansion Plumatic Schema forms collected from cljs ASTs.
The sci context exposes schema.core as the only allowlisted user
namespace; sci interprets the form by applying Plumatic's real JVM
functions and returns real Plumatic Schema records. Symbols outside
the allowlist (and sci's default clojure.core surface) cannot be
resolved, so the interpreter cannot execute arbitrary user code.
SCI is loaded through a private implementation namespace only when a CLJS
schema form actually needs interpretation. This keeps skeptic.core and the
Lein plugin load path from eagerly loading SCI/edamame.
sci-sandboxed interpretation of post-macroexpansion Plumatic Schema forms collected from cljs ASTs. The sci context exposes `schema.core` as the only allowlisted user namespace; sci interprets the form by applying Plumatic's real JVM functions and returns real Plumatic Schema records. Symbols outside the allowlist (and sci's default clojure.core surface) cannot be resolved, so the interpreter cannot execute arbitrary user code. SCI is loaded through a private implementation namespace only when a CLJS schema form actually needs interpretation. This keeps `skeptic.core` and the Lein plugin load path from eagerly loading SCI/edamame.
SCI-backed implementation for skeptic.cljs.schema-interpreter.
Keep this namespace out of eager host/plugin load paths. It requires SCI directly because SCI's namespace-copy helpers are macros.
SCI-backed implementation for `skeptic.cljs.schema-interpreter`. Keep this namespace out of eager host/plugin load paths. It requires SCI directly because SCI's namespace-copy helpers are macros.
Dependency ordering for cljs/cljc source files. Returns files in an order where each file's project-local :require'd dependencies appear before it. When a cycle blocks standard topo progress, the next pick is chosen by tiebreaker: nss without :require-macros / :use-macros first, then fewest :requires, then ns-sym alphabetical.
Ns-head data (:name / :requires / :require-macros / :use-macros)
is supplied by head-fn, which reads each file on the worker under the
project basis. This namespace never loads cljs sources itself.
Dependency ordering for cljs/cljc source files. Returns files in an order where each file's project-local :require'd dependencies appear before it. When a cycle blocks standard topo progress, the next pick is chosen by tiebreaker: nss without :require-macros / :use-macros first, then fewest :requires, then ns-sym alphabetical. Ns-head data (`:name` / `:requires` / `:require-macros` / `:use-macros`) is supplied by `head-fn`, which reads each file on the worker under the project basis. This namespace never loads cljs sources itself.
No vars found in this namespace.
Single source of truth for Skeptic's HOST runtime coordinates.
The hermetic-host launcher (lein-skeptic) resolves these via its build
system (lein's aether wrapper) and uses them as the host JVM's -cp.
The deps.edn path (clj -T:skeptic check) uses the equivalent alias
declaration in skeptic/deps.edn.
Parallel to skeptic.worker.deps/worker-deps for the worker JVM.
tools.reader is explicitly pinned at >= 1.0.0-beta3 (the first version
whose SourceLoggingPushbackReader is Closeable). Pre-1.0.0-beta3 the
host's with-open against the reader (skeptic.file/ns-for-clojure-file)
reflectively calls .close on a non-Closeable class and crashes — the
shape that caused the rc9 plumbing host-side bug.
Single source of truth for Skeptic's HOST runtime coordinates. The hermetic-host launcher (lein-skeptic) resolves these via its build system (lein's aether wrapper) and uses them as the host JVM's -cp. The deps.edn path (`clj -T:skeptic check`) uses the equivalent alias declaration in skeptic/deps.edn. Parallel to skeptic.worker.deps/worker-deps for the worker JVM. tools.reader is explicitly pinned at >= 1.0.0-beta3 (the first version whose SourceLoggingPushbackReader is Closeable). Pre-1.0.0-beta3 the host's `with-open` against the reader (skeptic.file/ns-for-clojure-file) reflectively calls .close on a non-Closeable class and crashes — the shape that caused the rc9 plumbing host-side bug.
Malli intake stream, hermetic from the project JVM. Reads two channels of
inert Malli spec data off the worker-shipped clj-state entries — never the
live (malli.core/function-schemas) registry or a loaded Var's metadata:
:malli/schema Var-meta → the :malli-schema field the worker captured
off the raw source-form (skeptic.worker.server/project-entry).m/=> → the spec vector at position 2 of the (m/=> sym SPEC) source-form.Specs are inert keyword/vector data; amb/admit-malli-spec (pinned Malli)
does all interpretation host-side. No malli.core require here.
Malli intake stream, hermetic from the project JVM. Reads two channels of inert Malli spec data off the worker-shipped clj-state entries — never the live `(malli.core/function-schemas)` registry or a loaded Var's metadata: 1. `:malli/schema` Var-meta → the `:malli-schema` field the worker captured off the raw source-form (`skeptic.worker.server/project-entry`). 2. `m/=>` → the spec vector at position 2 of the `(m/=> sym SPEC)` source-form. Specs are inert keyword/vector data; `amb/admit-malli-spec` (pinned Malli) does all interpretation host-side. No `malli.core` require here.
ClojureScript admission for Malli function schemas.
Mirrors skeptic.malli-spec.collect/ns-malli-spec-results for cljs source
files. Two channels:
:malli/schema spec the worker captured off the
raw source-form into each cljs entry's :malli-schema field
(skeptic.worker.server/project-cljs-entry), exactly as the clj collector
reads its entries. The cljs AST never carries user var-meta at
[:var :info :meta], so this channel reads the entry field, not the AST.(malli.core/=> sym SPEC) macroexpands to a
top-level :op :do whose form is
(do (malli.core/-register-function-schema! 'ns 'sym SPEC nil :cljs id) 'ns/sym).
Classification walks the outer :form directly.No cenv reads, no caller-managed compiler state.
ClojureScript admission for Malli function schemas. Mirrors `skeptic.malli-spec.collect/ns-malli-spec-results` for cljs source files. Two channels: - Var-meta channel: the `:malli/schema` spec the worker captured off the raw source-form into each cljs entry's `:malli-schema` field (`skeptic.worker.server/project-cljs-entry`), exactly as the clj collector reads its entries. The cljs AST never carries user var-meta at `[:var :info :meta]`, so this channel reads the entry field, not the AST. - Registration channel: `(malli.core/=> sym SPEC)` macroexpands to a top-level `:op :do` whose form is `(do (malli.core/-register-function-schema! 'ns 'sym SPEC nil :cljs id) 'ns/sym)`. Classification walks the outer `:form` directly. No cenv reads, no caller-managed compiler state.
Coerce a Clojure value to a JSON-safe form for the --debug wire-tap. The only job is to make the value printable through clojure.data.json. No type dispatch, no projection, no filtering. The value is printed by Clojure's own printer under locked bindings — the string it produces is what ships.
Coerce a Clojure value to a JSON-safe form for the --debug wire-tap. The only job is to make the value printable through clojure.data.json. No type dispatch, no projection, no filtering. The value is printed by Clojure's own printer under locked bindings — the string it produces is what ships.
Host-side JVM Plumatic admission from worker-captured evaluated schema values. The worker loads the project namespace and reads Var metadata; the host decodes the EDN schema payload and feeds the existing SchemaDesc builder. No source-form schema interpretation and no host project namespace loading.
Host-side JVM Plumatic admission from worker-captured evaluated schema values. The worker loads the project namespace and reads Var metadata; the host decodes the EDN schema payload and feeds the existing SchemaDesc builder. No source-form schema interpretation and no host project namespace loading.
ClojureScript admission for Plumatic Schema declarations.
Mirrors skeptic.schema.collect/ns-schema-results for cljs source files.
Operates on per-form analyzed ASTs from skeptic.cljs.analyzer-driver.
Reads alias requires from the parsed ns AST (output of
cljs.analyzer.api/parse-ns). The cljs analyzer does not attach the
s/defn :schema/:arglists Var-metadata to the :def AST node, so the
schema forms are read directly from the macroexpansion's :let binding
init forms (output-schema* / input-schema*); the single-arity arglist is
reconstructed from the s/one arg-name labels in the input-schema form. No
cenv reads, no caller-managed compiler state.
Three top-level shapes are recognized:
s/def :op :let, single binding output-schema__*__auto__,
inner :def at [:body :ret].s/defschema :op :def, :init :form starts with vary-meta.s/defn :op :let, multi-binding outer let with output-schema*
and input-schema* gensyms.Each output-schema* / input-schema* binding init is a symbolic schema
form (the cljs analyzer does not evaluate JVM-side); resolution to a real
Schema record goes through skeptic.cljs.schema-interpreter, which
interprets the form in a sci-sandboxed context that exposes only
schema.core.
ClojureScript admission for Plumatic Schema declarations.
Mirrors `skeptic.schema.collect/ns-schema-results` for cljs source files.
Operates on per-form analyzed ASTs from `skeptic.cljs.analyzer-driver`.
Reads alias requires from the parsed ns AST (output of
`cljs.analyzer.api/parse-ns`). The cljs analyzer does not attach the
`s/defn` `:schema`/`:arglists` Var-metadata to the `:def` AST node, so the
schema forms are read directly from the macroexpansion's `:let` binding
init forms (`output-schema*` / `input-schema*`); the single-arity arglist is
reconstructed from the `s/one` arg-name labels in the input-schema form. No
cenv reads, no caller-managed compiler state.
Three top-level shapes are recognized:
- `s/def` :op :let, single binding `output-schema__*__auto__`,
inner :def at [:body :ret].
- `s/defschema` :op :def, :init :form starts with vary-meta.
- `s/defn` :op :let, multi-binding outer let with `output-schema*`
and `input-schema*` gensyms.
Each `output-schema*` / `input-schema*` binding init is a symbolic schema
form (the cljs analyzer does not evaluate JVM-side); resolution to a real
Schema record goes through `skeptic.cljs.schema-interpreter`, which
interprets the form in a sci-sandboxed context that exposes only
`schema.core`.Plumatic source-form discovery, hermetic from the project JVM. Classifies
each top-level form's head symbol by alias-resolving it against the
namespace's (ns …) / top-level (require …) alias map, then tags matches
with a producer role.
The forms arrive as inert :source-form data shipped by the worker (the
project is never loaded on the host), so heads like s/defn /
schema.core/defn / schemy/defn are resolved to the canonical
schema.core/defn symbol via the alias map rather than by binding *ns* and
consulting live project Vars.
Plumatic source-form discovery, hermetic from the project JVM. Classifies each top-level form's head symbol by alias-resolving it against the namespace's `(ns …)` / top-level `(require …)` alias map, then tags matches with a producer role. The forms arrive as inert `:source-form` data shipped by the worker (the project is never loaded on the host), so heads like `s/defn` / `schema.core/defn` / `schemy/defn` are resolved to the canonical `schema.core/defn` symbol via the alias map rather than by binding `*ns*` and consulting live project Vars.
Host-side decoder for worker-encoded Plumatic schema values.
Host-side decoder for worker-encoded Plumatic schema values.
Clojure CLI tool entrypoints for hermetic deps.edn-side Skeptic runs.
Clojure CLI tool entrypoints for hermetic deps.edn-side Skeptic runs.
Worker-side clj analyzer execution. Mirrors the env-construction and
analyze-form body that live in skeptic.analysis.annotate, with no
Skeptic / Schema / Malli dependency. The worker reads the project's own
source files with the real Clojure reader and analyzes them in bulk; no
form ever crosses host->worker (the host sends only a source-file path).
tools.analyzer.* and tools.reader.* are required eagerly at ns-load
(worker boot), under the JVM launch classloader. The launch-classpath
order already prefers project entries (worker-classpath-entries in
classpath.clj), so the project's pinned tools.analyzer version wins
via first-occurrence; no lazy require is needed.
Worker-side clj analyzer execution. Mirrors the env-construction and `analyze-form` body that live in `skeptic.analysis.annotate`, with no Skeptic / Schema / Malli dependency. The worker reads the project's own source files with the real Clojure reader and analyzes them in bulk; no form ever crosses host->worker (the host sends only a source-file path). tools.analyzer.* and tools.reader.* are required eagerly at ns-load (worker boot), under the JVM launch classloader. The launch-classpath order already prefers project entries (`worker-classpath-entries` in `classpath.clj`), so the project's pinned tools.analyzer version wins via first-occurrence; no lazy require is needed.
Worker-side cljs analyzer execution. Mirrors the parse + reader-loop
that live in skeptic.cljs.analyzer-driver, with no Skeptic / Schema /
Malli dependency. The host-side source-file wrapper is rewired to issue a
worker RPC instead of running the cljs analyzer locally.
Wire payloads carry the source-file descriptor only; the cljs compiler state never crosses the wire.
cljs.analyzer / cljs.analyzer.api / cljs.env / cljs.compiler are loaded
lazily inside with-analysis-bindings so they intern from the project's
pinned clojurescript version when present, not Skeptic's runtime-cp version
at worker boot.
Worker-side cljs analyzer execution. Mirrors the parse + reader-loop that live in `skeptic.cljs.analyzer-driver`, with no Skeptic / Schema / Malli dependency. The host-side source-file wrapper is rewired to issue a worker RPC instead of running the cljs analyzer locally. Wire payloads carry the source-file descriptor only; the cljs compiler state never crosses the wire. cljs.analyzer / cljs.analyzer.api / cljs.env / cljs.compiler are loaded lazily inside `with-analysis-bindings` so they intern from the project's pinned clojurescript version when present, not Skeptic's runtime-cp version at worker boot.
Worker launch classpath assembly. Skeptic owns its worker runtime as a
coordinate declaration in skeptic.worker.deps/worker-deps; the lein and
deps.edn callers each resolve those coordinates through their own build
system and hand the resolved jar list (worker-jars) plus the project
classpath (project-classpath-entries) to worker-classpath-entries.
The result is a single launch classpath string: project-cp first, worker
jars second, Skeptic's own skeptic.worker.* source entry tail (so the
worker JVM can require its own server namespace at boot).
Worker launch classpath assembly. Skeptic owns its worker runtime as a coordinate declaration in `skeptic.worker.deps/worker-deps`; the lein and deps.edn callers each resolve those coordinates through their own build system and hand the resolved jar list (`worker-jars`) plus the project classpath (`project-classpath-entries`) to `worker-classpath-entries`. The result is a single launch classpath string: project-cp first, worker jars second, Skeptic's own `skeptic.worker.*` source entry tail (so the worker JVM can require its own server namespace at boot).
Host-side nREPL client: connect to a worker port, send ops, return replies.
Uses the Transport protocol directly (send + recv) for synchronous
request-response semantics. Does NOT use nrepl.core/client or
nrepl.core/message — those layer lazy seqs and timeouts designed for
interactive REPLs, not RPC.
nrepl.* namespaces are NOT required at namespace-load time. The worker
loads this namespace via server.clj's :require to get loopback-conn and
the loopback branch of ask — neither of which touches nrepl. Host-facing
functions below defer their nrepl loads via nrepl-resolve, so the
project's pinned nrepl wins via the launch classpath's project-first
ordering.
Host-side nREPL client: connect to a worker port, send ops, return replies. Uses the Transport protocol directly (`send` + `recv`) for synchronous request-response semantics. Does NOT use nrepl.core/client or nrepl.core/message — those layer lazy seqs and timeouts designed for interactive REPLs, not RPC. nrepl.* namespaces are NOT required at namespace-load time. The worker loads this namespace via server.clj's :require to get `loopback-conn` and the loopback branch of `ask` — neither of which touches nrepl. Host-facing functions below defer their nrepl loads via `nrepl-resolve`, so the project's pinned nrepl wins via the launch classpath's project-first ordering.
Single source of truth for Skeptic's worker runtime coordinates.
Both runtime entrypoints consume this declaration:
leiningen.skeptic resolves it via leiningen's aether wrapper.skeptic.cli.main resolves it via tools.deps.The 11 coordinates are the namespaces load-bearing for the worker JVM: clojure, clojurescript, tools.analyzer(.jvm), tools.reader, tools.namespace (ns-decl parsing for dependency-ordered loading), core.cache/memoize, data.priority-map, transit-clj, nrepl. Whatever each build system transitively resolves from this set is the worker's runtime universe under that build system.
skeptic/project.clj's :worker profile and skeptic/deps.edn's
:worker alias mirror this vector for ad-hoc invocations like
lein with-profile +worker classpath and clj -A:worker; the runtime
code paths read FROM this var, not from those build-file declarations.
Single source of truth for Skeptic's worker runtime coordinates. Both runtime entrypoints consume this declaration: - `leiningen.skeptic` resolves it via leiningen's aether wrapper. - `skeptic.cli.main` resolves it via tools.deps. The 11 coordinates are the namespaces load-bearing for the worker JVM: clojure, clojurescript, tools.analyzer(.jvm), tools.reader, tools.namespace (ns-decl parsing for dependency-ordered loading), core.cache/memoize, data.priority-map, transit-clj, nrepl. Whatever each build system transitively resolves from this set is the worker's runtime universe under that build system. `skeptic/project.clj`'s `:worker` profile and `skeptic/deps.edn`'s `:worker` alias mirror this vector for ad-hoc invocations like `lein with-profile +worker classpath` and `clj -A:worker`; the runtime code paths read FROM this var, not from those build-file declarations.
Host-side worker process lifecycle: spawn a JVM running skeptic.worker.server,
read the port handshake off its stdout, and tear it down. The caller passes
a single launch classpath assembled by skeptic.worker.classpath —
project-cp first, worker jars second, Skeptic's own worker source tail.
Host-side worker process lifecycle: spawn a JVM running skeptic.worker.server, read the port handshake off its stdout, and tear it down. The caller passes a single launch classpath assembled by `skeptic.worker.classpath` — project-cp first, worker jars second, Skeptic's own worker source tail.
Worker-side nREPL server. Runs in the spawned JVM on the combined
project-first launch classpath and answers host requests on demand. Project
operations run on the clojure.main launch thread, so project code loads
exactly as the project's own runtime loads it — Skeptic adds no reader-Var
or loading machinery. Plan 2 Phase 1.5 adds the handle-table machinery: every Class
operand on the wire is an opaque handle (integer for bootstrap-interned
host-runtime classes; UUID-string for project classes). Worker is sole owner
of Class/forName, .isAssignableFrom, instance?, and class equality.
Phase 5 adds the analyzer ops. The analyzer-execution glue lives in
skeptic.worker.analyzer-clj / skeptic.worker.analyzer-cljs. No other
skeptic.* namespace is required from this server: Skeptic's own analysis
code and Plumatic Schema / Malli stay on the host (B3/B4).
nREPL is lazy-loaded inside start! from the worker JVM's single launch
classloader (project-cp first, worker jars second). The ns form does NOT
require any nrepl.* namespace at load time — projects pinning a different
nrepl win their version via first-occurrence on the launch cp. The
wrap-* dispatchers are let-bound inside start!, closing over locally
resolved nrepl.transport/send, nrepl.misc/response-for, and
nrepl.server/{start-server,unknown-op}. Descriptor metadata is dead
code under the explicit handler thread and has been removed.
Worker-side nREPL server. Runs in the spawned JVM on the combined
project-first launch classpath and answers host requests on demand. Project
operations run on the clojure.main launch thread, so project code loads
exactly as the project's own runtime loads it — Skeptic adds no reader-Var
or loading machinery. Plan 2 Phase 1.5 adds the handle-table machinery: every Class
operand on the wire is an opaque handle (integer for bootstrap-interned
host-runtime classes; UUID-string for project classes). Worker is sole owner
of `Class/forName`, `.isAssignableFrom`, `instance?`, and class equality.
Phase 5 adds the analyzer ops. The analyzer-execution glue lives in
`skeptic.worker.analyzer-clj` / `skeptic.worker.analyzer-cljs`. No other
`skeptic.*` namespace is required from this server: Skeptic's own analysis
code and Plumatic Schema / Malli stay on the host (B3/B4).
nREPL is lazy-loaded inside `start!` from the worker JVM's single launch
classloader (project-cp first, worker jars second). The ns form does NOT
require any nrepl.* namespace at load time — projects pinning a different
nrepl win their version via first-occurrence on the launch cp. The
`wrap-*` dispatchers are let-bound inside `start!`, closing over locally
resolved `nrepl.transport/send`, `nrepl.misc/response-for`, and
`nrepl.server/{start-server,unknown-op}`. Descriptor metadata is dead
code under the explicit handler thread and has been removed.Length-prefixed Transit+msgpack transport for Skeptic's private nREPL
worker link. The Transport protocol is implemented in the peer namespace
skeptic.worker.transport-impl, which is required lazily on first
transit call so neither nrepl.transport nor the deftype's Protocol
symbol resolves at this namespace's load time.
Length-prefixed Transit+msgpack transport for Skeptic's private nREPL worker link. The Transport protocol is implemented in the peer namespace `skeptic.worker.transport-impl`, which is required lazily on first `transit` call so neither nrepl.transport nor the deftype's Protocol symbol resolves at this namespace's load time.
Holds the deftype that implements nrepl.transport/Transport over the
Transit+msgpack framing helpers in skeptic.worker.transport. This
namespace is required lazily by skeptic.worker.transport/transit so
nrepl.transport's Transport protocol resolves at the project's pinned
version, not Skeptic's runtime-cp version at worker boot.
Holds the deftype that implements `nrepl.transport/Transport` over the Transit+msgpack framing helpers in `skeptic.worker.transport`. This namespace is required lazily by `skeptic.worker.transport/transit` so nrepl.transport's Transport protocol resolves at the project's pinned version, not Skeptic's runtime-cp version at worker boot.
No vars found in this namespace.
Host-safe wire-contract constants shared by both JVMs. Holds ONLY the keys and accessors for values that cross the worker->host AST boundary; carries no nREPL, tools.analyzer, or other worker-classpath dependency, so the host may require it without re-coupling to worker-only code.
The non-EDN sentinel: a raw analyzer-AST :val/:form/:raw-forms leaf
outside the wire-safe set (plain EDN scalars/colls plus the transit-carried
leaves: char, UUID, exact java.util.Date) is shipped as
{::nonedn true ::class <class-handle>} — regex Patterns, fn objects, Vars,
Namespaces, and any project-runtime object a data reader produced (a joda
DateTime from a tagged literal). The host types it by its class via the
carried handle; it never inspects the original value. The transport's
default-handler backstop ships anything that slips past projection as
{::nonedn true ::class-name <name> ::string <print>} — same host typing,
name resolved to a handle lazily.
Form metadata: the worker captures the host-read meta keys off each form into
a plain data vector in clojure.walk/postwalk order (capture-form-meta);
the host replays them onto the structurally-identical received form in the
same order (apply-form-meta). Shape is never altered, so structural form
walks survive.
Host-safe wire-contract constants shared by both JVMs. Holds ONLY the keys
and accessors for values that cross the worker->host AST boundary; carries no
nREPL, tools.analyzer, or other worker-classpath dependency, so the host may
require it without re-coupling to worker-only code.
The non-EDN sentinel: a raw analyzer-AST `:val`/`:form`/`:raw-forms` leaf
outside the wire-safe set (plain EDN scalars/colls plus the transit-carried
leaves: char, UUID, exact java.util.Date) is shipped as
`{::nonedn true ::class <class-handle>}` — regex Patterns, fn objects, Vars,
Namespaces, and any project-runtime object a data reader produced (a joda
DateTime from a tagged literal). The host types it by its class via the
carried handle; it never inspects the original value. The transport's
default-handler backstop ships anything that slips past projection as
`{::nonedn true ::class-name <name> ::string <print>}` — same host typing,
name resolved to a handle lazily.
Form metadata: the worker captures the host-read meta keys off each form into
a plain data vector in `clojure.walk/postwalk` order (`capture-form-meta`);
the host replays them onto the structurally-identical received form in the
same order (`apply-form-meta`). Shape is never altered, so structural form
walks survive.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |