Skip to content

Commit 89505c6

Browse files
authored
Merge pull request #1 from IntentProof/go-sdk-core
feat(sdk-go): core wrap, signing, outbox, and golden parity
2 parents 85caa8d + d412879 commit 89505c6

37 files changed

Lines changed: 2701 additions & 24 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,4 @@ jobs:
2121
run: go mod verify
2222

2323
- name: Run tests
24-
run: |
25-
if [ -z "$(find . -name '*.go' -not -path './.git/*')" ]; then
26-
echo "No Go source files found (project scaffold only)"
27-
exit 0
28-
fi
29-
go test ./...
24+
run: go test ./...

.github/workflows/release-signing-dry-run.yml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,7 @@ jobs:
3535
run: go mod verify
3636

3737
- name: Run tests
38-
run: |
39-
if [ -z "$(find . -name '*.go' -not -path './.git/*')" ]; then
40-
echo "No Go source files found (project scaffold only)"
41-
exit 0
42-
fi
43-
go test ./...
38+
run: go test ./...
4439

4540
- name: Package module source archive
4641
run: |

README.md

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,75 @@ Go SDK for emitting signed IntentProof execution events.
44

55
## Status
66

7-
Early scaffolding repo for IntentProof's Go SDK. Tracks the Node and
8-
Python SDK `wrap()` / exporter / outbox contract so a Go application
9-
can emit and verify the same signed execution events.
10-
11-
## Planned scope
12-
13-
- `wrap()` instrumentation helper
14-
- Correlation-id helpers
15-
- Event signing and canonical serialization (JCS)
16-
- Durable outbox and hosted ingest transport
7+
Core SDK aligned with the Node and Python SDKs: `Configure`, `Wrap`,
8+
`RunWithCorrelationID`, `Flush`, JCS canonicalization, Ed25519 signing,
9+
SQLite WAL outbox, and optional HTTP export to ingest.
1710

1811
## Module path
1912

2013
```text
2114
github.com/intentproof/intentproof-sdk-go
2215
```
2316

24-
SDK implementation and local development steps land in a follow-on
25-
change.
17+
## Quick start
18+
19+
```go
20+
package main
21+
22+
import (
23+
"log"
24+
25+
"github.com/intentproof/intentproof-sdk-go/intentproof"
26+
)
27+
28+
func main() {
29+
if err := intentproof.Configure(intentproof.ConfigureOptions{
30+
DBPath: "./intentproof-outbox.db",
31+
DataDir: "./.intentproof-sdk-go",
32+
}); err != nil {
33+
log.Fatal(err)
34+
}
35+
36+
refund := intentproof.Wrap(
37+
"Return funds to the customer",
38+
"payments.refund.execute",
39+
func(input map[string]any) map[string]any {
40+
// call your payment provider here
41+
return map[string]any{"id": "re_123"}
42+
},
43+
)
44+
45+
intentproof.RunWithCorrelationID("req_refund_ord_1042", func() {
46+
_ = refund(map[string]any{
47+
"amount_cents": 4999,
48+
"payment_intent": "pi_123",
49+
})
50+
})
51+
52+
intentproof.Flush()
53+
}
54+
```
55+
56+
## Local ingest
57+
58+
- `INTENTPROOF_INGEST_URL` — hosted or local ingest base URL (normalized to
59+
`/v1/events`).
60+
- `INTENTPROOF_USE_LOCAL_INGEST=1` — default local loop
61+
`http://127.0.0.1:9787/v1/events`.
62+
- `INTENTPROOF_INGEST_TOKEN` — bearer token for hosted ingest.
63+
- `INTENTPROOF_TENANT_ID` — default tenant when `Configure` omits `TenantID`.
64+
- `INTENTPROOF_OUTBOX_PATH` — SQLite outbox path when `DBPath` is omitted.
65+
66+
Default signing keys live under `~/.intentproof/sdk-go/keypair.json`.
67+
68+
## Development
69+
70+
```bash
71+
go test ./...
72+
```
73+
74+
Cross-language signing fixtures under `testdata/fixtures/` match the Node and
75+
Python SDK conformance set.
2676

