When Documentation and Code Diverge: Tracing PCE Extraction Gaps in CuZK

Introduction

In the development of high-performance zero-knowledge proving systems, few things are more valuable than a healthy dose of skepticism. This article examines a pivotal moment in an opencode coding session where a user's simple question—"Are you sure the automatic PCE extraction is actually enabled?"—triggered a deep verification process that uncovered a significant gap between documented behavior and actual implementation. The message in question, delivered by the AI assistant at index 12 of the conversation, represents a correction: an acknowledgment that earlier claims about Pre-Compiled Constraint Evaluator (PCE) support for all proof types were incorrect, and that only one of the four proof types actually had automatic extraction wired up.

This moment matters because it illustrates a recurring challenge in complex systems engineering: documentation and architecture diagrams often describe an aspirational state, while the actual code may contain gaps, incomplete wiring, or unimplemented paths. The message also demonstrates a rigorous methodology for closing the gap between what we think the code does and what it actually does—by tracing concrete call sites, reading conditionals, and verifying assumptions against the source.

The Subject Message

The assistant's message, reproduced in full, reads:

You were right to question this. Here's what's actually going on:

>

## Automatic PCE extraction is only enabled for PoRep C2

>

All three background extraction call sites in engine.rs are PoRep-only:

>

- Line 1322-1334 (partition pipeline path): checks get_pce(&CircuitId::Porep32G) — hardcoded to PoRep 32G - Line 1529-1541 (slotted pipeline path): same — hardcoded to Porep32G - Line 1728-1741 (monolithic path): checks get_pce(&job.circuit_id) but then explicitly gates on if let ProofKind::PoRepSealCommit = proof_kind

>

The only extraction function that exists is extract_and_cache_pce_from_c1(), which is specific to PoRep — it deserializes SealCommitPhase1Output and builds a StackedCompound circuit.

>

There is no automatic background extraction for WinningPoSt, WindowPoSt, or SnapDeals. The generic extract_and_cache_pce() function at pipeline.rs:433 could handle any circuit type, but it's never called for non-PoRep proof types anywhere in the engine.

>

### What actually works today

>

| Proof Type | Auto-extract on first proof | Offline bench tool | Disk preload | |---|---|---|---| | PoRep C2 32G | Yes | Yes (cuzk-bench pce-bench) | Yes (if file exists) | | WinningPoSt | No | No | Yes (if file exists — but nothing creates it) | | WindowPoSt | No | No | Yes (if file exists — but nothing creates it) | | SnapDeals | No | No | Yes (if file exists — but nothing creates it) |

>

The preload_pce_from_disk() and get_pce() / synthesize_with_pce() infrastructure supports all four types, but the extraction path to actually create the PCE files only exists for PoRep C2. The statics (WINNING_POST_PCE, WINDOW_POST_PCE, SNAP_DEALS_PCE) and the disk file naming are all wired up, but nothing populates them.

>

So for now, you can only generate PCE for PoRep C2 32G — either automatically via the daemon's first proof, or explicitly via cuzk-bench pce-bench --c1 <file>.

This message is notable for its clarity, its direct acknowledgment of error, and its systematic presentation of evidence. It does not simply say "you're right, I was wrong"—it lays out why the earlier claim was incorrect, where the code falls short, and what the actual state of affairs is for each proof type.

The Backstory: How We Got Here

To understand why this message was written, we need to trace the conversation that preceded it. The session began with the user asking the assistant to read CuZK documentation and explain how to generate PCE ([msg 0]). The assistant returned a detailed summary of the documentation, describing three methods for generating PCE: automatic extraction on first proof, offline via the benchmark tool, and preloading from disk ([msg 2]). This summary was based on reading design documents like cuzk-project.md and cuzk-phase2-design.md, as well as the code structure.

The user then asked specifically about building PCE for all 32G proof types ([msg 3]). The assistant responded with a comprehensive table mapping each proof type to its PCE filename and described the automatic extraction flow as working for all four types ([msg 5]). This was the critical moment: the assistant asserted that "PCE is extracted on the first proof of each type and persisted to disk automatically," and described a flow where the daemon checks for all four PCE files on startup and triggers background extraction for any missing ones.

This claim was plausible because the infrastructure appeared to support it. The codebase had static OnceLock variables for WINNING_POST_PCE, WINDOW_POST_PCE, and SNAP_DEALS_PCE. The preload_pce_from_disk() function checked for all four filenames. The generic extract_and_cache_pce() function at pipeline.rs:433 accepted any circuit type. From a high-level architectural view, the system looked complete.

But the user was skeptical. They asked: "Are you sure the automatic PCE extraction is actually enabled?" ([msg 6]). This question was the catalyst for everything that followed.

The Verification Process

The assistant's response to the user's skepticism was methodical. Rather than defending its earlier answer or re-reading the same documentation, it went straight to the source code and began tracing the actual execution paths ([msg 7]). The first step was a grep for extract_and_cache_pce, which revealed that all call sites were in PoRep-specific contexts. The assistant then read the engine code around each call site to examine the conditionals.

The key discoveries came in rapid succession. At line 1322-1334 of engine.rs, the partition pipeline path hardcodes get_pce(&CircuitId::Porep32G). At line 1529-1541, the slotted pipeline path does the same. At line 1728-1741, the monolithic path appears more generic—it checks get_pce(&job.circuit_id)—but then explicitly gates on if let ProofKind::PoRepSealCommit = proof_kind. Every path that triggers extraction is PoRep-only.

