Skip to content

Commit b81acaf

Browse files
固化 L4/L5 证明等级状态机
Fixes #18. TrustDB CI is green. Cursor Bugbot was not treated as a required blocking check.
1 parent 185abd4 commit b81acaf

7 files changed

Lines changed: 345 additions & 24 deletions

File tree

clients/desktop/client.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"time"
1616

1717
"github.com/ryan-wong-coder/trustdb/internal/model"
18+
"github.com/ryan-wong-coder/trustdb/internal/prooflevel"
1819
)
1920

2021
// httpTimeout bounds each request against the server. Submit + proof
@@ -297,10 +298,10 @@ func looksLikeSHA256Hex(query string) bool {
297298
}
298299

299300
func localRecordFromIndex(idx model.RecordIndex) LocalRecord {
300-
proofLevel := "L2"
301+
proofLevel := prooflevel.L2
301302
var committed *model.CommittedReceipt
302303
if idx.BatchID != "" {
303-
proofLevel = "L3"
304+
proofLevel = prooflevel.L3
304305
committed = &model.CommittedReceipt{
305306
SchemaVersion: model.SchemaCommittedReceipt,
306307
RecordID: idx.RecordID,
@@ -326,7 +327,7 @@ func localRecordFromIndex(idx model.RecordIndex) LocalRecord {
326327
TenantID: idx.TenantID,
327328
ClientID: idx.ClientID,
328329
KeyID: idx.KeyID,
329-
ProofLevel: proofLevel,
330+
ProofLevel: proofLevel.String(),
330331
BatchID: idx.BatchID,
331332
CommittedReceipt: committed,
332333
}

clients/desktop/submit.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/ryan-wong-coder/trustdb/internal/anchor"
99
"github.com/ryan-wong-coder/trustdb/internal/model"
10+
"github.com/ryan-wong-coder/trustdb/internal/prooflevel"
1011
)
1112

1213
// SubmitRequest is the single, JSON-friendly shape the UI uses to
@@ -108,7 +109,7 @@ func (a *App) SubmitFile(req SubmitRequest) (*SubmitResult, error) {
108109
TenantID: id.TenantID,
109110
ClientID: id.ClientID,
110111
KeyID: id.KeyID,
111-
ProofLevel: "L2",
112+
ProofLevel: prooflevel.L2.String(),
112113
ServerRecord: &resp.ServerRecord,
113114
AcceptedReceipt: &resp.AcceptedReceipt,
114115
}
@@ -204,22 +205,25 @@ func (a *App) RefreshRecord(recordID string) (*LocalRecord, error) {
204205
return &rec, err
205206
}
206207
cr := bundle.CommittedReceipt
207-
rec.ProofLevel = "L3"
208+
evidence := prooflevel.EvidenceFor(prooflevel.L3)
209+
rec.ProofLevel = prooflevel.Evaluate(evidence).String()
208210
rec.BatchID = cr.BatchID
209211
rec.CommittedReceipt = &cr
210212

211213
globalProof, err := c.getGlobalProof(a.ensureCtx(), cr.BatchID)
212214
if err == nil {
213215
rec.GlobalProof = &globalProof
214-
rec.ProofLevel = "L4"
216+
evidence.GlobalLogProof = true
217+
rec.ProofLevel = prooflevel.Evaluate(evidence).String()
215218
anchor, err := c.getAnchor(a.ensureCtx(), globalProof.STH.TreeSize)
216219
if err == nil {
217220
rec.AnchorStatus = anchor.Status
218221
if anchor.Result != nil {
219222
rec.AnchorResult = anchor.Result
220223
rec.AnchorSink = anchor.Result.SinkName
221224
rec.AnchorID = anchor.Result.AnchorID
222-
rec.ProofLevel = "L5"
225+
evidence.STHAnchorResult = true
226+
rec.ProofLevel = prooflevel.Evaluate(evidence).String()
223227
}
224228
}
225229
}
@@ -350,20 +354,23 @@ func (a *App) mergeExportedProofState(recordID string, bundle model.ProofBundle,
350354
}
351355
rec.LastError = ""
352356
setLocalRecordLastSyncedAt(&rec, time.Now().UTC())
353-
rec.ProofLevel = "L3"
357+
evidence := prooflevel.EvidenceFor(prooflevel.L3)
358+
rec.ProofLevel = prooflevel.Evaluate(evidence).String()
354359
rec.BatchID = bundle.CommittedReceipt.BatchID
355360
cr := bundle.CommittedReceipt
356361
rec.CommittedReceipt = &cr
357362
if global != nil {
358363
rec.GlobalProof = global
359-
rec.ProofLevel = "L4"
364+
evidence.GlobalLogProof = true
365+
rec.ProofLevel = prooflevel.Evaluate(evidence).String()
360366
}
361367
if anchor != nil {
362368
rec.AnchorResult = anchor
363369
rec.AnchorStatus = model.AnchorStatePublished
364370
rec.AnchorSink = anchor.SinkName
365371
rec.AnchorID = anchor.AnchorID
366-
rec.ProofLevel = "L5"
372+
evidence.STHAnchorResult = true
373+
rec.ProofLevel = prooflevel.Evaluate(evidence).String()
367374
}
368375
_ = st.upsertRecord(rec)
369376
}

cmd/trustdb/commit_cmd.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/ryan-wong-coder/trustdb/internal/cborx"
1313
"github.com/ryan-wong-coder/trustdb/internal/keystore"
1414
"github.com/ryan-wong-coder/trustdb/internal/model"
15+
"github.com/ryan-wong-coder/trustdb/internal/prooflevel"
1516
"github.com/ryan-wong-coder/trustdb/internal/wal"
1617
"github.com/spf13/cobra"
1718
)
@@ -82,7 +83,7 @@ func newCommitCommand(rt *runtimeConfig) *cobra.Command {
8283
return rt.writeJSON(map[string]string{
8384
"record_id": record.RecordID,
8485
"proof": outPath,
85-
"level": "L3",
86+
"level": prooflevel.L3.String(),
8687
})
8788
},
8889
}
@@ -172,7 +173,7 @@ func newCommitBatchCommand(rt *runtimeConfig) *cobra.Command {
172173
outputs[i] = map[string]string{
173174
"record_id": bundles[i].RecordID,
174175
"proof": outPath,
175-
"level": "L3",
176+
"level": prooflevel.L3.String(),
176177
}
177178
}
178179
rt.logger.Info().

