Repository navigation
Prove authorized staging execution and Pact replay protection #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5803eec
f65d1a3
b68546d
60e8015
46673b4
980557e
3700229
05fffca
48fd140
6cda108
abdee4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -146,7 +146,7 @@ jobs: | |
| run: | | ||
| for attempt in $(seq 1 36); do | ||
| status="$(curl -sS --max-time 120 -o /tmp/discover.json -w '%{http_code}' "$IGRAPH_URL/v1/discover?query=%2A" || true)" | ||
| echo "Discovery attempt $attempt returned HTTP $status" | ||
| echo "Wildcard discovery attempt $attempt returned HTTP $status" | ||
| if [ "$status" = "200" ] && jq -e ' | ||
| .live == true and | ||
| ((.candidates | length) > 0) and | ||
|
|
@@ -155,7 +155,7 @@ jobs: | |
| break | ||
| fi | ||
| if [ "$attempt" = "36" ]; then | ||
| echo "Live discovery did not return a dataset with loaded lineage" | ||
| echo "Live wildcard discovery did not return a dataset with loaded lineage" | ||
| cat /tmp/discover.json || true | ||
| exit 1 | ||
| fi | ||
|
|
@@ -167,11 +167,33 @@ jobs: | |
| (.candidates | length) > 0 and | ||
| (any(.candidates[]; (.downstream_count // 0) > 0)) | ||
| ' /tmp/discover.json | ||
| URN="$(jq -r '.candidates | max_by(.downstream_count) | .urn' /tmp/discover.json)" | ||
| test -n "$URN" | ||
| test "$URN" != "null" | ||
| echo "Selected live dataset: $URN" | ||
| jq -n --arg entity "$URN" '{action:"modify_asset", entity:$entity}' >/tmp/analyze-request.json | ||
|
|
||
| TARGET_URN="urn:li:dataset:(urn:li:dataPlatform:snowflake,b2fd91.order_entry_db.order_entry.order_items,PROD)" | ||
| for attempt in $(seq 1 36); do | ||
| status="$(curl -sS --get --max-time 120 \ | ||
| -o /tmp/target-discover.json -w '%{http_code}' \ | ||
| "$IGRAPH_URL/v1/discover" --data-urlencode "query=order_items" || true)" | ||
| echo "Target discovery attempt $attempt returned HTTP $status" | ||
| if [ "$status" = "200" ] && jq -e --arg target "$TARGET_URN" ' | ||
| .live == true and | ||
| any(.candidates[]; .urn == $target and (.downstream_count // 0) > 0) | ||
| ' /tmp/target-discover.json >/dev/null 2>&1; then | ||
| break | ||
| fi | ||
| if [ "$attempt" = "36" ]; then | ||
| echo "Target order_items dataset did not become discoverable with loaded lineage" | ||
| cat /tmp/target-discover.json || true | ||
| exit 1 | ||
| fi | ||
| sleep 5 | ||
| done | ||
| cat /tmp/target-discover.json | ||
| jq -e --arg target "$TARGET_URN" ' | ||
| .live == true and | ||
| any(.candidates[]; .urn == $target and (.downstream_count // 0) > 0) | ||
| ' /tmp/target-discover.json | ||
| echo "Selected deterministic live dataset: $TARGET_URN" | ||
| jq -n --arg entity "$TARGET_URN" '{action:"modify_asset", entity:$entity}' >/tmp/analyze-request.json | ||
|
|
||
| - name: Prove Pact compilation and verification | ||
| run: | | ||
|
|
@@ -191,6 +213,88 @@ jobs: | |
| --data-binary @/tmp/pact.json | tee /tmp/verify.json | ||
| jq -e '.verified == true and (all(.validations[]; .status == "pass"))' /tmp/verify.json | ||
|
|
||
| - name: Prove authorized staging execution and replay protection | ||
| run: | | ||
| refresh_pact() { | ||
| curl -fsS --max-time 300 -X POST "$IGRAPH_URL/v1/analyze" \ | ||
| -H 'content-type: application/json' \ | ||
| --data-binary @/tmp/analyze-request.json | tee /tmp/analyze.json | ||
| jq -e ' | ||
| (.pact.context.live == true) and | ||
| (.pact.context.retrieval_complete == true) and | ||
| ((.pact.context.downstream | length) > 0) and | ||
| ((.pact.signature | length) >= 32) and | ||
| ((.pact.context_hash | length) > 0) | ||
| ' /tmp/analyze.json | ||
| jq '.pact' /tmp/analyze.json >/tmp/pact.json | ||
| curl -fsS --max-time 300 -X POST "$IGRAPH_URL/v1/verify" \ | ||
| -H 'content-type: application/json' \ | ||
| --data-binary @/tmp/pact.json | tee /tmp/verify.json | ||
| jq -e '.verified == true and (all(.validations[]; .status == "pass"))' /tmp/verify.json | ||
| } | ||
|
|
||
| for attempt in $(seq 1 5); do | ||
| echo "Authorized staging attempt $attempt" | ||
| ARTIFACT_SHA="$(jq -r '.execution_scope.deploy_staging.artifact_hashes[0]' /tmp/pact.json)" | ||
| test -n "$ARTIFACT_SHA" | ||
| test "$ARTIFACT_SHA" != "null" | ||
| jq -n --slurpfile pact /tmp/pact.json --arg artifact "$ARTIFACT_SHA" \ | ||
| '{pact:$pact[0], action:"deploy_staging", parameters:{target:"staging", artifact_sha256:$artifact}, human_approved:true}' \ | ||
| >/tmp/execute-request.json | ||
|
|
||
| status="$(curl -sS --max-time 120 -o /tmp/execute-response.json -w '%{http_code}' \ | ||
| -X POST "$IGRAPH_URL/v1/actions/execute" \ | ||
| -H 'content-type: application/json' \ | ||
| --data-binary @/tmp/execute-request.json || true)" | ||
| echo "Authorized staging attempt $attempt returned HTTP $status" | ||
| cat /tmp/execute-response.json | ||
|
|
||
| if [ "$status" = "200" ] && jq -e ' | ||
| .event.status == "executed" and | ||
| .event.executor_invoked == true and | ||
| .receipt.status == "executed" and | ||
| (.event.evidence_sha256 | type == "string") and | ||
| (.event.evidence_sha256 | length == 64) | ||
| ' /tmp/execute-response.json >/dev/null 2>&1; then | ||
| break | ||
| fi | ||
|
|
||
| if [ "$attempt" = "5" ]; then | ||
| echo "Authorized staging execution did not stabilize after five attempts" | ||
| exit 1 | ||
| fi | ||
|
|
||
| if jq -e ' | ||
| .event.status == "pact_stale" and | ||
| (.event.detail | test("Denied before executor invocation"; "i")) | ||
| ' /tmp/execute-response.json >/dev/null 2>&1; then | ||
| echo "Live context drifted before execution; refreshing the Pact" | ||
| refresh_pact | ||
| continue | ||
| fi | ||
|
|
||
| echo "Authorized staging execution failed for a non-drift reason" | ||
| exit 1 | ||
| done | ||
|
|
||
| jq -e ' | ||
| .event.status == "executed" and | ||
| .event.executor_invoked == true and | ||
| .receipt.status == "executed" and | ||
| (.event.evidence_sha256 | type == "string") and | ||
| (.event.evidence_sha256 | length == 64) | ||
| ' /tmp/execute-response.json | ||
|
|
||
| curl -fsS --max-time 120 -X POST "$IGRAPH_URL/v1/actions/execute" \ | ||
| -H 'content-type: application/json' \ | ||
| --data-binary @/tmp/execute-request.json | tee /tmp/replay-response.json | ||
| jq -e ' | ||
| .event.status == "denied" and | ||
| .event.executor_invoked == false and | ||
| .receipt.status == "blocked" and | ||
| (.event.detail | test("Replay|duplicate|invocation limit"; "i")) | ||
| ' /tmp/replay-response.json | ||
|
|
||
| - name: Prove signature tampering is blocked before execution | ||
| run: | | ||
| jq '.allowed_actions += ["deploy_production"]' /tmp/pact.json >/tmp/tampered-pact.json | ||
|
|
@@ -244,7 +348,7 @@ jobs: | |
| if: always() | ||
| run: | | ||
| mkdir -p /tmp/igraph-live-proof | ||
| for file in /tmp/health.json /tmp/datahub-health.txt /tmp/discover.json /tmp/analyze-request.json /tmp/analyze.json /tmp/verify.json /tmp/pact.json /tmp/tampered-pact.json /tmp/tampered-request.json /tmp/tampered-response.json /tmp/expired-pact.json /tmp/expired-request.json /tmp/expired-response.json /tmp/unavailable-health.json /tmp/unavailable-response.json /tmp/igraph.log /tmp/igraph-unavailable.log; do | ||
| for file in /tmp/health.json /tmp/datahub-health.txt /tmp/discover.json /tmp/analyze-request.json /tmp/analyze.json /tmp/verify.json /tmp/pact.json /tmp/execute-request.json /tmp/execute-response.json /tmp/replay-response.json /tmp/tampered-pact.json /tmp/tampered-request.json /tmp/tampered-response.json /tmp/expired-pact.json /tmp/expired-request.json /tmp/expired-response.json /tmp/unavailable-health.json /tmp/unavailable-response.json /tmp/igraph.log /tmp/igraph-unavailable.log; do | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new deterministic discovery request writes its evidence to Useful? React with 👍 / 👎. |
||
| if [ -f "$file" ]; then | ||
| cp "$file" /tmp/igraph-live-proof/ | ||
| fi | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,32 +1,42 @@ | ||
| # iGraph live proof | ||
|
|
||
| This page records the public GitHub Actions proof for the merged iGraph code. | ||
| This page records the public GitHub Actions proof for the iGraph control plane. | ||
|
|
||
| ## Merged code | ||
| ## Code and checks | ||
|
|
||
| - Repository: https://github.com/etvjay/iGraph | ||
| - Main commit: `0136cef2fa556d79f7565999f77cfc92c0a66d42` | ||
| - Pull request: https://github.com/etvjay/iGraph/pull/2 | ||
| - Post-merge CI: https://github.com/etvjay/iGraph/actions/runs/31618368233 | ||
| - Main branch: https://github.com/etvjay/iGraph/tree/main | ||
| - Proof change: https://github.com/etvjay/iGraph/pull/4 | ||
| - Ordinary CI for the proof branch: https://github.com/etvjay/iGraph/actions/runs/31749044332 | ||
|
|
||
| ## Recorded live results | ||
|
|
||
| The live-proof workflow ran against DataHub OSS and produced these results: | ||
| Live proof run #16 ran against DataHub OSS and produced these results: | ||
|
|
||
| - `/health`: `context_mode=live`, `datahub_configured=true`, write-back disabled. | ||
| - `/v1/discover`: 10 live candidates; the top candidate is `addresses` with | ||
| 22 downstream assets and risk score 55. | ||
| - `/v1/analyze`: live `addresses` context with 28 downstream assets, complete | ||
| retrieval, high risk 55/100, and signed Pact `igp_ce855182f9dc`. | ||
| - `/v1/discover`: live wildcard discovery returned DataHub showcase assets, and the deterministic | ||
| `order_items` target was found with loaded lineage. | ||
| - `/v1/analyze`: signed Pact `igp_eb762f75370d` with context fingerprint | ||
| `db707cbee0416b60`, complete live retrieval, and staging scope requiring human approval. | ||
| The compiled context contained 37 downstream assets, 3 dashboards, and high risk 55/100. | ||
| - `/v1/verify`: live post-read verification returned `verified=true` with complete | ||
| retrieval. This was a re-read proof; no real DataHub mutation was attempted. | ||
| - Authorized staging action: with explicit approval, the reversible staging simulator | ||
| returned `executed`, `executor_invoked=true`, and a receipt with evidence hash | ||
| `14d0b81de9e6850255958da682bab0d0dc4f1534036752634ca07c6be0b21396`. | ||
| - Same-request replay: returned `denied` with `executor_invoked=false` because the | ||
| Pact invocation limit had already been consumed. | ||
| - Tampered Pact: `deny`, `executor_invoked=false`. | ||
| - Expired Pact: `pact_stale`, `executor_invoked=false`. | ||
| - Missing live context: `context_unavailable` / HTTP 503, with no fixture fallback. | ||
|
|
||
| Download the complete workflow artifact from the successful proof run: | ||
| https://github.com/etvjay/iGraph/actions/runs/31479477960/artifacts/9096775884 | ||
| Download the complete proof artifact from run #16: | ||
| https://github.com/etvjay/iGraph/actions/runs/31749044334/artifacts/9200418599 | ||
|
|
||
| ## Claim boundary | ||
|
|
||
| This proves live reads, discovery, Pact signing, enforcement denial, expiry | ||
| rejection, and fail-closed behavior. It does not prove production deployment, | ||
| real database mutation, DataHub write-back, or a deployed public video. | ||
| This proves live reads, discovery, Pact signing, live post-read verification, | ||
| authorized reversible staging execution, one-shot replay protection, enforcement | ||
| denial, expiry rejection, and fail-closed behavior. It does not prove a real | ||
| database mutation, DataHub write-back, production deployment, or a deployed public | ||
| video. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On automatic
pull_requestandpushruns, the workflow unconditionally setshuman_approved:true; there is no protected environment, reviewer gate, or approval-derived input anywhere in this workflow. The run therefore proves only that a caller-controlled Boolean unlocks the approval branch, not the documented claim that an explicitly human-approved action executed. Source this value from a human-controlled gate or qualify the proof as simulated approval.Useful? React with 👍 / 👎.