Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .github/workflows/research-integrity.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Engraph research integrity

on:
pull_request:
branches:
- feat/engraph-p3-perf
paths:
- models/**
- research-evidence/**
Comment on lines +7 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include README.md in the claim-surface path filters

The claim-durability verifier binds live claim surfaces from README.md and scans the whole README for unregistered high-risk claims (scripts/verify_claim_durability.py:481-513), but README.md is not in either paths list. A PR or push on feat/engraph-p3-perf that changes only README claims will skip this workflow, leaving the live excerpt hashes and high-risk-claim guard unenforced in the path-scoped gate; include README.md in both filters.

Useful? React with 👍 / 👎.

- scripts/research_receipt.py
- scripts/test_research_receipt.py
- scripts/test_verify_*.py
- scripts/verify_*.py
- examples/p3_actor_parity.rs
- src/llm.rs
- src/serve.rs
- Cargo.lock
- target/release/examples/p3_actor_parity
- .github/workflows/research-integrity.yml
push:
branches:
- feat/engraph-p3-perf
paths:
- models/**
- research-evidence/**
- scripts/research_receipt.py
- scripts/test_research_receipt.py
- scripts/test_verify_*.py
- scripts/verify_*.py
- examples/p3_actor_parity.rs
- src/llm.rs
- src/serve.rs
- Cargo.lock
- target/release/examples/p3_actor_parity
- .github/workflows/research-integrity.yml

permissions:
contents: read

concurrency:
group: engraph-research-integrity-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
verify:
runs-on: ubuntu-latest
timeout-minutes: 10
env:
PYTHONDONTWRITEBYTECODE: "1"
PYTHONPATH: scripts
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.12"
- name: Verify shared manifest contract
run: python3 research-evidence/contracts/v2/verify_contract.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Install the JSON Schema CLI before the contract gate

The contract verifier this step invokes calls shutil.which("jsonschema") and raises reference JSON Schema validator unavailable when that executable is missing (research-evidence/contracts/v2/verify_contract.py:88), but the workflow only checks out the repo and runs actions/setup-python before this command. On a clean ubuntu-latest run, setup-python supplies the interpreter but does not install this CLI, so the first gate exits 1 before any integrity checks run; add an explicit validator install or use an in-repo/Python validator before invoking it.

Useful? React with 👍 / 👎.

- name: Verify model provenance
run: python3 scripts/verify_model_provenance.py
- name: Verify claim durability
run: python3 scripts/verify_claim_durability.py
- name: Verify authentic P3 correctness receipt
run: python3 scripts/research_receipt.py verify-record research-evidence/runs/p3-actor-parity-v2-20260717-a2.receipt.json
- name: Audit the authentic P3 attempt
run: python3 scripts/research_receipt.py audit-plans research-evidence/runs --attempt-id 076b69d2-3a1f-40d6-9555-9c7cc6c69302 --record-only
- name: Run integrity tests
run: python3 -m unittest discover -s scripts -p "test_*.py" -q

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make receipt tests platform-independent on Ubuntu

These tests include test_command_evidence_rejects_secrets_without_false_tokenizer_match, which asserts that /Users/d/private/file redacts as $HOME/private/file; on ubuntu-latest, HOME is not /Users/d, so _safe_command_argv returns $ABS_PATH_SHA256/... instead. Once earlier gates are fixed, this step fails on the hosted Ubuntu runner unless the test is made platform-independent or the workflow uses a runner/environment matching that hard-coded home path.

Useful? React with 👍 / 👎.

- name: Run integrity tests without assertions
run: python3 -O -m unittest discover -s scripts -p "test_*.py" -q
16 changes: 16 additions & 0 deletions research-evidence/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,22 @@ complete fail-closed envelope and globally unique event identity. Use
`--attempt-id <id>` to evaluate one new attempt without allowing an unrelated
historical failure to become either its pass or its failure.

Clean clones and CI can validate the committed receipt envelope and repository
artifacts without claiming custody of unavailable model or tokenizer bytes:

```sh
python3 scripts/research_receipt.py verify-record \
research-evidence/runs/p3-actor-parity-v2-20260717-a2.receipt.json
python3 scripts/research_receipt.py audit-plans research-evidence/runs \
--attempt-id 076b69d2-3a1f-40d6-9555-9c7cc6c69302 \
--record-only
```

Record-only verification filters only explicitly classified unavailable
external artifacts. Malformed, changed, unsafe, or hash-mismatched evidence
still fails. The strict `verify` and default `audit-plans` commands remain the
custody checks and must not be replaced by record-only results.

Receipts contain a canonical self-digest for accidental corruption detection.
The digest is stored beside editable metadata and is not authentication. They
are not signed, externally timestamped, or stored on immutable media, so
Expand Down
30 changes: 29 additions & 1 deletion scripts/research_receipt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,15 @@ def verify_receipt(path: Path) -> list[str]:
return errors


def verify_receipt_record(path: Path) -> list[str]:
"""Verify a committed receipt without claiming external-byte custody."""
return [
error
for error in verify_receipt(path)
if error not in TERMINAL_UNAVAILABLE_ERRORS
]


def _write_exclusive_json(path: Path, value: dict[str, Any]) -> None:
path = path.resolve()
path.parent.mkdir(parents=True, exist_ok=True)
Expand Down Expand Up @@ -1586,6 +1595,7 @@ def incomplete_attempts(
directory: Path,
*,
attempt_id_filter: str | None = None,
record_only: bool = False,
) -> list[dict[str, Any]]:
resolved = directory.resolve()
try:
Expand Down Expand Up @@ -1619,7 +1629,12 @@ def incomplete_attempts(
errors.append("planned event unexpectedly has a parent")
planned.setdefault(attempt_id, []).append((path, errors))
elif value.get("record_type") == "attempt_result":
terminal.setdefault(attempt_id, []).append((path, verify_receipt(path)))
receipt_errors = (
verify_receipt_record(path)
if record_only
else verify_receipt(path)
)
terminal.setdefault(attempt_id, []).append((path, receipt_errors))
terminal_plan_required[attempt_id] = (
terminal_plan_required.get(attempt_id, False)
or value.get("execution_provenance") == "tool_executed"
Expand Down Expand Up @@ -1801,9 +1816,16 @@ def common(run_parser: argparse.ArgumentParser) -> None:
run.add_argument("measured_command", nargs=argparse.REMAINDER)
verify = sub.add_parser("verify")
verify.add_argument("receipt", type=Path)
verify_record = sub.add_parser("verify-record")
verify_record.add_argument("receipt", type=Path)
audit = sub.add_parser("audit-plans")
audit.add_argument("directory", type=Path)
audit.add_argument("--attempt-id")
audit.add_argument(
"--record-only",
action="store_true",
help="do not claim custody of unavailable external artifacts",
)
args = parser.parse_args()
if args.action == "create":
print(json.dumps(create_receipt(args), sort_keys=True))
Expand All @@ -1814,10 +1836,16 @@ def common(run_parser: argparse.ArgumentParser) -> None:
if errors:
raise SystemExit("\n".join(errors))
print("OK")
elif args.action == "verify-record":
errors = verify_receipt_record(args.receipt)
if errors:
raise SystemExit("\n".join(errors))
print("OK: receipt record verified; external byte custody not claimed")
else:
issues = incomplete_attempts(
args.directory,
attempt_id_filter=args.attempt_id,
record_only=args.record_only,
)
print(json.dumps(issues, indent=2, sort_keys=True))
if issues:
Expand Down
19 changes: 18 additions & 1 deletion scripts/test_research_receipt.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@


class ReceiptTests(unittest.TestCase):
def test_committed_a2_record_is_clone_verifiable(self) -> None:
receipt = (
rr.ROOT
/ "research-evidence/runs/"
"p3-actor-parity-v2-20260717-a2.receipt.json"
)
self.assertEqual(rr.verify_receipt_record(receipt), [])
self.assertEqual(
rr.incomplete_attempts(
rr.ROOT / "research-evidence/runs",
attempt_id_filter="076b69d2-3a1f-40d6-9555-9c7cc6c69302",
record_only=True,
),
[],
)

def test_v2_verifier_rejects_rights_and_artifact_overclaims(self) -> None:
representatives = json.loads(
(
Expand Down Expand Up @@ -101,8 +117,9 @@ def test_command_evidence_rejects_secrets_without_false_tokenizer_match(
rr._safe_command_argv(["tool", "--tokenizer", "embedded"]),
["tool", "--tokenizer", "embedded"],
)
home_input = Path.home() / "private" / "file"
self.assertEqual(
rr._safe_command_argv(["tool", "--input=/Users/d/private/file"])[1],
rr._safe_command_argv(["tool", f"--input={home_input}"])[1],
"--input=$HOME/private/file",
)
with self.assertRaisesRegex(ValueError, "secret-bearing"):
Expand Down