The assistant also discovered that the only extraction function in existence is extract_and_cache_pce_from_c1(), which is hardcoded to deserialize SealCommitPhase1Output and build a StackedCompound circuit. There is no equivalent function for WinningPoSt, WindowPoSt, or SnapDeals. The generic extract_and_cache_pce() function exists but is never called outside of PoRep contexts.

This verification process is a textbook example of how to audit a claim about code behavior. The assistant did not rely on documentation comments, architecture diagrams, or variable names. It traced the actual control flow from the entry points (the three pipeline paths in engine.rs) to the extraction functions, examining every conditional gate along the way. It verified that the infrastructure for non-PoRep types (static variables, disk preload, synthesis with PCE) was wired up, but that the extraction trigger—the code that actually creates the PCE data—was missing.

The Key Finding: A Half-Built Bridge

The most striking aspect of the assistant's finding is the asymmetry it reveals. The CuZK codebase has a complete "consumption" path for all four proof types: preload_pce_from_disk() can load PCE files for WinningPoSt, WindowPoSt, and SnapDeals; get_pce() can retrieve them; synthesize_with_pce() can use them for fast proving. The static variables, the disk file naming convention, and the synthesis pipeline all treat the four types uniformly.

But the "production" path—the code that actually creates the PCE data in the first place—only exists for PoRep. This is like building a highway with three off-ramps that lead to a bridge that was never constructed. The system can use a PCE file for any proof type if one happens to exist, but it has no mechanism to create one for non-PoRep types.

This pattern is common in systems that evolve incrementally. PoRep was likely the first proof type implemented, and PCE was designed and optimized for it. The architecture was then generalized to support other proof types—the static variables and disk paths were added, the generic extract_and_cache_pce() function was written—but the final step of wiring up the extraction trigger for each new type was never completed. The codebase reflects an architectural vision that was partially implemented.

Assumptions and Mistakes

The assistant's initial mistake was trusting the documentation and the architectural surface area over the actual code. The design documents described automatic extraction for all proof types. The code had variables named WINNING_POST_PCE and functions like preload_pce_from_disk() that checked for all four files. These signals collectively suggested that the feature was complete.

The mistake was not unreasonable. In many codebases, the presence of infrastructure (static variables, disk paths, generic functions) is a reliable indicator that the feature is wired up. But in this case, the infrastructure was a promise that the code had not yet fulfilled. The assistant learned a valuable lesson: when dealing with complex systems, the only reliable source of truth is the concrete call sites and their conditional guards.

The user's skepticism, meanwhile, was well-founded. They had enough domain knowledge to suspect that the automatic extraction claim was too broad. This highlights an important dynamic in AI-assisted development: the assistant can read and summarize code quickly, but it may not have the domain-specific intuition to spot inconsistencies. The user, who understands the operational realities of the system, can provide that intuition.

Input and Output Knowledge

To fully understand this message, the reader needs several pieces of input knowledge. First, an understanding of what PCE is and why it matters: a performance optimization that pre-extracts R1CS constraint matrices into CSR format, trading a one-time extraction cost for faster per-proof synthesis. Second, familiarity with the four Filecoin proof types: PoRep (Proof of Replication), WinningPoSt (Winning Proof of Spacetime), WindowPoSt (Window Proof of Spacetime), and SnapDeals. Third, an understanding of the CuZK proving engine's pipeline architecture, including the partition pipeline, slotted pipeline, and monolithic path. Fourth, familiarity with Rust concepts like OnceLock for lazy initialization and the CircuitId enum for identifying proof types.

The output knowledge created by this message is a precise, verified mapping of PCE support across proof types. The table at the end of the message is the key artifact: it shows that PoRep has full support (auto-extraction, offline tool, disk preload), while the other three types have only disk preload—meaning they can use a PCE file if one exists, but nothing creates one. This knowledge is actionable: it tells a developer exactly what needs to be built (extraction triggers for WinningPoSt, WindowPoSt, and SnapDeals) and where the generic infrastructure already exists to support it.

The Thinking Process

The message reveals a clear thinking process, though the reasoning is presented in the final result rather than as a step-by-step trace. The assistant started with a hypothesis ("the documentation says auto-extraction works for all types"), then formulated a test ("let me check the actual call sites in engine.rs"), executed the test via grep and read operations, and synthesized the results into a coherent finding.

The reasoning is visible in the structure of the message itself. The assistant leads with the headline finding ("Automatic PCE extraction is only enabled for PoRep C2"), then presents three pieces of evidence (the three call sites), then explains why the extraction function is PoRep-specific, then summarizes the gap in a table, and finally explains the architectural asymmetry. This is not just a list of facts—it is an argument, structured to convince the reader that the finding is correct and to explain why the earlier claim was wrong.

Conclusion

The message at index 12 of this conversation is a model of intellectual honesty and methodological rigor. It demonstrates how to respond to skepticism not with defensiveness but with investigation. It shows the value of tracing code paths rather than trusting documentation. And it reveals a common pattern in complex systems: infrastructure that looks complete but has critical gaps in the wiring.

For the developer reading this article, the lesson is clear. When you need to know what a system actually does, do not read the documentation—read the conditionals. Trace the call sites. Verify that the extraction trigger exists, not just that the storage and retrieval paths are wired up. And when a user questions your assumptions, thank them. They may have just saved you from shipping a system that promises features it cannot deliver.