Skip to content

feat(core): fingerprint assembled system prompt with SHA-256 (#110)#129

Open
web-dev0521 wants to merge 5 commits into
GeniePod:mainfrom
web-dev0521:feat/issue-110-system-prompt-sha
Open

feat(core): fingerprint assembled system prompt with SHA-256 (#110)#129
web-dev0521 wants to merge 5 commits into
GeniePod:mainfrom
web-dev0521:feat/issue-110-system-prompt-sha

Conversation

@web-dev0521
Copy link
Copy Markdown
Contributor

Summary

Adds a real SHA-256 fingerprint of the fully-assembled system prompt so the M1 exit criterion "system prompt SHA is identical across full-stack restart" is provable, and silent prompt drift between runs becomes a visible hash mismatch instead of an undetected behavior change. Closes #110.

Changes

  • New pure-Rust prompt_sha module computing a SHA-256 hex digest. No crypto dependency (honors the existing runtime_contract no-crypto policy); the digest is an operational determinism fingerprint, not a security primitive.
    Pinned to the FIPS-180 known-answer vectors so any transcription drift in the constants fails CI.
  • genie-core computes the digest over the boot-assembled prompt (persona + tool defs + hydrated household memory), logs it at boot (system_prompt_sha=…), and serves it as system_prompt_sha on /api/health.
  • genie-ctl status prints it as Prompt: <sha>.
  • New prompt_sha_test integration test: two prompt assemblies from identical config + hydrated state produce an identical SHA; a model-family (assembly) change or a hydration change shifts the SHA.
  • README control-plane list + CHANGELOG updated.

Real Behavior Proof

  • I have built and run the affected code locally (or noted why I could not).
  • I have verified the change end-to-end on Jetson hardware OR explained the equivalent verification path I used.

What I ran

Environment: Windows 11 dev host, no Jetson available and no Rust toolchain installed on this box, so I could not run cargo build/clippy/test locally. The Rust build/clippy/test gates run in CI.

To verify the hand-written SHA-256 is a genuine SHA-256 (not just internally consistent), I computed the same inputs through the OS crypto library and confirmed they equal the vectors hard-coded into the new unit test:

PS> $sha=[System.Security.Cryptography.SHA256]::Create()
PS> # UTF-8 bytes -> lowercase hex
""    -> e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
"abc" -> ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
      -> 248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1

These are the canonical FIPS-180 single- and two-block vectors and match the matches_published_sha256_vectors test exactly.

What I observed

  • Determinism is exercised by prompt_sha_test: it assembles the system prompt twice (separate fresh SQLite memory files, same model + same hydrated identity facts — i.e. two simulated boots) and asserts the SHA is identical, and that
    a model-family change or an added household fact changes the SHA.
  • handle_health unit test now asserts /api/health returns a 64-char system_prompt_sha equal to the SHA of the served prompt.

A maintainer or reviewer with a toolchain can confirm with:

cargo test -p genie-core prompt_sha
cargo test -p genie-core health_endpoint_reports_llm_backend

and on a running stack:

curl -s 127.0.0.1:3000/api/health | jq .system_prompt_sha   # 64-char hex
genie-ctl status                                            # prints "Prompt: <sha>"
# restart the stack with unchanged config + memory -> same value

Test plan

  1. Build the stack and start genie-core.
  2. curl -s 127.0.0.1:3000/api/health | jq .system_prompt_sha — note the digest.
  3. genie-ctl status — confirm the Prompt: line matches.
  4. Restart the full stack with unchanged config and household memory; repeat steps 2–3 and confirm the SHA is byte-identical.
  5. Change the prompt (e.g. switch model family or add a household fact),
    restart, and confirm the SHA changes.

Notes for reviewers

  • SHA-256 is hand-rolled in pure Rust deliberately, to avoid adding a crypto crate (consistent with runtime_contract::stable_hash). It is keyed to the FIPS-180 known-answer vectors so it cannot silently diverge from real SHA-256.
  • This is complementary to the existing runtime_contract.prompt_hash (FNV-1a operational fingerprint); M1 exit: system prompt SHA is identical across full-stack restart #110 specifically calls for a published "system prompt SHA", so a genuine SHA-256 is exposed as a distinct, top-level field.
  • I could not compile locally (no Rust toolchain on the dev host); please treat CI fmt/clippy/test/cross-compile as the authoritative build verification.

…d#110)

Add a real SHA-256 of the fully-assembled system prompt so the M1 exit
criterion 'system prompt SHA is identical across full-stack restart' is
provable and silent prompt drift between runs becomes a visible hash
mismatch instead of an undetected behavior change.

- New pure-Rust prompt_sha module (no crypto dependency, honoring the
  runtime_contract no-crypto policy), pinned to the FIPS-180 known-answer
  vectors so a transcription error fails CI.
- genie-core logs system_prompt_sha at boot and serves it on /api/health.
- genie-ctl status prints it as 'Prompt:'.
- New prompt_sha_test boots prompt assembly twice from identical config +
  hydrated state and asserts an identical SHA, and asserts a prompt-assembly
  or hydration change shifts the digest.
@ai-hpc
Copy link
Copy Markdown
Contributor

ai-hpc commented May 22, 2026

Review note: not merging this in the current state because it is not mergeable against main. A test merge hits a README conflict after the README was simplified around the new agent-layer boundary.

The core idea still looks aligned with M1, but the branch needs to be refreshed against current main and should drop/rework the stale README change. After that full CI needs to run before this can be merged.

@web-dev0521
Copy link
Copy Markdown
Contributor Author

Hi, @ai-hpc ,
I just resolved the conflicts. Please review again, 👍

@ai-hpc
Copy link
Copy Markdown
Contributor

ai-hpc commented May 23, 2026

Reviewed and closed: this PR is now conflicting with current main and is not safe to merge as-is. Please reopen or resubmit a rebased branch if you want to continue it; thanks @web-dev0521.

@ai-hpc ai-hpc closed this May 23, 2026
@ai-hpc ai-hpc reopened this May 23, 2026
Copy link
Copy Markdown
Contributor

@ai-hpc ai-hpc left a comment

Choose a reason for hiding this comment

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

Reviewed and reopened with changes requested: #110 is valid, but this branch is conflicting and only the PR-body check ran, so it is not merge-ready. Please rebase on current main, drop or rework stale README/CHANGELOG conflicts, and provide passing fmt/clippy/test proof; thanks @web-dev0521.

@web-dev0521 web-dev0521 requested a review from ai-hpc May 23, 2026 22:50
…; add missing blank line

The real_server_client_disconnect_cancels_llm_producer test called
ChatServer::new with 9 arguments; the system_prompt_sha parameter added
in this branch's feature commit was never threaded through, causing a
type-mismatch compile error that failed all four CI jobs (fmt, clippy,
test, no-default-features).

Also adds the blank line rustfmt requires between api_probe_addr and
SKILL_RESTART_HINT in genie-ctl/src/main.rs (cargo fmt --check, exit 1).
cargo fmt --all --check broke the single-line assert_eq! because its
argument list exceeds the fn_call_width heuristic; reformat across lines
to match rustfmt output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

M1 exit: system prompt SHA is identical across full-stack restart

2 participants