mirror of
https://github.com/penpot/penpot.git
synced 2026-09-25 05:16:14 +00:00
* ✨ Enable closed schemas for RPC methods * 🐛 Fix duplicate make-dummy-request test helper definition The branch added a variadic DummyRequest/make-dummy-request pair but left the pre-existing single-arg definition in place. Because it was loaded last, zero-arg (make-dummy-request) calls added by prepare-rpc-params and rpc-nitrate-test threw ArityException, which broke 384 tests and caused 14 downstream assertion failures. Remove the stale duplicate so the variadic definition is the only one, and drop the now-unused yrq alias and duplicate yres alias. AI-assisted-by: deepseek-v4.1-flash * ✨ Add focused tests for make-dummy-request helper Pin the call contract of make-dummy-request, which the suite uses in three styles: no arguments, a single options map, and keyword arguments. The helper's redefinition shadowing in 8ca95adb98 was only caught by a full-suite run with hundreds of unrelated errors; these tests fail locally in a focused --focus run. Cover the zero-arg defaults, map and keyword overrides, the :body-bytes -> ByteArrayInputStream wrapping, :body-stream precedence, and cookie readback. Also clarify the docstring to list all supported call styles. AI-assisted-by: deepseek-v4.1-flash * 🚑 Prevent RPC client params from overriding auth context Strip qualified keys from decoded request params before merging them with the server-built auth context, so transit bodies can no longer override ::profile-id, ::auth-type or ::token-perms. Adds a regression test proving the override and the fix. AI-assisted-by: muse-spark-1.3-contributor * 📚 Merge backend subtleties memories under generic name Rename rpc-db-worker-subtleties to subtleties and fold in http-storage-filedata-subtleties, so the name no longer enumerates topics. Update all mem: references accordingly. AI-assisted-by: muse-spark-1.3-contributor * ✨ Add realistic tests for RPC auth override Cover the transit wire vector and the real wrapped :get-profile method with two database profiles, proving a session cannot read another profile by smuggling :app.rpc/profile-id in the body. AI-assisted-by: muse-spark-1.3-contributor * ✨ Add e2e test for RPC auth context override Parametrize rpcPost with contentType, accept and query so e2e can send hand-written transit bodies without new dependencies. The new test proves a transit-smuggled :app.rpc/profile-id no longer overrides the session in get-profile. Also fix the demo email assertion in auth-flow to the current uuid format. AI-assisted-by: muse-spark-1.3-contributor
27 KiB
27 KiB
Graph Experiment
Scope
- Purpose: project Penpot file data into an embedded Ladybug graph database.
- Purpose: keep the graph current with Penpot file changes.
- Purpose: expose a read-only graph console for backend debugging.
- This is an experiment, not a replacement for PostgreSQL file storage.
- The graph subsystem is off unless
:graphis in the backend flags. - The main Penpot frontend has no graph feature code for this subsystem.
- The graph console is a backend-served HTML template with JavaScript.
Memory Links
- Read
mem:backend/corefor backend architecture, HTTP routes, DB rules, and test commands. - Read
mem:backend/subtletiesfor RPC and message bus behavior, and for file data loading and realization. - Read
mem:common/changes-architecturefor the change record vocabulary. - Read
mem:frontend/routing-app-shell-subtletiesfor the existing notification WebSocket. - Read
mem:prod-infra/corefor Redis or Valkey message bus topology.
Branch Surface
- The graph experiment adds about 6,336 lines and changes about 27 files.
- The graph implementation lives under
backend/src/app/graph/. - The graph console lives at
backend/resources/app/templates/graph-console.tmpl. - The existing debug page gains graph links in
backend/resources/app/templates/debug.tmpl. - The existing debug HTTP routes gain graph handlers in
backend/src/app/http/debug.clj. - The backend system passes the message bus to the debug route component in
backend/src/app/main.clj. - The backend adds Ladybug and Arrow dependencies in
backend/deps.edn. - The backend adds JVM options for Ladybug and Arrow native access.
- The common flag registry adds
:graphincommon/src/app/common/flags.cljc. - The graph experiment adds
graph_sync_parity_test.cljandgraph_binder_gate_test.clj.
System Model
Storage layers
- PostgreSQL remains the source of truth for Penpot files.
- The graph database stores a projection of one file.
- A persistent graph uses a
.lbugpath underPENPOT_GRAPH_DIR. - The default graph directory is
/tmp/penpot-graph. - A debug session uses a Ladybug
:memory:database. - A debug session database lives inside the backend JVM process.
- A debug session does not survive a backend restart.
- A debug session does not store file data back to PostgreSQL.
Two graph update paths
- Cold projection reads the complete file and rebuilds the graph.
- Incremental sync reads file change records and updates the open graph.
- Both paths must produce the same graph for the same file state.
- The parity test treats cold projection as the reference path.
- A reload discards the session graph and uses cold projection again.
Main Namespaces
app.graph.ladybug
- Opens and closes Ladybug
DatabaseandConnectionobjects. - Installs and loads the Ladybug JSON extension.
- Executes Cypher statements.
- Executes prepared statements.
- Binds scalar parameters.
- Formats UUID, string, integer, number, JSON, and timestamp values.
- Formats compound values such as arrays, maps, and structs.
- Converts Ladybug values back to Clojure values.
- Limits normal query results to 200 rows by default.
- Detects result truncation with
:truncated?. - Uses query timeout
0by default. - Query timeout
0disables the timeout. - Provides
validate-on-connection!for parse, bind, and read-only checks. exec-prepared-on-connection!prepares every statement before the first execution.- A prepare failure stops the batch before a mutation runs.
app.graph.schema
- Provides the public schema facade.
- Exposes schema version
penpot-graph-slice-4. - Delegates node and relationship definitions to
app.graph.schema.nodes.
app.graph.schema.nodes
- Holds the single registry for graph node tables.
- Generates node DDL.
- Generates relationship DDL.
- Maps Penpot shape types to graph tables.
- Projects source attributes into graph attributes.
- Formats graph column values.
- Quotes reserved graph labels such as
GroupandBoolean. - Defines container tables and shape tables.
- Defines
IsChildOf,IsInstanceOf,RefersTo, andFillsSwapSlot.
app.graph.schema.contract
- Records deliberate graph contract decisions.
- Renames graph columns such as
:revntorevision. - Drops attributes that do not belong in this graph slice.
- Records attributes that the graph does not project.
- Applies per-table dropped attributes.
- Defines type overrides for vectors, transforms, colors, maps, and JSON arrays.
- Maps selected map keys to the frontend JSON naming convention.
:background-blurremains a declared unprojected attribute.
app.graph.schema.projection
- Derives projected schemas from canonical Malli schemas.
- Builds the projected document schema.
- Builds projected shape schemas.
- Selects the schema for each shape type.
app.graph.schema.types
- Maps Malli types to Ladybug types.
- Maps matrices to
DOUBLE[6]. - Maps points to
DOUBLE[2]. - Maps rectangles to
DOUBLE[4]. - Maps colors to
UINT32. - Maps collections to Ladybug arrays.
- Maps
:map-ofschemas toMAP. - Maps closed scalar maps to
STRUCT. - Maps other complex values to
JSON.
app.graph.schema.values
- Coerces source values to graph column values.
- Writes fixed vectors with deterministic order.
- Packs colors into the graph color representation.
- Sorts set values when deterministic output is needed.
app.graph.arrow
- Loads projection rows with Apache Arrow.
- Creates temporary staged node and relationship tables.
- Uses
COPY ... FROM (MATCH ...)for bulk loading. - Groups relationship loads by source and target table pair.
- Resolves relationship endpoints with joins.
- Does not use
createArrowRelTablefor UUID relationship endpoints. - Keeps the Arrow
RootAllocatoralive until Ladybug releases staged buffers. - Closes the allocator after the connection and database close sequence.
app.graph.ingest
- Fetches a complete file with
bfc/get-fileand:realize? true. - Rejects missing files.
- Rejects files without file data.
- Can run file data validation before projection.
- Creates the DDL.
- Loads nodes and edges through Arrow.
- Executes post-load transforms.
- Writes graph metadata last.
- Treats the final metadata write as the complete-build marker.
- Supports a persistent database path and an open connection.
app.graph.projection.document
- Projects
Document,Page,Component, and supported shape nodes. - Skips the page root frame.
- Creates
IsChildOfedges from shapes to parents. - Creates page edges to the document.
- Creates component edges to the document.
- Stores page order in
Page.indexand edgeposition. - Reverses the stored
:shapeslist for Penpot z-order. - Adds
page-idto every projected shape. - Propagates an instance head
component-idto descendants. - Stops component inheritance at a non-Frame shape with its own component ID.
- Skips deleted components during cold projection.
- Logs unsupported shape types and missing shape records.
app.graph.projection.transforms
- Runs after the base nodes and edges load.
link-component-instancescreatesIsInstanceOfedges.- A Frame needs
component-fileto qualify as an instance head. link-shape-refscreatesRefersToedges fromshape-ref.- Ladybug limits multi-label relationship
MERGEstatements. - The transform emits one statement for each shape-table pair.
link-swap-slotscreatesFillsSwapSlotedges.- Swap slot IDs come from
swap-slot-<uuid>entries intouched. - The transform removes swap slot entries from
touchedafter edge creation. - The transform order matters because it reads and then changes
touched.
app.graph.meta
- Stores graph provenance in
GraphMeta. - Stores schema version, source revision, producer, and build time.
- The source revision identifies the file revision used for cold projection.
app.graph.stats and app.graph.report
app.graph.statscounts graph nodes and relationships from the live catalog.app.graph.reportprints ingest information for REPL use.
Cold Projection Flow
- Get the file row and realized file data from PostgreSQL.
- Read the file revision from the file row.
- Build the node and edge projection.
- Create all graph tables from the graph schema.
- Load node rows with Arrow.
- Load relationship rows with Arrow.
- Run
CHECKPOINT;. - Run the registered derived transforms.
- Write
GraphMetaas the final build step. - Return file ID, file revision, database path, projection stats, and transform stats.
Projection node groups
Documentcontains file-level attributes without the file data blob.Document.optionsreceives file-level options from the data blob.Pagecontains page attributes without the page object map.Componentcontains component attributes without component object maps.- Shape tables contain the supported shape attributes.
- The graph stores selected derived attributes such as
page-id.
Projection relationship groups
- Structural edges use
IsChildOf. - Page and component edges point to
Document. - Derived edges come from the post-load transform registry.
Incremental Sync
Change source
app.rpc.commands.files-updatepersists the file update first.- The same command publishes a
:file-changemessage to the file topic. - The topic key is the file UUID.
- The message contains the file ID, profile ID, session ID, revision, version, and changes.
- Library changes also publish a team-topic message.
- The graph session only consumes the file-topic
:file-changemessages.
Session subscription
app.graph.debug/start-sync-loop!creates a channel with a dropping buffer of 64.- The session subscribes the channel to the file UUID topic.
- The loop reads one message at a time.
- The loop ignores message types other than
:file-change. - The loop stops when the channel closes.
destroy-session!closes the channel and purges its message bus subscription.
Session state
- Sessions are stored in a global
defonceatom. - The map key is the string form of
profile-id. - One profile has one graph session.
- Loading another file first destroys the old session.
- A session stores the Ladybug database and connection.
- A session stores a shared lock for graph access.
- A session stores file metadata.
- A session stores the incremental sync index.
- A session stores the message bus channel.
- A session stores load time and profile ID.
- The session keeps projection statistics but drops full projection rows after index creation.
Sync index
build-indexstarts from the complete cold projection.- The index stores the graph file ID and document ID.
- The index stores the current graph revision.
- The index stores page IDs, names, and positions.
- The index stores component IDs, names, and deleted state.
- The index stores shape table, parent, position, frame, page, and component context.
- The index stores child IDs by parent ID.
- The index supports later change application without another PostgreSQL file read.
Change application
apply-changes!processes the change list in source order.- Each supported change returns a new index and a list of Cypher statements.
- Unsupported changes enter the
:skippedresult. - Supported changes enter the
:appliedresult. - The function collects all statements before it executes them.
- The function appends a document revision statement when at least one change applies.
- The index revision advances only when at least one change applies.
- A larger incoming revision than the index revision creates a warning.
- A revision gap does not trigger catch-up.
Shape change rules
:add-objreusesprojection.document/denormalized-shape.:add-objcreates the shape node and its parent edge.:mod-objapplies supported:setoperations to graph columns.:mod-objkeeps false and zero values as values.:del-objdeletes shapes in deep post-order.:mov-objectsdetaches shapes from the old parent.:mov-objectscloses the old sibling position gap.:mov-objectsinserts shapes at the new position.:mov-objectsupdatesparent_idandframe_id.:mov-objectsrewrites containershapesvalues.- The parent columns and child lists must match a cold projection.
Page and component change rules
- Page add creates a projected page node and a document edge.
- Page delete removes the page subtree.
- Page modification updates supported page attributes.
- Component add creates a component node and document edge.
- Component modification updates supported component attributes.
- Component delete uses a soft-delete state.
- Component restore removes the soft-delete state.
- Component purge removes the component node and document edge.
- Component sync paths need more parity coverage than the current tests provide.
Session Locking
- The sync loop and HTTP handlers share one lock per session.
- The lock protects one Ladybug connection from concurrent access.
- Queries acquire the lock before binder validation and execution.
- Graph data export acquires the lock before catalog reads.
- Session export acquires the lock before
EXPORT DATABASE. - A long query blocks sync for the same session.
- A sync batch blocks queries for the same session.
- Ladybug connection thread safety is not assumed.
Graph Query Rules
- The console accepts Cypher text.
- Blank query text raises a validation error.
- The query first passes Ladybug prepare and bind checks.
- The query must pass the engine read-only analysis.
- A mutating query is rejected.
- The graph console does not provide a write path.
- A session graph is rebuilt from the file by Reload.
- Normal query results have a 200-row limit.
- Query results use string values for the HTML console representation.
- JSON requests receive a Transit JSON response with the query and result.
- HTML requests receive the rendered console with the result.
Graph Data Export
G6 data
/dbg/actions/graph-datareads the live Ladybug database.- It does not read the sync index for nodes and edges.
- It therefore shows database drift if a batch fails after index update.
- Node export covers all registered node tables.
- Relationship export reads the Ladybug relationship catalog.
- Relationship export includes source, target, relationship name, and position.
- Node and relationship export uses a 100,000-row limit.
- The response reports
truncatedwhen a limit cuts the result. - The response reports buffer-manager memory usage.
.lbug export
source=filerebuilds the persistent graph from PostgreSQL file data.source=fileruns a synchronous full ingest for each request.source=sessionexports the caller profile's live in-memory graph.- Session export uses Ladybug
EXPORT DATABASEto Parquet files. - Session export creates a new
.lbugdatabase withIMPORT DATABASE. - The temporary Parquet staging directory is deleted after import.
- The final session
.lbugfile remains in the system temporary directory. - The HTTP response streams the database file to the caller.
HTTP Routes and Access
- The graph routes live in
backend/src/app/http/debug.clj. - The graph route list is added only when
:graphis enabled. /dbg/graphserves the graph console page./dbg/actions/graph-filesreturns the profile file tree./dbg/actions/graph-loadloads a file into the profile session./dbg/actions/graph-unloadcloses the profile session./dbg/actions/graph-reloadrebuilds the loaded file graph./dbg/actions/graph-queryruns a read-only Cypher query./dbg/actions/graph-sync-statusreturns the sync state./dbg/actions/graph-datareturns nodes and edges for G6./dbg/actions/graph-exportstreams a.lbugdatabase.- The
/dbgsession middleware remains active. - The
/dbgadmin middleware remains active. - A devenv host with a profile ID passes the debug authorization rule.
- Other hosts need a profile email in the configured admin set.
/dbg/actions/graph-fileslists reachable teams, projects, and files.- The file tree query has a 500-file limit.
- The graph handlers resolve graph namespaces at call time.
- The backend requires
app.graph.debugandapp.graph.ingestwhen the flag is on. - Ladybug native loading then fails during route initialization instead of first use.
Console Frontend
Page type
graph-console.tmplis a backend resource template.- It is not a Rumext component.
- It is not part of the main frontend route table.
- The page uses browser
fetchcalls and a browser WebSocket. - The page loads G6 version
5.1.1from jsDelivr.
File tree
- The page fetches
/dbg/actions/graph-files. - The response contains team, project, and file groups.
- The page creates the tree with DOM APIs.
- A file click submits the graph load form.
- The page shows a message when no file exists.
Graph rendering
- The page fetches
/dbg/actions/graph-data. - The page converts graph nodes and edges to G6 data.
- The page skips repaint when the node and edge signature does not change.
- The page marks added, removed, and changed graph entities.
- The page supports tree, dagre, circular, force, and combo layouts.
- The page supports collapsed container combos.
- The page has render guards at 4,000 nodes and 8,000 edges.
- The
?safequery option bypasses the render guard. - The page shows graph size by node count and relationship count.
- The page shows buffer-manager memory in MiB.
- The page reports a CDN failure when G6 is undefined.
Query result filtering
- A query can return
filter_*columns with node IDs. - The HTML result table hides columns with the
filter_prefix. - The JSON result keeps the full result.
- The graph view uses the hidden IDs to select matching nodes.
- The graph view re-runs the query after graph refresh.
- This keeps the query filter aligned with the current graph.
- A user column named
filter_*follows the same hiding rule.
Node inspector
- A node click creates a query for that node.
- The inspector calls
/dbg/actions/graph-querywith JSON negotiation. - The inspector displays the full projected row.
- The inspector uses table and ID values from the graph data.
WebSocket Data Flow
- The page opens
/ws/notificationswith a randomsession-idquery value. - The page sends
:subscribe-filewith a Transit UUID value. - The server makes sure that the file exists and that the profile has read permission.
- The server subscribes the connection to the file topic.
files_updatepublishes:file-changeto the same topic.- The graph session consumes the message from its message bus subscription.
- The WebSocket server sends the message to the browser connection.
- The browser adds the change to the changelog.
- The browser fetches sync status after 150 milliseconds.
- The browser fetches graph data after a 400-millisecond debounce.
- The browser repaints the G6 graph when the graph data changes.
WebSocket reconnect behavior
- The page reconnects after three seconds when the socket closes.
- The page resubscribes to the file after the socket opens.
- The page refreshes sync status after reconnect.
- The page refreshes graph data after reconnect.
- Reconnect does not recover dropped message-bus changes.
- The page shows the sync error or skipped-change state when the status reports it.
Feature Flag and Runtime Dependencies
:graphis defined incommon/src/app/common/flags.cljc.- The flag is off by default.
com.ladybugdb/lbugversion0.19.1is a backend dependency.org.apache.arrow/arrow-memory-nettyversion18.2.0supports ArrowRootAllocator.- The JVM uses
--enable-native-access=ALL-UNNAMED. - The JVM uses
--add-opens=java.base/java.nio=ALL-UNNAMED. - The JVM uses
--sun-misc-unsafe-memory-access=allow. - The JVM options appear in the development alias and backend launch scripts.
- A Ladybug version change needs new binder and parity tests.
- A JDK version change needs a startup test with the graph flag enabled.
Tests
backend-tests.graph-sync-parity-test
- Uses two Ladybug
:memory:databases. - Does not use PostgreSQL or a live graph session.
- Projects initial file data into database A.
- Applies changes to database A through incremental sync.
- Applies the same changes to file data.
- Projects the changed file data into database B.
- Compares every node row and relationship row.
- Reports differences by table, row key, and column.
- Covers shape add, shape modification, shape deletion, movement, and page changes.
- Contains a test that injects a sync defect and expects a graph difference.
- Does not cover all component change variants.
- Does not cover every movement insertion mode.
backend-tests.graph-binder-gate-test
- Creates the live graph DDL in a Ladybug
:memory:database. - Prepares each sync statement template without executing it.
- Detects parse errors and missing tables.
- Detects missing columns and bad label quoting.
- Reports the expected read-only classification.
- Covers reserved node labels across the node registry.
- Reports an error result for an invalid statement.
Test gaps
- No automated HTTP handler tests cover graph routes.
- No automated session lifecycle tests cover load and unload.
- No automated WebSocket tests cover graph subscription.
- No automated export tests cover persistent and session sources.
- Component add, modify, delete, restore, and purge need parity tests.
- Page delete needs parity coverage.
- Movement with
:after-shapeneeds parity coverage. - Buffer overflow and revision gap behavior need tests.
- Partial batch failure and recovery need tests.
- Query timeout and long-query behavior need tests.
Known Risks and Limits
Dropped changes
- The sync channel uses a dropping buffer of 64.
- A burst can discard file-change messages.
- The sync loop logs a revision gap when it sees a larger revision.
- The sync loop does not fetch missing rows from
file_change. - Reload is the only built-in recovery path.
Partial batch state
apply-changes!does not provide Ladybug transaction atomicity.- A statement failure can leave a partly changed graph.
- The in-memory index can advance before the database state is complete.
/dbg/actions/graph-datareads the database and exposes this drift.- Reload rebuilds the graph from PostgreSQL file data.
Query resource use
- The default session query timeout is zero.
- A costly query can hold the session lock for a long time.
- The same lock blocks incremental sync.
- The graph export also holds the same lock during catalog reads.
- The graph schema has a high memory floor.
- The console reports about 115 MiB for the wide slice before file data.
Session lifecycle
- Sessions have no TTL.
- Sessions remain until unload, replacement, or process shutdown.
- Each session owns native Ladybug memory.
- Many profiles can create many native databases.
- A profile load replaces its previous session.
- Two browser tabs for one profile share one graph session.
Temporary files
- Session export leaves the final
.lbugfile in the system temporary directory. - Long-lived servers can accumulate exported session databases.
- The staging directory is deleted after import.
Browser dependency
- The graph view depends on a runtime CDN request.
- A network restriction can remove the G6 view.
- Queries and session status still use backend endpoints without G6.
Data exposure
- The graph console can list many files available to the profile.
- The console can load complete projected file data.
- The console can export a graph database.
- The console can inspect all projected node attributes.
- The console is safe only when the
/dbgaccess boundary is correct. - The graph flag must remain off for deployments that do not need this tool.
Contract drift
- The graph schema is a deliberate slice of the Penpot file model.
- New source attributes do not enter the graph automatically in all cases.
- Dropped and unprojected attributes need an explicit contract decision.
applied_tokenskey mapping depends on the JSON naming function.filter_*is a frontend convention, not a graph schema guarantee.
Ladybug dialect coupling
- Cypher strings contain Ladybug-specific syntax.
- Label quoting handles reserved labels explicitly.
- Relationship transforms depend on Ladybug relationship limits.
- Arrow loading depends on Ladybug
COPY FROM (MATCH ...)behavior. - A dependency upgrade needs schema, binder, Arrow, and parity checks.
REPL Helpers
app.srepl.mainresolves graph functions only when a helper runs.graph-smoke-test!runs a basic Ladybug operation.graph-query-test!runs a graph query test.ingest-file-to-graph!projects a file into a graph database.- These helpers use
requiring-resolveto keep the graph dependency lazy.
Operational Invariants
- PostgreSQL file data remains authoritative.
- Cold projection and incremental sync must produce equal graph state.
- The graph revision must identify the last applied file revision.
- The document revision must update when a sync batch applies.
- A missing or skipped change must remain visible in sync status.
- A graph query from the console must be read-only.
- A graph session must serialize connection access.
- Graph routes must remain behind the
:graphflag and/dbgaccess control. - The Arrow allocator must outlive all Ladybug operations that use its buffers.
GraphMetamust be written after the full ingest and transforms finish.
Key Files
backend/src/app/graph/ladybug.clj: Ladybug API and query gates.backend/src/app/graph/arrow.clj: Arrow bulk load.backend/src/app/graph/ingest.clj: Complete file ingest.backend/src/app/graph/debug.clj: Session lifecycle, sync loop, query, and export.backend/src/app/graph/sync.clj: Incremental change application.backend/src/app/graph/meta.clj: Graph provenance.backend/src/app/graph/stats.clj: Graph counts.backend/src/app/graph/report.clj: REPL ingest report.backend/src/app/graph/projection/document.clj: Base document projection.backend/src/app/graph/projection/transforms.clj: Derived relationship transforms.backend/src/app/graph/schema/nodes.clj: Node and relationship registry.backend/src/app/graph/schema/contract.clj: Projection contract decisions.backend/src/app/graph/schema/projection.clj: Malli projection schemas.backend/src/app/graph/schema/types.clj: Malli-to-Ladybug type mapping.backend/src/app/graph/schema/values.clj: Value coercion.backend/src/app/http/debug.clj: Graph route registration and handlers.backend/src/app/http/websocket.clj: File WebSocket subscription handlers.backend/src/app/rpc/commands/files_update.clj: File-change publication.backend/src/app/main.clj: Integrant message bus wiring.backend/resources/app/templates/graph-console.tmpl: Graph console browser code.backend/resources/app/templates/debug.tmpl: Debug page graph links.common/src/app/common/flags.cljc::graphfeature flag.backend/test/backend_tests/graph_sync_parity_test.clj: Cold versus sync parity.backend/test/backend_tests/graph_binder_gate_test.clj: Cypher binder gate.
Development Commands
- Run backend commands from the
backend/directory. - Run focused parity tests with
clojure -M:dev:test --focus backend-tests.graph-sync-parity-test. - Run focused binder tests with
clojure -M:dev:test --focus backend-tests.graph-binder-gate-test. - Run the backend test suite with
clojure -M:dev:test. - Examine Clojure formatting with
pnpm run check-fmt:clj. - Run backend Clojure lint with
pnpm run lint:clj. - Write test output to a file before reading or filtering it.