relay-flow 0.2.4-alpha → 0.2.6-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -18
- package/cmd/relay-flow/backend_selection_test.go +149 -0
- package/cmd/relay-flow/beads_composition_test.go +3 -3
- package/cmd/relay-flow/main.go +94 -13
- package/cmd/relay-flow/scenario_test.go +19 -2
- package/cmd/relay-flow/serve.go +98 -19
- package/cmd/relay-flow/serve_recovery_test.go +100 -0
- package/cmd/relay-flow/temporal_init.go +170 -0
- package/cmd/relay-flow/temporal_init_test.go +217 -0
- package/cmd/relay-flow/temporal_report_test.go +733 -0
- package/examples/beads-workflow.yaml +3 -0
- package/examples/config-reference.yaml +7 -3
- package/examples/minimal-beads-task-workflow.yaml +2 -1
- package/examples/workflow-reference.yaml +2 -1
- package/go.mod +37 -16
- package/go.sum +129 -61
- package/internal/config/machine.go +33 -1
- package/internal/config/machine_test.go +76 -0
- package/internal/execution/goworkflows/activities.go +19 -0
- package/internal/execution/goworkflows/engine.go +13 -38
- package/internal/execution/goworkflows/engine_test.go +1 -1
- package/internal/execution/goworkflows/node_runtime_test.go +113 -4
- package/internal/execution/goworkflows/projection.go +47 -464
- package/internal/execution/projection/projection.go +867 -0
- package/internal/execution/projection/projection_test.go +347 -0
- package/internal/execution/temporal/activities.go +586 -0
- package/internal/execution/temporal/engine.go +384 -0
- package/internal/execution/temporal/engine_test.go +277 -0
- package/internal/execution/temporal/interpreter.go +736 -0
- package/internal/execution/temporal/operations.go +455 -0
- package/internal/execution/temporal/operations_test.go +101 -0
- package/internal/execution/temporal/recovery.go +194 -0
- package/internal/execution/temporal/recovery_runtime.go +41 -0
- package/internal/execution/temporal/recovery_test.go +102 -0
- package/internal/execution/temporal/snapshot_restart_test.go +72 -0
- package/internal/execution/temporal/spike_test.go +934 -0
- package/internal/execution/temporal/visibility_lag_test.go +415 -0
- package/internal/harness/harness.go +5 -0
- package/internal/harness/opencode/opencode.go +14 -3
- package/internal/harness/opencode/opencode_test.go +1 -1
- package/internal/harness/opencode/repo_setup.go +1 -1
- package/internal/harness/opencode/task_env_test.go +57 -0
- package/internal/harness/pi/pi.go +58 -47
- package/internal/harness/pi/pi_test.go +26 -10
- package/internal/harness/pi/prompt_test.go +30 -1
- package/internal/harness/pi/task_env_test.go +51 -0
- package/internal/harness/pi/validation_test.go +27 -51
- package/internal/repo/service.go +18 -8
- package/internal/runner/herdr/herdr.go +14 -0
- package/internal/runner/herdr/herdr_test.go +20 -0
- package/internal/runner/orca/orca.go +33 -0
- package/internal/runner/orca/orca_test.go +33 -4
- package/internal/runner/runner.go +8 -0
- package/internal/task/beads/agent_env_test.go +52 -0
- package/internal/task/beads/beads.go +87 -7
- package/internal/task/beads/beads_test.go +78 -9
- package/internal/task/beads/repo_composition_test.go +47 -3
- package/internal/task/factory.go +31 -2
- package/internal/task/task.go +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
package projection_test
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"database/sql"
|
|
6
|
+
"errors"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"sort"
|
|
9
|
+
"testing"
|
|
10
|
+
"time"
|
|
11
|
+
|
|
12
|
+
_ "modernc.org/sqlite"
|
|
13
|
+
|
|
14
|
+
"github.com/rajpopat27/relay-flow/internal/execution/projection"
|
|
15
|
+
"github.com/rajpopat27/relay-flow/internal/run"
|
|
16
|
+
"github.com/rajpopat27/relay-flow/internal/task"
|
|
17
|
+
"github.com/rajpopat27/relay-flow/internal/workflow"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
func openProjection(t *testing.T) (*projection.RunProjection, *sql.DB) {
|
|
21
|
+
t.Helper()
|
|
22
|
+
db, err := sql.Open("sqlite", filepath.Join(t.TempDir(), "state.db"))
|
|
23
|
+
if err != nil {
|
|
24
|
+
t.Fatalf("open projection database: %v", err)
|
|
25
|
+
}
|
|
26
|
+
db.SetMaxOpenConns(1)
|
|
27
|
+
t.Cleanup(func() { _ = db.Close() })
|
|
28
|
+
p := &projection.RunProjection{DB: db}
|
|
29
|
+
if err := p.Migrate(); err != nil {
|
|
30
|
+
t.Fatalf("migrate projection: %v", err)
|
|
31
|
+
}
|
|
32
|
+
return p, db
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func projectionStart(id run.ID, ticket string) run.Start {
|
|
36
|
+
return run.Start{
|
|
37
|
+
ID: id,
|
|
38
|
+
Repo: "payments",
|
|
39
|
+
RepoPath: "/srv/payments",
|
|
40
|
+
Workflow: workflow.Workflow{
|
|
41
|
+
Name: "basicFlow",
|
|
42
|
+
Repos: []string{"payments"},
|
|
43
|
+
Nodes: map[string]workflow.Node{
|
|
44
|
+
"start": {OnSuccess: []workflow.Route{{Target: "coding"}}},
|
|
45
|
+
"coding": {
|
|
46
|
+
Type: workflow.NodeAgent, Agent: "build", Description: "work",
|
|
47
|
+
OnSuccess: []workflow.Route{{Target: "end"}},
|
|
48
|
+
OnFailure: []workflow.Route{{Target: "coding"}},
|
|
49
|
+
},
|
|
50
|
+
"end": {},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
Ticket: task.TicketRef{ID: ticket, Key: ticket, Title: "parent"},
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func TestSharedProjectionSchemaAndQueries(t *testing.T) {
|
|
58
|
+
ctx := context.Background()
|
|
59
|
+
p, db := openProjection(t)
|
|
60
|
+
|
|
61
|
+
rows, err := db.Query(`SELECT name FROM sqlite_master WHERE type = 'table' AND name LIKE 'relay_%'`)
|
|
62
|
+
if err != nil {
|
|
63
|
+
t.Fatalf("list relay tables: %v", err)
|
|
64
|
+
}
|
|
65
|
+
defer rows.Close()
|
|
66
|
+
var tables []string
|
|
67
|
+
for rows.Next() {
|
|
68
|
+
var name string
|
|
69
|
+
if err := rows.Scan(&name); err != nil {
|
|
70
|
+
t.Fatalf("scan relay table: %v", err)
|
|
71
|
+
}
|
|
72
|
+
tables = append(tables, name)
|
|
73
|
+
}
|
|
74
|
+
if err := rows.Err(); err != nil {
|
|
75
|
+
t.Fatalf("iterate relay tables: %v", err)
|
|
76
|
+
}
|
|
77
|
+
sort.Strings(tables)
|
|
78
|
+
wantTables := []string{
|
|
79
|
+
"relay_executor_identity",
|
|
80
|
+
"relay_node_runtime",
|
|
81
|
+
"relay_node_sessions",
|
|
82
|
+
"relay_processed_reports",
|
|
83
|
+
"relay_runs",
|
|
84
|
+
}
|
|
85
|
+
if len(tables) != len(wantTables) {
|
|
86
|
+
t.Fatalf("relay tables = %v, want exactly %v", tables, wantTables)
|
|
87
|
+
}
|
|
88
|
+
for i := range wantTables {
|
|
89
|
+
if tables[i] != wantTables[i] {
|
|
90
|
+
t.Fatalf("relay tables = %v, want exactly %v", tables, wantTables)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
start := projectionStart("payments/basicFlow/PAY-101", "PAY-101")
|
|
95
|
+
startedAt := time.Unix(100, 0).UTC()
|
|
96
|
+
if err := p.InsertStart(ctx, start, startedAt); err != nil {
|
|
97
|
+
t.Fatalf("insert run: %v", err)
|
|
98
|
+
}
|
|
99
|
+
got, err := p.Get(ctx, start.ID)
|
|
100
|
+
if err != nil {
|
|
101
|
+
t.Fatalf("get run: %v", err)
|
|
102
|
+
}
|
|
103
|
+
if got.ID != start.ID || got.Ticket.Key != "PAY-101" || got.State != run.StateStarting {
|
|
104
|
+
t.Fatalf("get run = %+v", got)
|
|
105
|
+
}
|
|
106
|
+
byTicket, err := p.FindByTicket(ctx, "PAY-101")
|
|
107
|
+
if err != nil || byTicket.ID != start.ID {
|
|
108
|
+
t.Fatalf("find by ticket = %+v, err %v", byTicket, err)
|
|
109
|
+
}
|
|
110
|
+
listed, err := p.List(ctx, run.Filter{Workflow: "basicFlow", Active: boolPtr(true)})
|
|
111
|
+
if err != nil || len(listed) != 1 || listed[0].ID != start.ID {
|
|
112
|
+
t.Fatalf("list active runs = %+v, err %v", listed, err)
|
|
113
|
+
}
|
|
114
|
+
activeWorkflow, err := p.HasActiveWorkflow(ctx, "basicFlow")
|
|
115
|
+
if err != nil || !activeWorkflow {
|
|
116
|
+
t.Fatalf("HasActiveWorkflow = %v, err %v", activeWorkflow, err)
|
|
117
|
+
}
|
|
118
|
+
activeRepo, err := p.HasActiveRepo(ctx, "payments")
|
|
119
|
+
if err != nil || !activeRepo {
|
|
120
|
+
t.Fatalf("HasActiveRepo = %v, err %v", activeRepo, err)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if err := p.UpdateNode(ctx, start.ID, run.StateWaiting, "coding", "visit-1"); err != nil {
|
|
124
|
+
t.Fatalf("update node: %v", err)
|
|
125
|
+
}
|
|
126
|
+
runtime := projection.NodeRuntime{
|
|
127
|
+
RunID: start.ID, Node: "coding", TerminalID: "term-1", SessionID: "session-1",
|
|
128
|
+
NodeVisitID: "visit-1", UpdatedAt: startedAt,
|
|
129
|
+
}
|
|
130
|
+
if err := p.UpdateNodeRuntime(ctx, runtime); err != nil {
|
|
131
|
+
t.Fatalf("update node runtime: %v", err)
|
|
132
|
+
}
|
|
133
|
+
gotRuntime, err := p.GetNodeRuntime(ctx, start.ID, "coding")
|
|
134
|
+
if err != nil || gotRuntime.TerminalID != "term-1" || gotRuntime.SessionID != "session-1" || gotRuntime.NodeVisitID != "visit-1" {
|
|
135
|
+
t.Fatalf("get node runtime = %+v, err %v", gotRuntime, err)
|
|
136
|
+
}
|
|
137
|
+
if err := p.RecordProcessedReport(ctx, start.ID, "visit-1", "report-1"); err != nil {
|
|
138
|
+
t.Fatalf("record report: %v", err)
|
|
139
|
+
}
|
|
140
|
+
if err := p.RecordProcessedReport(ctx, start.ID, "visit-1", "report-1"); err != nil {
|
|
141
|
+
t.Fatalf("idempotent record report: %v", err)
|
|
142
|
+
}
|
|
143
|
+
processed, err := p.HasProcessedReport(ctx, start.ID, "report-1")
|
|
144
|
+
if err != nil || !processed {
|
|
145
|
+
t.Fatalf("HasProcessedReport = %v, err %v", processed, err)
|
|
146
|
+
}
|
|
147
|
+
var reportCount int
|
|
148
|
+
if err := db.QueryRow(`SELECT COUNT(*) FROM relay_processed_reports WHERE run_id = ?`, string(start.ID)).Scan(&reportCount); err != nil {
|
|
149
|
+
t.Fatalf("count processed reports: %v", err)
|
|
150
|
+
}
|
|
151
|
+
if reportCount != 1 {
|
|
152
|
+
t.Fatalf("processed report rows = %d, want one after repeated write", reportCount)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
func TestProjectionWritesAreDerivedAndDoNotSelectRoutes(t *testing.T) {
|
|
157
|
+
ctx := context.Background()
|
|
158
|
+
p, db := openProjection(t)
|
|
159
|
+
start := projectionStart("run-derived", "PAY-201")
|
|
160
|
+
if err := p.InsertStart(ctx, start, time.Now().UTC()); err != nil {
|
|
161
|
+
t.Fatal(err)
|
|
162
|
+
}
|
|
163
|
+
if err := p.UpdateNode(ctx, start.ID, run.StateWaiting, "coding", "visit-1"); err != nil {
|
|
164
|
+
t.Fatal(err)
|
|
165
|
+
}
|
|
166
|
+
if err := p.UpdateNode(ctx, start.ID, run.StateWaiting, "coding", "visit-1"); err != nil {
|
|
167
|
+
t.Fatal(err)
|
|
168
|
+
}
|
|
169
|
+
if err := p.RecordProcessedReport(ctx, start.ID, "visit-1", "report-1"); err != nil {
|
|
170
|
+
t.Fatal(err)
|
|
171
|
+
}
|
|
172
|
+
if err := p.RecordProcessedReport(ctx, start.ID, "visit-1", "report-1"); err != nil {
|
|
173
|
+
t.Fatal(err)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
columns, err := db.Query(`SELECT name FROM pragma_table_info('relay_runs')`)
|
|
177
|
+
if err != nil {
|
|
178
|
+
t.Fatal(err)
|
|
179
|
+
}
|
|
180
|
+
defer columns.Close()
|
|
181
|
+
for columns.Next() {
|
|
182
|
+
var name string
|
|
183
|
+
if err := columns.Scan(&name); err != nil {
|
|
184
|
+
t.Fatal(err)
|
|
185
|
+
}
|
|
186
|
+
switch name {
|
|
187
|
+
case "selected_route", "report", "report_id", "next_step":
|
|
188
|
+
t.Fatalf("projection stores execution-authority column %q", name)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
var receipts int
|
|
192
|
+
if err := db.QueryRow(`SELECT COUNT(*) FROM relay_processed_reports WHERE run_id = ?`, string(start.ID)).Scan(&receipts); err != nil {
|
|
193
|
+
t.Fatal(err)
|
|
194
|
+
}
|
|
195
|
+
if receipts != 1 {
|
|
196
|
+
t.Fatalf("idempotent projection receipt count = %d, want one", receipts)
|
|
197
|
+
}
|
|
198
|
+
got, err := p.Get(ctx, start.ID)
|
|
199
|
+
if err != nil || got.CurrentNode != "coding" || got.CurrentNodeVisitID != "visit-1" {
|
|
200
|
+
t.Fatalf("derived run after repeated writes = %+v, err %v", got, err)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
func TestProjectionImplementationIsSharedByExecutorModes(t *testing.T) {
|
|
205
|
+
for _, mode := range []string{"goworkflows", "temporal"} {
|
|
206
|
+
t.Run(mode, func(t *testing.T) {
|
|
207
|
+
p, db := openProjection(t)
|
|
208
|
+
var count int
|
|
209
|
+
if err := db.QueryRow(`SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name LIKE 'relay_%'`).Scan(&count); err != nil {
|
|
210
|
+
t.Fatal(err)
|
|
211
|
+
}
|
|
212
|
+
if count != 5 {
|
|
213
|
+
t.Fatalf("relay schema table count = %d, want five shared tables", count)
|
|
214
|
+
}
|
|
215
|
+
if err := p.InsertStart(context.Background(), projectionStart(run.ID("run-"+mode), "PAY-"+mode), time.Now().UTC()); err != nil {
|
|
216
|
+
t.Fatalf("insert %s run through shared projection: %v", mode, err)
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
func TestInitDatabaseWithIdentityWritesMarkerBeforeSuccess(t *testing.T) {
|
|
223
|
+
path := filepath.Join(t.TempDir(), "state.db")
|
|
224
|
+
identity := projection.ExecutorIdentity{
|
|
225
|
+
ExecutorPlugin: "temporal", TemporalAddress: "localhost:7233", TemporalNamespace: "relay-flow-init",
|
|
226
|
+
}
|
|
227
|
+
if err := projection.InitDatabaseWithIdentity(path, identity); err != nil {
|
|
228
|
+
t.Fatalf("initialize projection with identity: %v", err)
|
|
229
|
+
}
|
|
230
|
+
db, err := sql.Open("sqlite", path)
|
|
231
|
+
if err != nil {
|
|
232
|
+
t.Fatal(err)
|
|
233
|
+
}
|
|
234
|
+
defer db.Close()
|
|
235
|
+
p := &projection.RunProjection{DB: db}
|
|
236
|
+
got, ok, err := p.Identity(context.Background())
|
|
237
|
+
if err != nil || !ok || got != identity {
|
|
238
|
+
t.Fatalf("initialized identity = %#v/%v, err %v", got, ok, err)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
func TestExecutorIdentityIsSingletonAndImmutable(t *testing.T) {
|
|
243
|
+
ctx := context.Background()
|
|
244
|
+
p, db := openProjection(t)
|
|
245
|
+
embedded := projection.ExecutorIdentity{ExecutorPlugin: "goworkflows"}
|
|
246
|
+
if err := p.InitializeIdentity(ctx, embedded); err != nil {
|
|
247
|
+
t.Fatalf("initialize embedded identity: %v", err)
|
|
248
|
+
}
|
|
249
|
+
if err := p.InitializeIdentity(ctx, embedded); err != nil {
|
|
250
|
+
t.Fatalf("repeat identical identity initialization: %v", err)
|
|
251
|
+
}
|
|
252
|
+
got, ok, err := p.Identity(ctx)
|
|
253
|
+
if err != nil || !ok || got != embedded {
|
|
254
|
+
t.Fatalf("identity = %#v/%v, err %v; want %#v/present", got, ok, err, embedded)
|
|
255
|
+
}
|
|
256
|
+
if err := p.VerifyIdentity(ctx, embedded); err != nil {
|
|
257
|
+
t.Fatalf("verify matching identity: %v", err)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
for _, tc := range []struct {
|
|
261
|
+
name string
|
|
262
|
+
expected projection.ExecutorIdentity
|
|
263
|
+
wantError error
|
|
264
|
+
}{
|
|
265
|
+
{
|
|
266
|
+
name: "different executor",
|
|
267
|
+
expected: projection.ExecutorIdentity{ExecutorPlugin: "temporal", TemporalAddress: "localhost:7233", TemporalNamespace: "relay-flow"},
|
|
268
|
+
wantError: projection.ErrIdentityMismatch,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: "Temporal fields in embedded config",
|
|
272
|
+
expected: projection.ExecutorIdentity{ExecutorPlugin: "goworkflows", TemporalAddress: "localhost:7233"},
|
|
273
|
+
wantError: nil,
|
|
274
|
+
},
|
|
275
|
+
} {
|
|
276
|
+
t.Run("reject "+tc.name, func(t *testing.T) {
|
|
277
|
+
err := p.VerifyIdentity(ctx, tc.expected)
|
|
278
|
+
if tc.wantError != nil {
|
|
279
|
+
if !errors.Is(err, tc.wantError) {
|
|
280
|
+
t.Fatalf("verify mismatch error = %v, want %v", err, tc.wantError)
|
|
281
|
+
}
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
if err == nil {
|
|
285
|
+
t.Fatal("invalid embedded identity unexpectedly verified")
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
var identityRows int
|
|
290
|
+
if err := db.QueryRow(`SELECT COUNT(*) FROM relay_executor_identity`).Scan(&identityRows); err != nil {
|
|
291
|
+
t.Fatal(err)
|
|
292
|
+
}
|
|
293
|
+
if identityRows != 1 {
|
|
294
|
+
t.Fatalf("identity rows = %d, want singleton", identityRows)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
func TestLegacyMissingIdentityIsGoworkflowsOnly(t *testing.T) {
|
|
299
|
+
ctx := context.Background()
|
|
300
|
+
p, _ := openProjection(t)
|
|
301
|
+
legacy := projection.ExecutorIdentity{ExecutorPlugin: "goworkflows"}
|
|
302
|
+
if err := p.VerifyIdentity(ctx, legacy); err != nil {
|
|
303
|
+
t.Fatalf("legacy embedded identity should be adopted: %v", err)
|
|
304
|
+
}
|
|
305
|
+
got, ok, err := p.Identity(ctx)
|
|
306
|
+
if err != nil || !ok || got != legacy {
|
|
307
|
+
t.Fatalf("adopted legacy identity = %#v/%v, err %v", got, ok, err)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
p2, _ := openProjection(t)
|
|
311
|
+
temporal := projection.ExecutorIdentity{
|
|
312
|
+
ExecutorPlugin: "temporal", TemporalAddress: "localhost:7233", TemporalNamespace: "relay-flow-test",
|
|
313
|
+
}
|
|
314
|
+
if err := p2.VerifyIdentity(ctx, temporal); !errors.Is(err, projection.ErrIdentityMissing) {
|
|
315
|
+
t.Fatalf("missing Temporal identity error = %v, want ErrIdentityMissing", err)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
func TestIdentitySurvivesProjectionRebuild(t *testing.T) {
|
|
320
|
+
ctx := context.Background()
|
|
321
|
+
p, db := openProjection(t)
|
|
322
|
+
temporal := projection.ExecutorIdentity{
|
|
323
|
+
ExecutorPlugin: "temporal", TemporalAddress: "localhost:7233", TemporalNamespace: "relay-flow-rebuild",
|
|
324
|
+
}
|
|
325
|
+
if err := p.InitializeIdentity(ctx, temporal); err != nil {
|
|
326
|
+
t.Fatalf("initialize Temporal identity: %v", err)
|
|
327
|
+
}
|
|
328
|
+
start := projectionStart("run-1", "PAY-101")
|
|
329
|
+
if err := p.InsertStart(ctx, start, time.Now().UTC()); err != nil {
|
|
330
|
+
t.Fatalf("insert run before rebuild: %v", err)
|
|
331
|
+
}
|
|
332
|
+
if _, err := db.Exec(`DELETE FROM relay_runs`); err != nil {
|
|
333
|
+
t.Fatalf("clear derived projection: %v", err)
|
|
334
|
+
}
|
|
335
|
+
if err := p.Migrate(); err != nil {
|
|
336
|
+
t.Fatalf("rebuild shared projection schema: %v", err)
|
|
337
|
+
}
|
|
338
|
+
if err := p.VerifyIdentity(ctx, temporal); err != nil {
|
|
339
|
+
t.Fatalf("identity after projection rebuild: %v", err)
|
|
340
|
+
}
|
|
341
|
+
got, ok, err := p.Identity(ctx)
|
|
342
|
+
if err != nil || !ok || got != temporal {
|
|
343
|
+
t.Fatalf("identity after rebuild = %#v/%v, err %v", got, ok, err)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
func boolPtr(v bool) *bool { return &v }
|