Skip to content

feat(cabinet): report whether the ProofTrace was persisted, and allow requiring it - #177

Open
sinkog wants to merge 2 commits into
mainfrom
feature/relay-165-proof-persisted
Open

sinkog wants to merge 2 commits into
mainfrom
feature/relay-165-proof-persisted

Conversation

@sinkog

@sinkog sinkog commented Nov 14, 2026

Copy link
Copy Markdown
Member

Refs #165

What

Setx logged a recording failure and returned a normal response with Warnings: nil, so a caller could not tell "execution succeeded" from "the proof was persisted".

  • SetResponse.Proof (persisted, commit, error_code) is returned on every successful execution. The error_code is a fixed value (record_failed, recorder_not_configured); the recorder error text (which may name Vault paths) stays in the log.
  • ExecutionRecorder is unchanged. A new optional CommitRecorder lets Setx return the persisted commit hash; WorkflowRecorder implements it (it used to discard the hash from RecordState).
  • cabinet.WithEvidenceRequired / CIC_EVIDENCE_REQUIRED=true: an execution whose proof was not persisted (recording failed, or no recorder is configured) fails with ErrEvidenceRequired, mapped to HTTP 503 evidence_required. The workflow has already run at that point, so the error carries the chain_hash. CIC_EVIDENCE_REQUIRED accepts only true/false; any other value is a start-up error.
  • Default is unchanged in status: HTTP 200, proof.persisted=false, trace kept.
  • context/SYMBOLS.md regenerated (6 new exported symbols).

Claim check against the issue

All three acceptance points are covered. One addition the issue did not mention: returning the commit hash needed the new CommitRecorder, because ExecutionRecorder.Record returns only an error and WorkflowRecorder dropped the hash.

Tests

With the old behaviour restored under the new types (temporarily), these fail: all seven TestSetx_Proof_* cabinet tests and TestSetHandler_EvidenceRequired.

Characterization only, they do not fail without the change: TestSetHandler_ProofInResponse (serialises the mock's response), TestEvidenceRequiredFromEnv (new function), the RecordCommit unit tests. var _ cabinet.CommitRecorder = (*WorkflowRecorder)(nil) breaks the build if RecordCommit is removed, and TestWorkflowRecorder_RecordCommit_RealGitRecorder_ReturnsHead checks the hash against the real git HEAD.

Local gates (Docker golang:1.26.11): gofmt -s -l empty, go vet ./... clean, staticcheck (cabinet, recorder, cmd/relay) clean, go test -count=1 ./... all ok, no new entry in deadcode ./....

Reachability

cmd/relay/main.go setHandler -> cabinet.NewAPI(...).Set -> Setx -> recordProof -> WorkflowRecorder.RecordCommit -> GitStateRecorder.RecordState. Switch: main.go evidenceRequiredFromEnv -> cabinet.WithEvidenceRequired in the NewCabinetService call.

Not covered / notes

  • main() was not run; the switch wiring is verified by reading and deadcode, not by a test.
  • In required mode the workflow's side effects have already happened before the 503; the 503 does not undo them.
  • Not done: a start-up failure when CIC_EVIDENCE_REQUIRED=true and no recorder is configured (it is a per-call 503 today), and a per-workflow declaration (needs a workflow-format change).
  • Docs: the CIC_EVIDENCE_REQUIRED row is not added to docs/{hu,en}/pipeline/relay-container.md, because fix(relay): require an explicit CIC_TRUST_MODE and guard prod against an unloaded trust store #176 edits the adjacent CIC_TRUST_MODE row; follow-up after both merge.
  • The response mixes key styles (Results/Trace capitalised, new proof lowercase) because SetResponse has no JSON tags; proof follows the issue text.
  • The commit carries the developer's Vault signing metadata, added by a global commit hook. No workflow or CI files are touched.

🤖 Generated with Claude Code

Gábor Z Sinkó added 2 commits November 14, 2026 21:42
… requiring it

Setx logged a recording failure and returned a normal response with
Warnings=nil, so a caller could not tell "execution succeeded" from "the proof
was persisted".

- SetResponse.Proof ({persisted, commit, error_code}) is returned on every
  successful execution; the recorder error text stays in the log, only a fixed
  error code reaches the response
- ExecutionRecorder is unchanged; an optional CommitRecorder lets Setx return the
  persisted commit hash, and WorkflowRecorder implements it
- WithEvidenceRequired / CIC_EVIDENCE_REQUIRED=true: an execution whose proof was
  not persisted (recording failed, or no recorder is configured) fails with
  ErrEvidenceRequired, mapped to HTTP 503 evidence_required; the error carries the
  chain hash because the workflow has already run
- default stays non-fatal: HTTP 200 with proof.persisted=false and the trace kept
- CIC_EVIDENCE_REQUIRED accepts only "true"/"false", so a typo cannot switch a
  required mode off

SYMBOLS.md is regenerated for the new exported API.

Refs #165

---
[signing-metadata]
key = cic-my-sign-key
signature = vault:v1:MEUCIG5iHcypWDbQUGJr4Grc5pB7VgrQLtIBEoW99oGm2u9zAiEA879xHkJbkHOihs9iX59xjWxyIIht8YQXrYWp/76Qje0=
hash-algorithm = sha256
digest = 7nm9tQyatYL523w/SYRZoB9m0rb0ETFbVSTtBbYse1o=

[certificate]
-----BEGIN CERTIFICATE-----
MIICBjCCAaygAwIBAgIUSnRMR6RPnEbg296XWPOqq/u5PCwwCgYIKoZIzj0EAwIw
QzELMAkGA1UEBhMCSFUxGTAXBgNVBAoMEENlbnRyYWxJbmZyYUNvcmUxGTAXBgNV
BAMMEENJQyBEZXZlbG9wZXIgQ0EwHhcNMjYwMzIwMTMyMjU5WhcNMjYxMjMxMTMy
MjU5WjBFMQswCQYDVQQGEwJIVTEZMBcGA1UECgwQQ2VudHJhbEluZnJhQ29yZTEb
MBkGA1UEAwwSR2Fib3IgWm9sdGFuIFNpbmtvMFkwEwYHKoZIzj0CAQYIKoZIzj0D
AQcDQgAEIG2CVmTfmLB9pLLclj7YmP2eedAjklpy4LGrU2ijoiy6Xqpuybv7OgJe
i+ez31s65NEV8+X/ByeX1cstR988z6N8MHowCQYDVR0TBAIwADAdBgNVHQ4EFgQU
yZN6AIX/TNnIJ9GwAa/NRN3ujHAwHwYDVR0jBBgwFoAUXn6CHYzPUqU4JVP8g+OS
WeDYjhcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF
BQcDBDAKBggqhkjOPQQDAgNIADBFAiEA+bFzXRoJ4PCQbhAAtpkcMjt0vNj5rEW0
lOMBGDNyaWkCIB1vmM7PcZzv/c9bIrxF5kqv6QXomouhByUfeNUTbpKW
-----END CERTIFICATE-----
…h MANIFEST.sha256

The required-evidence failure happens after the workflow has run, so a client must
not read the 503 as a failure it can retry blindly. The error is now a structured
EvidenceRequiredError (workflow id, chain hash, error code), and the 503 body adds
execution_completed=true, proof.persisted=false and chain_hash next to the usual
code/error. errors.Is(err, ErrEvidenceRequired) still holds.

The previous commit regenerated context/SYMBOLS.md but not MANIFEST.sha256, which
hashes it; manifest-check failed on that drift. MANIFEST.sha256 is regenerated with
scripts/gen_manifest.sh and differs from the base only in the SYMBOLS.md line.

Refs #165

---
[signing-metadata]
key = cic-my-sign-key
signature = vault:v1:MEQCIAZePYkyY/K38oB2KVn/188gbMZ2g41xPmANwCLOtClSAiB6ZMAkahciOaKY4yAyzHyiTZb3PyI7iF0MQ7fnoZGYUA==
hash-algorithm = sha256
digest = IKBez3zA0dxL5DHpfrwMq3HrBoz8gLKi3JY1Ctih7z8=

[certificate]
-----BEGIN CERTIFICATE-----
MIICBjCCAaygAwIBAgIUSnRMR6RPnEbg296XWPOqq/u5PCwwCgYIKoZIzj0EAwIw
QzELMAkGA1UEBhMCSFUxGTAXBgNVBAoMEENlbnRyYWxJbmZyYUNvcmUxGTAXBgNV
BAMMEENJQyBEZXZlbG9wZXIgQ0EwHhcNMjYwMzIwMTMyMjU5WhcNMjYxMjMxMTMy
MjU5WjBFMQswCQYDVQQGEwJIVTEZMBcGA1UECgwQQ2VudHJhbEluZnJhQ29yZTEb
MBkGA1UEAwwSR2Fib3IgWm9sdGFuIFNpbmtvMFkwEwYHKoZIzj0CAQYIKoZIzj0D
AQcDQgAEIG2CVmTfmLB9pLLclj7YmP2eedAjklpy4LGrU2ijoiy6Xqpuybv7OgJe
i+ez31s65NEV8+X/ByeX1cstR988z6N8MHowCQYDVR0TBAIwADAdBgNVHQ4EFgQU
yZN6AIX/TNnIJ9GwAa/NRN3ujHAwHwYDVR0jBBgwFoAUXn6CHYzPUqU4JVP8g+OS
WeDYjhcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF
BQcDBDAKBggqhkjOPQQDAgNIADBFAiEA+bFzXRoJ4PCQbhAAtpkcMjt0vNj5rEW0
lOMBGDNyaWkCIB1vmM7PcZzv/c9bIrxF5kqv6QXomouhByUfeNUTbpKW
-----END CERTIFICATE-----
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.

1 participant