feat(02-02): capture, scrub, and strictly diff stateful flows

Resolve named placeholders from a private variable store, mask
dates and ids after shape checks, and keep comparing independent steps.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jakub Zych
2026-09-17 12:32:49 +02:00
parent bb6a5a91c9
commit aa165fe3d0
13 changed files with 1390 additions and 203 deletions

View File

@@ -30,12 +30,27 @@ func RecordFlow(ctx context.Context, spec Flow, cfg RecordConfig) (Flow, error)
out.Version = CurrentVersion
out.Steps = make([]Step, len(spec.Steps))
copy(out.Steps, spec.Steps)
for i, step := range spec.Steps {
resp, err := doStep(ctx, client, cfg.Target, step.Request, limit)
for i := range out.Steps {
step := &out.Steps[i]
mergeRouteCaptures(step, cfg.Rules)
req, err := expandRequest(step.Request, cfg.Store)
if err != nil {
return Flow{}, fmt.Errorf("tide: record step %s: %w", step.ID, err)
}
out.Steps[i].Response = resp
resp, err := doStep(ctx, client, cfg.Target, req, limit)
if err != nil {
return Flow{}, fmt.Errorf("tide: record step %s: %w", step.ID, err)
}
step.Response = resp
if err := CaptureStep(cfg.Store, step); err != nil {
return Flow{}, fmt.Errorf("tide: record step %s: %w", step.ID, err)
}
if err := ScrubStep(cfg.Store, step); err != nil {
return Flow{}, fmt.Errorf("tide: record step %s: %w", step.ID, err)
}
if err := cfg.Store.Save(); err != nil {
return Flow{}, err
}
}
if err := validateFlow(out); err != nil {
return Flow{}, err
@@ -100,11 +115,24 @@ func joinURL(target, path, rawQuery string) (string, error) {
}
func keepResponseHeaders(h http.Header) map[string]string {
ct := h.Get("Content-Type")
if ct == "" {
return nil
}
return map[string]string{"Content-Type": ct}
return filterHeaders(h, recordedHeaderNames)
}
var recordedHeaderNames = []string{
"Content-Type",
"Location",
"Set-Cookie",
"Cache-Control",
"Pragma",
"WWW-Authenticate",
"Content-Disposition",
"Access-Control-Allow-Origin",
"Access-Control-Allow-Credentials",
"Access-Control-Allow-Headers",
"Access-Control-Allow-Methods",
"Access-Control-Expose-Headers",
"X-Total-Count",
"Link",
}
func readBounded(r io.Reader, max int64) ([]byte, error) {