[DJ Semantic Fingerprint 2] Evaluate fingerprints across deployment graphs - #2488
Merged
Conversation
philipfweiss
marked this pull request as ready for review
September 1, 2026 23:24
philipfweiss
force-pushed
the
semantic-fingerprint-deployment-graph
branch
from
September 2, 2026 01:53
da0a6ef to
1783278
Compare
betodealmeida
self-requested a review
September 2, 2026 17:08
philipfweiss
force-pushed
the
semantic-fingerprint-deployment-graph
branch
from
September 2, 2026 19:46
1783278 to
de5ed41
Compare
shangyian
reviewed
Sep 2, 2026
added 7 commits
September 3, 2026 12:21
Resolve every semantic parent edge and evaluate current and proposed Merkle hashes with iterative SCC handling so deep chains, cycles, and unavailable ancestors produce stable deployment results.
A graph-owned API resolves parents once and memoizes node hashes. Incomplete snapshots return unknown, preventing plausible hashes built from omitted parents.
Treat dimension paths as ordered node-name candidates so nested struct fields resolve to the longest existing node. Consolidate graph extraction and remove the unused parse-tolerance path.
philipfweiss
force-pushed
the
semantic-fingerprint-deployment-graph
branch
from
September 3, 2026 19:27
1ebbd8d to
a7a25ff
Compare
✅ Deploy Preview for thriving-cassata-78ae72 canceled.
|
Use the deployment utility from orchestration and fingerprinting so parser behavior stays aligned and every branch is tested.
…deployment-graph # Conflicts: # datajunction-server/datajunction_server/internal/deployment/orchestrator.py
shangyian
approved these changes
Sep 4, 2026
shangyian
left a comment
Collaborator
There was a problem hiding this comment.
Thanks for addressing comments, looks good!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fingerprint construction primitives can compose parent hashes, but deployment code has no server-owned evaluator for the full semantic parent graph. Cyclic dimension links also have no topological starting node, and recursive graph traversal can exceed Python's recursion limit on deep chains.
Depends on #2482.
This PR:
SemanticFingerprintGraphfor lazy, memoized evaluation within one graph snapshotCallers construct one graph for each current or proposed snapshot, then use
fingerprint(name)orfingerprints(names). Missing parents returnunknown, so an incomplete snapshot cannot produce a plausible graph-bound hash.Why cyclic graphs need SCC hashing
Direct Merkle evaluation cannot compute
A,B, orChere because every object hash requires another unfinished object hash:flowchart LR A["dimension A<br/>h_A = H(f_A, h_B)"] B["dimension B<br/>h_B = H(f_B, h_C)"] C["dimension C<br/>h_C = H(f_C, h_A)"] A -->|requires h_B| B B -->|requires h_C| C C -->|requires h_A| A classDef cycle fill:#fee2e2,stroke:#dc2626,color:#450a0a class A,B,C cycleThe evaluator uses an iterative two-pass SCC traversal. Its graph walk is
O(V + E), whereVis the node count andEis the semantic parent edge count. Deterministic ordering adds sorting overhead. Explicit stacks avoid Python recursion limits on deep dependency chains. Parent discovery, SQL parsing, and database loading occur outside this graph-walk bound.Each cycle becomes one atomic component in a condensation graph, which is always a DAG:
flowchart LR P["external parent P<br/>object hash: h_P"] SCC["SCC {A, B, C}<br/>component hash: h_scc"] A["dimension A<br/>h_A = H(f_A, h_scc)"] B["dimension B<br/>h_B = H(f_B, h_scc)"] C["dimension C<br/>h_C = H(f_C, h_scc)"] D["downstream node D<br/>object hash changes"] P -->|external parent hash| SCC SCC --> A SCC --> B SCC --> C A --> D B --> D C --> D classDef parent fill:#fff1d6,stroke:#c2410c,color:#431407 classDef component fill:#ede9fe,stroke:#7c3aed,color:#2e1065 classDef member fill:#ecfdf5,stroke:#059669,color:#064e3b classDef downstream fill:#dbeafe,stroke:#2563eb,color:#172554 class P parent class SCC component class A,B,C member class D downstreamChanging a member definition, internal edge, or external parent hash changes
h_scc, every member hash, and downstream hashes. Acyclic singleton components keep the ordinary Merkle path. An unavailable member or external parent makes the component and its dependents unavailable.Verification:
Observed:
djand deployedgraph_verification.external_sourceso the graph evaluator could load a parent from committed database state:SemanticFingerprintGraphinside the backend container with:docker exec -i dj-s2 /code/.venv/bin/python -The script exercised these inputs directly against the code at
/code/datajunction_server:source -> transform -> metric, then changed only the source tablefirst -> second -> third -> firstplus a downstream node, with reversed input order and a changed cycle memberObserved:
{ "acyclic_propagation": { "all_descendants_changed": true, "changed": [ "verify.metric", "verify.source", "verify.transform" ], "memoized": true }, "cycle_evaluation": { "input_order_stable": true, "member_change_propagates": true, "members": [ "cycle_verify.downstream", "cycle_verify.first", "cycle_verify.second", "cycle_verify.third" ] }, "deep_chain": { "nodes": 1100, "target": "deep_verify.node_1099", "target_known": true }, "external_ancestor": { "loaded_from_database": true, "matches_expected_merkle_hash": true, "fingerprint": { "version": 1, "digest": "ecce67b36517bb7ad6ce126902d0116909759c96838b08d5ce1b372cd96a4aac" } }, "failure_propagation": { "verify.invalid": "unknown", "verify.invalid_dependent": "unknown", "verify.unresolved": "unknown", "verify.unresolved_dependent": "unknown" }, "required_dimension_identity_stable": true }Semantic fingerprint stack
This is PR 2 of 4: