Proposal: optional verdict_ref on request_approval -- an independently-checkable claim alongside the approver's message
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
Not competing with #2376/WF-08 -- that's the plumbing (suspend/resume/grant/deny) and it's real, needed, orthogonal work. This is a separate, additive proposal for what the approver *sees* while deciding, once that plumbing lands.
`request_approval` suspends a run and waits for a human (or pubkey) to grant/deny via `from`. That's identity-gated authorization -- is this party allowed to say yes. It doesn't yet give the approver anything about whether the specific action is *sound*: the `message` field is free text, not a checkable claim.
Proposing an optional field that attaches an independently-checkable pre-action claim to the approval request as input to the human decision, never a replacement for it.
## Concrete shape
Extend `request_approval`'s config with an optional `verdict_ref`:
```
action: request_approval
from: "@release-manager"
message: "Deploy prod config v3"
verdict_ref: "sha256:1a3a2675...c02" # optional -- content-addressed pointer to an external, independently-checkable claim
timeout: 24h
```
When present, the `workflow_approvals` row carries `verdict_ref` alongside the token hash, and the kind:46010 announcement includes it so it shows up in the approver's needs-action feed next to the message -- something concrete to check, not just prose to trust. The approver still grants or denies; the claim is context. Additive on the request + announcement event only -- no changes to the grant/deny relay handlers or the resume path, so it doesn't depend on WF-08 landing first and could ship in parallel.
## Why content-addressed, not inline
A pointer (vs. embedding the claim's payload) means the approver isn't asked to trust whoever attached it -- they (or anyone auditing the thread later) independently recompute/verify the referenced claim against its own issuer's public key, the same non-repudiation property buzz's signed-event model already has everywhere else. The field shape doesn't need to name any specific issuer -- any signed, recomputable claim fits the slot. We run one shape of this (a pre-action verdict, checkable without an account) and would build a real worked example once WF-08 lands, but that's one implementation, not what the field itself commits to.
## Scope
Purely additive. Doesn't block or get blocked by #2376. `from` stays authoritative on who can grant -- this changes what they see, not who decides.
## Worked interop appendix (added after cross-verification with @ThoughtProof)
Two independently-implemented, independently-recomputed digest pairs — useful for anyone implementing a `verdict_ref`-compatible checker who wants a literal fixture to test their own canonicalization against, rather than trusting either party's claim:
**Pair 1 — bare content hash (no envelope):** `artifact_hash` (ThoughtProof's side) and `action_digest` (ours) over the same raw bytes matched directly, no canonicalization involved.
**Pair 2 — envelope layer, deliberately non-interchangeable:** `decision_ref` (ours, binds content hash + policy fields + verdict) and `package_digest` (ThoughtProof's, binds the verify *request* only — claim/evidence/mode, no outcome enum) sit at the same structural layer but commit to different objects by design. Don't expect them to be equal even when the underlying `artifact_hash` matches.
Fixture (experiment-only, not a production contract):
```json
{"claim":"","evidence":"","mode":"handoff","tier":"standard"}
```
Canonical form (RFC 8785 / JCS, undefined keys stripped):
```
{"claim":"","evidence":"","mode":"handoff","tier":"standard"}
sha256: d735f0020908d304d5b331665544f3cd85b20df64f2d1639d4251d4bf3aa5788
```
Verified independently on both sides against the same literal fixture: our path (`json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)`) and ThoughtProof's (`canonicalize`, RFC 8785 proper) — no shared code, matching output. A mismatch on this exact fixture in practice is almost always key-stripping or float-formatting drift, not a real digest-scheme disagreement.
Contributor guide
Research direction
Start by locating the request_approval configuration, the workflow_approvals row, and the kind:46010 announcement path described in the proposal. Determine how an optional verdict_ref would be carried through the request and announcement while leaving grant/deny relay handlers and the resume path unchanged; done means the reference is available to approvers without replacing human authorization.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- authorization, backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100