diff --git a/.github/workflows/igraph-live-proof.yml b/.github/workflows/igraph-live-proof.yml index 9fc61a2..30fc279 100644 --- a/.github/workflows/igraph-live-proof.yml +++ b/.github/workflows/igraph-live-proof.yml @@ -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 if [ -f "$file" ]; then cp "$file" /tmp/igraph-live-proof/ fi diff --git a/docs/DEVPOST_SUBMISSION.md b/docs/DEVPOST_SUBMISSION.md index 9942be4..ca01ed6 100644 --- a/docs/DEVPOST_SUBMISSION.md +++ b/docs/DEVPOST_SUBMISSION.md @@ -16,18 +16,23 @@ validation states, and optional DataHub custom-property/Document write-back. ## What the recorded proof demonstrates -1. Live DataHub context is available and discovery returns 10 candidates. -2. The `addresses` context binds 28 downstream assets into a signed Impact Pact - with a high consequence score of 55/100. -3. A tampered Pact is denied before executor invocation. -4. An expired Pact is rejected as `pact_stale` before executor invocation. -5. Unavailable live context fails closed as `context_unavailable` / HTTP 503; +1. Live DataHub context is available and discovery selects a real showcase asset. +2. The selected `order_items` context contains 37 downstream assets and 3 dashboards, + producing a high-risk 55/100 signed Pact. +3. An explicitly approved `deploy_staging` action reaches only the reversible + staging simulator and returns an executed receipt with evidence. +4. Repeating the same signed request is denied before a second executor invocation. +5. A tampered Pact is denied before executor invocation. +6. An expired Pact is rejected as `pact_stale` before executor invocation. +7. Unavailable live context fails closed as `context_unavailable` / HTTP 503; iGraph does not substitute fixture metadata. +8. Live post-read verification returns complete live context; it does not claim that + a real DataHub mutation occurred. The 10-second video is a silent, deterministic vector reconstruction of the -control-room visual language, populated from the recorded proof artifact. It is -labeled as a reconstruction and does not claim a production deployment, real -database mutation, or DataHub write-back. +control-room visual language, populated from recorded proof data. It is labeled as +a reconstruction and does not claim a production deployment, real database +mutation, or DataHub write-back. ## Built with @@ -39,8 +44,8 @@ database mutation, or DataHub write-back. ## Links to include - Public source repository: `https://github.com/etvjay/iGraph` -- Live proof run: `https://github.com/etvjay/iGraph/actions/runs/31479477960` -- Live proof artifact: `https://github.com/etvjay/iGraph/actions/runs/31479477960/artifacts/9096775884` +- Live proof run: `https://github.com/etvjay/iGraph/actions/runs/31749044334` +- Live proof artifact: `https://github.com/etvjay/iGraph/actions/runs/31749044334/artifacts/9200418599` - Reproducible request: [`examples/change-request.json`](../examples/change-request.json) - Video asset: `igraph-demo-candidate.mp4` from the accompanying demo package @@ -48,4 +53,4 @@ database mutation, or DataHub write-back. The built-in executor is a reversible staging simulator. Production deployment, real database mutation, and DataHub write-back remain outside this proof. The -live proof keeps `IGRAPH_ENABLE_WRITEBACK=false`. \ No newline at end of file +live proof keeps `IGRAPH_ENABLE_WRITEBACK=false`. diff --git a/docs/LIVE_PROOF.md b/docs/LIVE_PROOF.md index d62835a..82ae253 100644 --- a/docs/LIVE_PROOF.md +++ b/docs/LIVE_PROOF.md @@ -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. \ No newline at end of file +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.