2777
## License
2878

go.mod

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
11
module github.com/intentproof/intentproof-sdk-go
22

33
go 1.25.0
4+
5+
require (
6+
github.com/oklog/ulid/v2 v2.1.1
7+
modernc.org/sqlite v1.50.1
8+
)
9+
10+
require (
11+
github.com/dustin/go-humanize v1.0.1 // indirect
12+
github.com/google/uuid v1.6.0 // indirect
13+
github.com/mattn/go-isatty v0.0.20 // indirect
14+
github.com/ncruces/go-strftime v1.0.0 // indirect
15+
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
16+
golang.org/x/sys v0.42.0 // indirect
17+
modernc.org/libc v1.72.3 // indirect
18+
modernc.org/mathutil v1.7.1 // indirect
19+
modernc.org/memory v1.11.0 // indirect
20+
)

go.sum

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
2+
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
3+
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
4+
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
5+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
6+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
7+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
8+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
9+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
10+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
11+
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
12+
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
13+
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
14+
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
15+
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
16+
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
17+
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
18+
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
19+
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
20+
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
21+
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
22+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
23+
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
24+
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
25+
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
26+
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
27+
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
28+
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
29+
modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
30+
modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A=
31+
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
32+
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
33+
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
34+
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
35+
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
36+
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
37+
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
38+
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
39+
modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
40+
modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
41+
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
42+
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
43+
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
44+
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
45+
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
46+
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
47+
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
48+
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
49+
modernc.org/sqlite v1.50.1 h1:l+cQvn0sd0zJJtfygGHuQJ5AjlrwXmWPw4KP3ZMwr9w=
50+
modernc.org/sqlite v1.50.1/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
51+
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
52+
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
53+
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
54+
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

intentproof/canon.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package intentproof
2+
3+
import (
4+
"github.com/intentproof/intentproof-sdk-go/internal/canon"
5+
)
6+
7+
// Canonicalize returns the RFC 8785 canonical JSON encoding of v.
8+
func Canonicalize(v any) (string, error) {
9+
b, err := canon.Marshal(v)
10+
if err != nil {
11+
return "", err
12+
}
13+
return string(b), nil
14+
}
15+
16+
// canonicalizeEvent returns canonical JSON for an unsigned execution event.
17+
func canonicalizeEvent(event map[string]any) (string, error) {
18+
unsigned := make(map[string]any, len(event))
19+
for k, v := range event {
20+
if k == "signature" {
21+
continue
22+
}
23+
unsigned[k] = v
24+
}
25+
return Canonicalize(unsigned)
26+
}

intentproof/canon_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package intentproof_test
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
"testing"
8+
9+
"github.com/intentproof/intentproof-sdk-go/intentproof"
10+
)
11+
12+
func TestPolicyBodyCrossCheck(t *testing.T) {
13+
body := map[string]any{
14+
"schema": "intentproof.policy.v1",
15+
"policy_id": "tnt.test",
16+
"policy_version": int64(1),
17+
"tenant_id": "tnt",
18+
"spec_version": "1.0.0",
19+
"scope": map[string]any{"any_event_action_in": []any{"a"}},
20+
"rules": []any{
21+
map[string]any{
22+
"id": "r1",
23+
"category": "required",
24+
"severity": "high",
25+
"spec": map[string]any{"action": "a"},
26+
},
27+
},
28+
}
29+
got, err := intentproof.Canonicalize(body)
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
wantBytes, err := os.ReadFile(filepath.Join("..", "testdata", "fixtures", "policy_body_canon.json"))
34+
if err != nil {
35+
t.Fatal(err)
36+
}
37+
want := strings.TrimRight(string(wantBytes), "\r\n")
38+
if got != want {
39+
t.Fatalf("canonical mismatch:\nwant %q\ngot %q", want, got)
40+
}
41+
}

0 commit comments

Comments
 (0)