Skip to content

Break the last source dependency cycle, and forbid new ones - #8476

Merged
Amaury Chamayou (achamayou) merged 1 commit into
mainfrom
achamayou-issue-3517-clarify-dependencies-between-framework-s-350c39
Sep 30, 2026
Merged

Amaury Chamayou (achamayou) merged 1 commit into
mainfrom
achamayou-issue-3517-clarify-dependencies-between-framework-s-350c39

Conversation

@achamayou

@achamayou Amaury Chamayou (achamayou) commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Motivation

Closes #3517. This is the last step of the plan in the issue: remove js -> node.

Since #8475, the only cycle left in the source dependency graph is js <-> node. js is in it only because eight of its #include directives name node headers. None of them is the JS runtime depending on the node. They are governance-only extensions that happen to live in src/js/, two node headers that belong lower down, and one stale include. This PR moves them. The graph has no cycles, every component has a dependency policy, and the checker now rejects any change that would add a cycle back.

Before After
Direct internal edges 82 81
Components in a cycle js, node none
Components with a dependency policy 23 of 25 25 of 25

Following includes transitively, src/js/ now reaches 145 files outside itself instead of 200, and none in node (previously 9).

The resulting layering (transitive reduction of the 81 edges)

An arrow is omitted when a longer path implies it. msgpack and threading have no internal dependencies in either direction.

flowchart TD
  host --> enclave
  enclave --> indexing
  enclave --> common
  indexing --> node
  apps --> js
  node --> js
  node --> consensus
  node --> snapshots
  node --> tls
  node --> ledger
  js --> endpoints
  js --> pal
  common --> service
  consensus --> service
  endpoints --> service
  endpoints --> http
  snapshots --> http
  snapshots --> http_client
  rust --> kv
  service --> kv
  kv --> crypto
  http --> crypto
  pal --> crypto
  tls --> crypto
  tls --> tasks
  tls --> tcp
  http_client --> uv
  http_client --> ds
  tasks --> ds
  crypto --> ds
  msgpack
  threading
Loading

Implementation summary

Code moves only, one per removed include:

  • The governance extensions move from js/extensions/ccf/ to node/gov/extensions/ (five includes). These are NetworkExtension (network.h/.cpp), NodeExtension (node.h/.cpp) and the implementation of GovEffectsExtension (gov_effects.cpp). They install ccf.network.*, ccf.node.*, ccf.setJwtPublicSigningKeys() and so on for the constitution's apply(), and their only installer is node/gov/handlers/proposals.h. GovEffectsExtension keeps its public declaration in ccf/js/extensions/ccf/gov_effects.h, as build_receipt_for_committed_tx() did in Take endpoints out of the source dependency cycle #8475. All three stay in the ccf_js library, so linking does not change. member_frontend.h included network.h and node.h but used neither. It now includes node/network_state.h, which is what it needed from them.
  • gov_logging.h moves from node/rpc/ to ds/. It only defines the GOV_*_FMT macros over ds/internal_logger.h, the governance counterpart of CCF_APP_*. console.cpp uses them for console.log() in governance contexts.
  • UVM endorsement verification (uvm_endorsements.h/.cpp) moves from node/ to pal/. The public ccf/pal/uvm_endorsements.h already declared ccf::pal::verify_uvm_endorsements_descriptor(), but node implemented it. snp_attestation.cpp needs it for ccf.snp_attestation.verifySnpAttestation(). The helpers it uses from node/cose_common.h are generic COSE/CWT parsing: COSEDecodeError, COSESignatureValidationError, CwtClaims, decode_cwt_claims() and validate_cwt_iat_against_x5chain(). They move to crypto/cose_utils.h, next to parse_x5chain(), so pal still only depends on crypto and ds. cose_common.h includes cose_utils.h, so its users see the same names, and the CCF receipt decoding stays in node. The moved header drops its unused direct include of ccf/service/tables/uvm_endorsements.h. ccf/pal/uvm_endorsements.h still includes it for the DID and Feed aliases. uvm_endorsements.cpp is compiled into the same targets as before.
  • converters.cpp drops node/rpc/jwt_management.h. It used nothing JWT-specific, only the Pem, verifier and Sha256Hash declarations for ccf.pemToId(). It now includes those directly.

source-dependencies.json gains policies for js and node, which are exactly the measured sets.

check-source-dependencies.py now also fails if:

  • a src/ component has no policy, or
  • the policies allow a cycle. This uses graphlib, and the error prints the cycle.

The existing check matches every include against its component's policy. With both new checks, the include graph is a subgraph of an acyclic policy graph, so no cycle can form without a policy change that the checker rejects. For example, adding node to js's policy fails with Dependency policy allows a cycle: js -> node -> js. A new component such as src/tracing fails until it gets a policy. This PR's policy applied to main's sources reports all eight js -> node includes, and main's policy applied to this checker reports Missing dependency policy for: js, node.

Finally, src/kv/README.md claimed that kv only depends on ds. It was one of the rotted dependency READMEs that opened this issue, and it now points to the enforced policy.

