Repository navigation
feat: ledger integrity and language-independent gating - #2
Merged
Merged
Conversation
- replace re.MULTILINE with strict string start anchor \A in transcript polling - flag pipe operators (| cat, | tee) as tainted in verification commands - implement manifest tampering defense in Tier 2 runner against Makefile/package.json poisoning - add tmpfs mounts and cache env overrides for Docker read-only runner - enforce CWD awareness and strict path encompassment in suite resolution - expand test suite with 5 new adversarial regression tests (39/39 passing)
…baseline immutability
…filter flags, Tier 2 fail-closed, API DoS limits)
… PATH spoofing, conftest injection, flag-only pytest)
…ger writes, Tier 2 workspace visibility)
Round 7 Finding A (unauthenticated daemon writes):
- /v1/ledger/record and other state-changing endpoints allowed any local process
to forge tool=run_command/exit-0 records and clear genuine failures from the
gate premise. Now require a shared bearer API token (HARDTRUTH_API_TOKEN env or
~/.hardtruth/daemon_api.key); missing/invalid token -> 401.
- Hook sends the token on all requests and warns on 401 mismatch.
Round 7 Finding B (Tier 2 workspace visibility):
- containerized daemons could not see host workspace paths, so /v1/verify/handoff
always failed ('Workspace path does not exist'), forcing HARDTRUTH_SKIP_TIER2=1
which removed the only containment for Finding A.
- Add resolve_workspace_path() (HARDTRUTH_WORKSPACE_MAP), fail closed with
status=unverified_no_workspace when the workspace is invisible, and wire
workspace mounts + token + pytest/git into the daemon image/compose.
- Dockerfile now copies the full daemon package (was app.py only).
8 new regression tests (63 total, all passing); live-verified against the
rebuilt daemon container.
…eload+warmup, test ledger isolation + purge tooling) - #3 GET /v1/ledger/premise + /v1/session/baseline now require the shared API token (health stays open). Live-verified 401/200. - #5 Docker image preloads cross-encoder/nli-deberta-v3-small at build time and daemon warms it at startup: first /v1/verify-claim 159ms vs ~8.3s cold. - #6 test suites (live, hybrid, inverted_gate) run hook subprocesses + in-process helpers token-less so test runs never write into the physical daemon ledger (full suite now adds exactly +1 record, was +36). Added ops-only purge CLI (scripts/purge_test_records.py) with dry-run; rebuilds+re-verifies HMAC chain, refuses tampered ledgers. - Tests: 68 passed, 0 skipped (was 63).
… across unreachable, host, and containerized daemon
…y and monotonic step sequencing
… battery artifacts
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.
HardTruth Design Note: Ledger Integrity & Language-Independent Gate
Branch:
feat/ledger-integrityPR: #2
Status: PROPOSED (Incorporating Opus Auditor Amendments)
Date: 2026-09-19
1. Executive Summary & Architectural Shift
The initial version of HardTruth introduced a basic NLI cross-encoder gate and an append-only JSONL ledger. However, an external architectural review revealed three fatal weaknesses:
"SUCCEEDED (exit 0)"from the mere absence of an error string, writes to a user-writable JSONL file, and uses a 6-command sliding window that allows 6echocommands to age out a failingpytest."10/10 green across the suite","zero failures across every module") bypass the gate completely without ever touching the model.contradiction >= 0.70was chosen arbitrarily without an evaluation set or error curves on out-of-domain CLI execution premises.This design note outlines the complete architectural overhaul to make the ledger cryptographically tamper-evident, daemon-owned, and failure-biased, while inverting the gate to enforce physical ledger proof language-independently.
flowchart TD subgraph Agent Runtime A[Agent emits Tool Call] --> B[Harness executes tool] B --> C[PostToolUse Lifecycle Hook] end subgraph HardTruth Client Hook C --> D[Extract exit code from transcript / error with polling] C --> E[Extract git diff stat for source code edits] D & E --> F[POST /v1/ledger/record] end subgraph HardTruth Daemon Port 8000 F --> G[HMAC-SHA256 Hash Chain] G --> H[(Daemon-Owned Ledger: 0600)] H --> I[Verification Command State Engine] end subgraph Stop Hook Interception J[Agent attempts Stop] --> K[Inverted Ledger Gate] K -->|Source code modified & 0 tests run| L[HALT: No tests run] K -->|Unresolved test failures exist| M[HALT: Unresolved test failures] K -->|AST stubs present in code| N[HALT: Unimplemented stub] K -->|Ledger clean & verified| O[Adjudicate Claims via DeBERTa-v3] O -->|Contradiction >= tau*| P[HALT: Contradiction] O -->|Entailed / Clean / Meta-discussion| Q[ALLOW Termination] end2. Problem 1: The Ledger is Not Evidence
1.1 Harness Payload Investigation & Ground Truth Observation
We inspected
/Users/ai/.gemini/antigravity-cli/builtin/skills/agy-customizations/docs/hooks.mdand live execution transcripts.What the Harness Exposes
PostToolUsestdin payload:{ "stepIdx": 5, "error": "exit status 1", "toolCall": { "name": "run_command", "args": { "CommandLine": "pytest tests/" } }, "conversationId": "...", "workspacePaths": ["/path/to/workspace"], "transcriptPath": "/path/to/workspace/.gemini/antigravity-cli/transcript.jsonl" }payload["error"]contains a string such as"exit status 1".payload.get("error")isNoneor omitted. The harness payload does not include an explicit integer field"exitCode": 0.GENERICstep totranscriptPathfor every tool execution:{ "step_index": 5, "type": "GENERIC", "source": "MODEL", "content": "Created At: ... The command exited with code 0. Output: \n10 passed in 0.42s" }PostToolUsefires, the hook will polltranscriptPath(up to 300ms, 50ms intervals) for the matchingstepIdx.observed_exit_code: integer extracted via regexr"The command exited with code (\d+)".stdout_stderr_tail: truncated last 15 lines (up to 1,000 characters).Explicit Non-Synthesis Rule
If
transcriptPathis not readable or the step is not visible after polling:payload.get("error")matchesr"exit status (\d+)", we extract that integer.payload.get("error")isNone, we recordobserved_exit_code: nullandharness_status: "no_error"."exit 0"out of thin air. The premise string will explicitly state:COMMAND: '...'. HARNESS_STATUS: no_error (raw exit code uncorroborated by transcript).Git Diff Stat for File Edits
For tools
write_to_fileandreplace_file_content:git diff --stat -- <TargetFile>in the target file's repository.+45 lines).1.2 Ledger Ownership Shift to Daemon
The previous implementation allowed any agent with bash access to append fake entries to
~/.gemini/antigravity-cli/ledger.jsonl.New Architecture
~/.hardtruth/daemon_ledger.jsonl(file mode0o600, directory mode0o700). In Docker, it resides inside an isolated container volume.POST /v1/ledger/record:conversationId,stepIdx,tool,target,observed_exit_code,harness_status,error,stdout_tail,diff_stat.index, computes HMAC-SHA256, appends to the internal ledger.GET /v1/ledger/premise?conversationId=...:1.3 Cryptographic Hash-Chaining & Threat Model
Hash-Chain Formula
The daemon maintains a 256-bit secret key
~/.hardtruth/daemon_hmac.key(mode0o400), generated securely at daemon startup if missing.Each ledger record contains:
index: Monotonically increasing sequence integer (prev_hash: HMAC of predecessor (Genesis block uses"0" * 64)entry: Canonical JSON of execution datahash:Read-Time Verification
On every call to
GET /v1/ledger/premise:entry[i]["prev_hash"] == entry[i-1]["hash"].{"error": "LEDGER_TAMPER_DETECTED", "broken_at_index": i}. The Stop hook immediately issuesdecision: "continue"with a tamper alert.Honest Threat Model & Physical Limits (Demoting Security Theater)
The README will explicitly document the exact threat boundary:
echo,ls) to push failures out of sight.1.4 Failure-Biased Premise Window & Verification Command Heuristic
Avoiding the
grepDeath Trap (Test vs Exploratory Commands)In standard POSIX tooling, non-zero exits are frequently informative, not failures (e.g.
grep -q "foo" bar.pyexiting 1 when no matches exist, orls non_existent_direxiting 1).Only commands classified as Verification Commands (test runners, linters, type checkers, and compilers) enter
unresolved_failureswhen they exit non-zero:r"^(pytest|python3? -m (unittest|pytest)|npm test|yarn test|bun test|cargo test|make test|go test|rspec|jest|vitest|tox|ctest|ruff|mypy|flake8|eslint)\b"cat,ls,grep,find,echo,cd,pwd,curl) exiting non-zero are recorded in the general execution history, but NEVER enterunresolved_failures.State Engine & Resolution Rules
Within a given
conversationId:observed_exit_code != 0orerror is not Noneis added tounresolved_failures.pytest) exits withobserved_exit_code == 0anderror is None.3. Problem 2: Language-Independent Gating (The Inversion Principle)
2.1 The Flaw of English Regex Whitelists
The existing hook relies on
action_triggers:r"\b(i\s+(have\s+)?(ran|run|executed|tested|verified|fixed|modified|created)|...)\b"This fails catastrophically:
"10/10 green across the suite, everything is solid now."-> allow (missed)"The full suite came back clean, zero failures across every module."-> allow (missed)"Everything has been validated end to end and the suite is green."-> allow (missed)"Tous les tests unitaires sont passés avec succès."-> allow (missed)"所有测试均已通过。"-> allow (missed)2.2 The Inverted Gate Policy
We invert the policy: Gate on physical ledger state first; use NLI for claim adjudication.
flowchart TD Start[Stop Hook Triggered] --> CheckFiles{Were executable source files modified?} CheckFiles -->|Yes| CheckTests{Did agent run any verification commands?} CheckFiles -->|No| CheckReadOnlyClaims[Scan for unverified assertions] CheckTests -->|No| HaltNoTests[HALT: Source code modified but 0 tests executed] CheckTests -->|Yes| CheckFailures{Any unresolved verification failures in ledger?} CheckFailures -->|Yes| HaltFailures[HALT: Unresolved test failures remain] CheckFailures -->|No| CheckAST{AST stub linter clean?} CheckAST -->|No| HaltStubs[HALT: Unimplemented code stub detected] CheckAST -->|Yes| NLICheck{Claims contradict premise?} CheckReadOnlyClaims --> NLICheck NLICheck -->|Yes: conf >= tau*| HaltNLI[HALT: Claim contradicts ledger] NLICheck -->|No / Clean| Allow[ALLOW: Termination Approved]source_files_modified(excluding non-executable documentation such as.md,.txt,.csv, images, and pure metadata).source_files_modified > 0andverification_commands_executed == 0:"🚨 HARDTRUTH GATE HALTED: Source code files were modified in this conversation, but NO verification commands or test suites were executed. You must execute tests to verify your changes before stopping.".md,.txt) were modified: Rule 1 does NOT halt.unresolved_failures > 0:"🚨 HARDTRUTH GATE HALTED: Unresolved test failures exist in the ledger: [list of failing commands & tails]. Fix the failures and re-run tests before stopping."pass,NotImplementedError, dummy returns).4. Problem 3: Threshold Calibration & Evaluation
4.1 The Dataset:
evals/dataset.jsonl(3 Distinct Categories)To prevent the cross-encoder from false-positive tripping on meta-discussions, hypothetical explanations, and quoted guidelines, the evaluation set will contain 260 labelled pairs across 3 categories:
"All 10 tests passed","10/10 green","suite came back clean","zero regressions")."Todos los tests pasaron exitosamente"), French ("Tous les tests unitaires ont réussi"), German ("Alle Tests erfolgreich bestanden"), Chinese ("所有单元测试均已通过").pass."The goal is that all 10 tests pass.","If tests passed, continue.")."The test suite failed with 2 errors in auth.py.").NEUTRALorENTAILEDto ensure threshold calibration does not flag conversational or diagnostic text as contradiction.4.2 Evaluation Harness:
evals/evaluate.pyThe evaluation script will:
evals/dataset.jsonl.(premise, hypothesis)token pairs directly through DeBERTa-v3 cross-encoder.4.3 Replacing Silent 3-Strike Release (Never Fail Open)
Silently releasing an agent after 3 strikes allows an agent under RL pressure that repeats an unverified assertion 3 times to escape unverified.
🚨 HardTruth Gate Halt Limit Reached. The agent repeatedly made claims that contradict the ledger. Do you want to force release? [y/N]exit status 1), permanently failing the build.5. Implementation Roadmap (Post-Approval)
Upon review and approval:
evals/dataset.jsonl(260+ labelled pairs across the 3 categories) andevals/evaluate.py.daemon/):POST /v1/ledger/recordwith HMAC-SHA256 hash chaining.GET /v1/ledger/premisewith chain integrity validation and failure-biased verification command resolution logic.~/.hardtruth/daemon_ledger.jsonl(0o600) and key in~/.hardtruth/daemon_hmac.key(0o400).client/hardtruth_hook.py):handle_post_tool_useto poll transcript, extract real exit codes and diff stats, and POST to daemon.handle_stopto enforce Rule 1 (source code modified requires tests, doc-only excluded) and Rule 2 (unresolved verification failures forbid completion) language-independently.