internal/httpapi/handler.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/ryan-wong-coder/trustdb/internal/ingest"
1818
"github.com/ryan-wong-coder/trustdb/internal/model"
1919
"github.com/ryan-wong-coder/trustdb/internal/observability"
20+
"github.com/ryan-wong-coder/trustdb/internal/prooflevel"
2021
"github.com/ryan-wong-coder/trustdb/internal/trusterr"
2122
)
2223

@@ -206,7 +207,7 @@ func (h Handler) submitClaim(w http.ResponseWriter, r *http.Request) {
206207
writeJSON(w, status, submitClaimResponse{
207208
RecordID: record.RecordID,
208209
Status: accepted.Status,
209-
ProofLevel: "L2",
210+
ProofLevel: prooflevel.L2.String(),
210211
Idempotent: idempotent,
211212
BatchEnqueued: batchEnqueued,
212213
BatchError: batchErr,
@@ -228,7 +229,7 @@ func (h Handler) getProof(w http.ResponseWriter, r *http.Request) {
228229
}
229230
writeJSON(w, http.StatusOK, proofResponse{
230231
RecordID: bundle.RecordID,
231-
ProofLevel: "L3",
232+
ProofLevel: prooflevel.L3.String(),
232233
ProofBundle: bundle,
233234
})
234235
}
@@ -583,7 +584,7 @@ func (h Handler) getAnchor(w http.ResponseWriter, r *http.Request) {
583584
writeError(w, trusterr.New(trusterr.CodeNotFound, "anchor not found for STH"))
584585
return
585586
}
586-
resp := anchorResponse{TreeSize: treeSize, ProofLevel: "L5"}
587+
resp := anchorResponse{TreeSize: treeSize, ProofLevel: prooflevel.L5.String()}
587588
switch {
588589
case resultOK:
589590
resp.Status = model.AnchorStatePublished

internal/prooflevel/prooflevel.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Package prooflevel defines the TrustDB L1-L5 proof ladder in one place.
2+
//
3+
// The package is intentionally pure: callers provide evidence that is already
4+
// present or already verified, and the evaluator returns the strongest level
5+
// reachable without inventing intermediate trust.
6+
package prooflevel
7+
8+
type Level string
9+
10+
const (
11+
Unknown Level = ""
12+
L1 Level = "L1"
13+
L2 Level = "L2"
14+
L3 Level = "L3"
15+
L4 Level = "L4"
16+
L5 Level = "L5"
17+
)
18+
19+
type Evidence struct {
20+
ContentHash bool
21+
ClientSignature bool
22+
AcceptedReceipt bool
23+
CommittedReceipt bool
24+
BatchProof bool
25+
GlobalLogProof bool
26+
STHAnchorResult bool
27+
}
28+
29+
type Definition struct {
30+
Level Level
31+
Label string
32+
Requirement string
33+
}
34+
35+
var ladder = []Definition{
36+
{Level: L1, Label: "local content", Requirement: "content hash matches the signed client claim and the client signature is valid"},
37+
{Level: L2, Label: "accepted", Requirement: "L1 plus a valid server AcceptedReceipt"},
38+
{Level: L3, Label: "batch committed", Requirement: "L2 plus a valid CommittedReceipt and batch Merkle inclusion proof"},
39+
{Level: L4, Label: "global log", Requirement: "L3 plus a valid BatchRoot -> SignedTreeHead global-log inclusion proof"},
40+
{Level: L5, Label: "external anchor", Requirement: "L4 plus an STH/global-root external anchor result"},
41+
}
42+
43+
func Evaluate(e Evidence) Level {
44+
if !(e.ContentHash && e.ClientSignature) {
45+
return Unknown
46+
}
47+
if !e.AcceptedReceipt {
48+
return L1
49+
}
50+
if !(e.CommittedReceipt && e.BatchProof) {
51+
return L2
52+
}
53+
if !e.GlobalLogProof {
54+
return L3
55+
}
56+
if !e.STHAnchorResult {
57+
return L4
58+
}
59+
return L5
60+
}
61+
62+
func EvidenceFor(level Level) Evidence {
63+
evidence := Evidence{}
64+
if AtLeast(level, L1) {
65+
evidence.ContentHash = true
66+
evidence.ClientSignature = true
67+
}
68+
if AtLeast(level, L2) {
69+
evidence.AcceptedReceipt = true
70+
}
71+
if AtLeast(level, L3) {
72+
evidence.CommittedReceipt = true
73+
evidence.BatchProof = true
74+
}
75+
if AtLeast(level, L4) {
76+
evidence.GlobalLogProof = true
77+
}
78+
if AtLeast(level, L5) {
79+
evidence.STHAnchorResult = true
80+
}
81+
return evidence
82+
}
83+
84+
func Definitions() []Definition {
85+
out := make([]Definition, len(ladder))
86+
copy(out, ladder)
87+
return out
88+
}
89+
90+
func Parse(raw string) (Level, bool) {
91+
switch Level(raw) {
92+
case L1, L2, L3, L4, L5:
93+
return Level(raw), true
94+
default:
95+
return Unknown, false
96+
}
97+
}
98+
99+
func Rank(level Level) int {
100+
switch level {
101+
case L1:
102+
return 1
103+
case L2:
104+
return 2
105+
case L3:
106+
return 3
107+
case L4:
108+
return 4
109+
case L5:
110+
return 5
111+
default:
112+
return 0
113+
}
114+
}
115+
116+
func AtLeast(level, target Level) bool {
117+
return Rank(level) >= Rank(target) && Rank(target) > 0
118+
}
119+
120+
func (l Level) String() string {
121+
return string(l)
122+
}

0 commit comments

Comments
 (0)