Validated locally with a clang 21 Debug build:

  • All targets build without warnings.
  • All 62 unit tests pass, including js_test, js_policy_test, endorsements_test, cose_test, frontend_test, node_frontend_test, internal_tables_access_test and historical_queries_test.
  • programmability_and_jwt passes. It covers ccf.snp_attestation.verifySnpAttestation(), with UVM endorsements verified in src/pal/uvm_endorsements.cpp, and JWT signing keys set and removed through governance.
  • includes-checks.sh (including the dependency checker), clang-format 18, black, prettier, gersemi, and the ASCII, copyright and TODO checks pass.
  • governance_test did not pass in a single local run, so CI needs to confirm it. Each of its nine sub-tests passed in at least one of four full runs. Every failure was a spurious election or a dropped connection (SessionConsistencyLost, PrimaryNotFound, RPC could not be forwarded to unknown primary, TransactionReplicationFailed, CCFConnectionException), in a different sub-test each time. This machine is slow for the test, with fsync stalls of up to 12s. With sub-tests run one at a time, only member_client failed, with a ballot rejected with 503 after an election. With the workspace on tmpfs, member_client passed and only session_coseauth lost its connection. None of the failures involves an apply() error, and the logs show the constitution calling the moved ccf.node.* and console code.

Safety and compatibility

Production behaviour does not change. Every function and type keeps its name, namespace and body. Only the file that defines it changes.

  • The constitution's ccf.network.*, ccf.node.* and JWT signing key functions, console.* logging, UVM endorsement verification for node joins and ccf.snp_attestation.verifySnpAttestation(), and COSE receipt decoding run the same code as before.
  • Every moved .cpp stays in the library or test targets it was built into, so applications compile and link as before.

Nothing under include/ changes. There are no wire, ledger or KV format changes, and no effect on mixed-version operation or recovery. Nothing is user-facing, so there is no CHANGELOG.md entry.

Remove the eight js -> node includes, so the source component graph is
acyclic:

- Move the governance-only JS extensions (ccf.network, ccf.node and the
  GovEffectsExtension implementation) to node/gov/extensions.
- Move gov_logging.h from node/rpc to ds.
- Move UVM endorsement verification from node to pal, and the generic
  COSE/CWT helpers it uses from node/cose_common.h to
  crypto/cose_utils.h.
- Drop a stale jwt_management.h include from converters.cpp.

Add dependency policies for js and node, and make
check-source-dependencies.py require a policy for every source component
and reject policies that allow a cycle.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings September 30, 2026 16:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

It moves governance and attestation code across component boundaries, while the full governance test remains unconfirmed.

Review effort: Balanced
Findings: None

What changed in this PR

Removes the final js-to-node source dependency and enforces an acyclic dependency policy across all source components.

Changes:

  • Relocates governance extensions, logging helpers, and UVM verification to appropriate lower-level components.
  • Moves shared COSE/CWT utilities from node into crypto.
  • Adds complete dependency policies and rejects missing or cyclic policies.

Custom instructions used

  • .github/copilot-instructions.md
  • .github/instructions/reviewing.instructions.md
  • .github/skills/testing/SKILL.md
  • .github/skills/formatting-and-linting/SKILL.md
File Description
CMakeLists.txt Updates relocated source paths in libraries and tests.
scripts/​check-source-dependencies.py Rejects missing and cyclic dependency policies.
scripts/​source-dependencies.json Adds policies for js and node.
src/​crypto/​cose_utils.h Hosts shared COSE errors and CWT utilities.
src/​ds/​gov_logging.h Relocates governance logging macros.
src/​js/​extensions/​ccf/​converters.cpp Replaces a node include with direct crypto includes.
src/​js/​extensions/​console.cpp Uses the relocated governance logger.
src/​js/​extensions/​snp_attestation.cpp Uses PAL-owned UVM verification.
src/​kv/​README.md Points dependency documentation to enforced policy.
src/​node/​cose_common.h Removes utilities moved into crypto.
src/​node/​gov/​extensions/​gov_effects.cpp Relocates governance-effects implementation.
src/​node/​gov/​extensions/​network.cpp Relocates network extension implementation.
src/​node/​gov/​extensions/​network.h Relocates network extension declaration.
src/​node/​gov/​extensions/​node.cpp Relocates node extension implementation.
src/​node/​gov/​extensions/​node.h Relocates node extension declaration.
src/​node/​gov/​handlers/​helpers.h Uses the relocated governance logger.
src/​node/​gov/​handlers/​proposals.h Includes governance extensions from node.
src/​node/​internal_tables_access.h Uses PAL-owned UVM verification.
src/​node/​node_state.h Uses PAL-owned UVM verification.
src/​node/​quote.cpp Uses PAL-owned UVM verification.
src/​node/​rpc/​member_frontend.h Removes unused JS extension dependencies.
src/​node/​rpc/​node_call_types.h Uses PAL-owned UVM verification.
src/​node/​test/​endorsements.cpp Updates the relocated UVM include.
src/​pal/​test/​verify_uvm_attestation_and_endorsements.cpp Updates the relocated UVM include.
src/​pal/​test/​verify_uvm_attestation_and_endorsements.h Updates the relocated UVM include.
src/​pal/​uvm_endorsements.cpp Relocates UVM verification implementation.
src/​pal/​uvm_endorsements.h Uses shared crypto COSE utilities.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@achamayou
Amaury Chamayou (achamayou) merged commit 3e48ee8 into main Sep 30, 2026
13 checks passed
@achamayou
Amaury Chamayou (achamayou) deleted the achamayou-issue-3517-clarify-dependencies-between-framework-s-350c39 branch September 30, 2026 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Clarify dependencies between framework source components

3 participants