Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/asottile/dockerfile v3.1.0+incompatible
github.com/blang/semver v3.5.1+incompatible
github.com/briandowns/spinner v1.23.2
github.com/codefly-dev/core v0.2.87
github.com/codefly-dev/core v0.2.89
github.com/codefly-dev/golor v0.1.3
github.com/codefly-dev/llm v0.1.0
github.com/codefly-dev/sdk-go v0.1.58
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/codefly-dev/core v0.2.87 h1:bPRCC5ZmT/JLpEz20ZpI8UKbdpoSRexmf7eTq5nl6lE=
github.com/codefly-dev/core v0.2.87/go.mod h1:hNxTk7ZnR5AU8imfvNJcGpoULQ1uzML5M3E4/RffWU4=
github.com/codefly-dev/core v0.2.89 h1:+kA6ACb/4Eit7xk7SLO9E2eTsW23Ti/H3QwO9/80je0=
github.com/codefly-dev/core v0.2.89/go.mod h1:hNxTk7ZnR5AU8imfvNJcGpoULQ1uzML5M3E4/RffWU4=
github.com/codefly-dev/golor v0.1.3 h1:xmo+ceyJFRYZdvpWE2fNd0jeaadp/Ibm1BnganiGKOc=
github.com/codefly-dev/golor v0.1.3/go.mod h1:sl/u/K1l7J0Pr3xyVZp8fOJYQItKKst1No9JqgzLLoY=
github.com/codefly-dev/gortk v0.2.0 h1:7bOlS5valYz2zil+fZctQNcPCYBcPj86abcw9N8h1hQ=
Expand Down
17 changes: 11 additions & 6 deletions pkg/gateway/prepared_mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ func (s *Server) PrepareMutation(ctx context.Context, req *gatewayv1.PrepareMuta
var fixActions []string
var after []byte
var previewBeforeHash, previewAfterHash string
var previewBeforeSize, previewAfterSize uint64
if edit != nil {
path, err = cleanGatewayPath(edit.GetFile())
if err != nil || path == "" {
Expand Down Expand Up @@ -168,6 +169,7 @@ func (s *Server) PrepareMutation(ctx context.Context, req *gatewayv1.PrepareMuta
after = []byte(preview.GetContent())
strategy, fixActions = preview.GetStrategy(), append([]string(nil), preview.GetFixActions()...)
previewBeforeHash, previewAfterHash = preview.GetBeforeSha256(), preview.GetAfterSha256()
previewBeforeSize, previewAfterSize = preview.GetBeforeSizeBytes(), preview.GetAfterSizeBytes()
} else {
path, err = cleanGatewayPath(symbolPatch.GetFile())
if err != nil || path == "" {
Expand Down Expand Up @@ -203,15 +205,16 @@ func (s *Server) PrepareMutation(ctx context.Context, req *gatewayv1.PrepareMuta
after = []byte(preview.GetContent())
strategy, fixActions = preview.GetStrategy(), append([]string(nil), preview.GetFixActions()...)
previewBeforeHash, previewAfterHash = preview.GetBeforeSha256(), preview.GetAfterSha256()
previewBeforeSize, previewAfterSize = preview.GetBeforeSizeBytes(), preview.GetAfterSizeBytes()
}
current, err := s.fileOps().ReadFile(ctx, path)
if err != nil {
return prepareFailure(fmt.Sprintf("read prepared target: %v", err)), nil
}
beforeHash := contentSHA256(current)
afterHash := contentSHA256(after)
if previewBeforeHash != beforeHash || previewAfterHash != afterHash {
return prepareFailure("language agent preview hashes do not match authoritative project bytes"), nil
if previewBeforeHash != beforeHash || previewAfterHash != afterHash || previewBeforeSize != uint64(len(current)) || previewAfterSize != uint64(len(after)) {
return prepareFailure("language agent preview identities do not match authoritative project bytes"), nil
}
prepared := &gatewayv1.PreparedMutation{
SchemaVersion: preparedMutationSchemaVersion,
Expand All @@ -223,6 +226,7 @@ func (s *Server) PrepareMutation(ctx context.Context, req *gatewayv1.PrepareMuta
Files: []*gatewayv1.PreparedFileMutation{{
Path: path, Operation: gatewayv1.PreparedFileOperation_PREPARED_FILE_OPERATION_MODIFY,
BeforeSha256: beforeHash, AfterSha256: afterHash,
BeforeSizeBytes: uint64(len(current)), AfterSizeBytes: uint64(len(after)),
Strategy: strategy, FixActions: fixActions, SymbolId: symbolID,
}},
PreparedAt: timestamppb.Now(), ExpiresAt: timestamppb.New(time.Now().UTC().Add(preparedMutationLifetime)),
Expand Down Expand Up @@ -281,7 +285,7 @@ func (s *Server) ApplyPreparedMutation(ctx context.Context, req *gatewayv1.Apply
if err != nil {
return applyPreparedFailure(fmt.Sprintf("read prepared target %q: %v", file.GetPath(), err)), nil
}
if contentSHA256(current) != file.GetBeforeSha256() {
if contentSHA256(current) != file.GetBeforeSha256() || uint64(len(current)) != file.GetBeforeSizeBytes() {
return applyPreparedFailure(fmt.Sprintf("prepared target %q drifted after preparation", file.GetPath())), nil
}
}
Expand All @@ -296,7 +300,7 @@ func (s *Server) ApplyPreparedMutation(ctx context.Context, req *gatewayv1.Apply
}
for _, file := range prepared.GetFiles() {
after, ok := afterByPath[file.GetPath()]
if !ok || contentSHA256(after) != file.GetAfterSha256() {
if !ok || contentSHA256(after) != file.GetAfterSha256() || uint64(len(after)) != file.GetAfterSizeBytes() {
return applyPreparedFailure(fmt.Sprintf("prepared bytes for %q are unavailable or corrupted", file.GetPath())), nil
}
response, err := s.proxyExecute(ctx, &codev0.CodeRequest{Operation: &codev0.CodeRequest_WriteFile{WriteFile: &codev0.WriteFileRequest{
Expand All @@ -314,6 +318,7 @@ func (s *Server) ApplyPreparedMutation(ctx context.Context, req *gatewayv1.Apply
applied = append(applied, &gatewayv1.AppliedFileMutation{
Path: file.GetPath(), Operation: file.GetOperation(),
BeforeSha256: file.GetBeforeSha256(), AfterSha256: file.GetAfterSha256(),
BeforeSizeBytes: file.GetBeforeSizeBytes(), AfterSizeBytes: file.GetAfterSizeBytes(),
})
}
s.deletePreparedMutation(prepared.GetPreparationId(), prepared.GetMutationDigest())
Expand Down Expand Up @@ -348,8 +353,8 @@ func (s *Server) storePreparedMutation(prepared *gatewayv1.PreparedMutation, aft
storedByteCount := 0
for _, file := range prepared.GetFiles() {
content, ok := afterByPath[file.GetPath()]
if !ok || contentSHA256(content) != file.GetAfterSha256() {
return fmt.Errorf("prepared bytes for %q do not match after_sha256", file.GetPath())
if !ok || contentSHA256(content) != file.GetAfterSha256() || uint64(len(content)) != file.GetAfterSizeBytes() {
return fmt.Errorf("prepared bytes for %q do not match the after identity", file.GetPath())
}
if len(content) > maxPreparedMutationBytes || storedByteCount > maxPreparedMutationBytes-len(content) {
return fmt.Errorf("prepared mutation exceeds the %d-byte retention limit", maxPreparedMutationBytes)
Expand Down
8 changes: 8 additions & 0 deletions pkg/gateway/prepared_mutation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ func TestPreparedMutationRequiresPinnedAuthorityAndAppliesSignedPermitOnce(t *te
if prepared.GetFiles()[0].ProtoReflect().Descriptor().Fields().ByName("after_content") != nil {
t.Fatal("prepared mutation RPC exposes project bytes")
}
fileIdentity := prepared.GetFiles()[0]
if fileIdentity.GetBeforeSizeBytes() != uint64(len("package service\n\nfunc Value() int { return 1 }\n")) || fileIdentity.GetAfterSizeBytes() != uint64(len("package service\n\nfunc Value() int { return 2 }\n")) {
t.Fatalf("prepared mutation sizes = before:%d after:%d", fileIdentity.GetBeforeSizeBytes(), fileIdentity.GetAfterSizeBytes())
}
if prepared.GetExpiresAt() == nil || !prepared.GetExpiresAt().AsTime().After(time.Now().UTC()) {
t.Fatalf("prepared mutation has no future expiry: %v", prepared.GetExpiresAt())
}
Expand Down Expand Up @@ -127,6 +131,9 @@ func TestPreparedSymbolPatchRetainsBytesAndRequiresExactSymbolFence(t *testing.T
if len(prepared.GetFiles()) != 1 || prepared.GetFiles()[0].GetSymbolId() != "symbol-service-value" {
t.Fatalf("prepared symbol resource = %+v", prepared.GetFiles())
}
if prepared.GetFiles()[0].GetBeforeSizeBytes() != uint64(len(before)) || prepared.GetFiles()[0].GetAfterSizeBytes() != uint64(len("package service\n\nfunc Value() int { return 2 }\n")) {
t.Fatalf("prepared symbol sizes = before:%d after:%d", prepared.GetFiles()[0].GetBeforeSizeBytes(), prepared.GetFiles()[0].GetAfterSizeBytes())
}
unchanged, err := os.ReadFile(path)
if err != nil || string(unchanged) != before {
t.Fatalf("preparation changed source: content=%q err=%v", unchanged, err)
Expand Down Expand Up @@ -256,6 +263,7 @@ func TestPreparedMutationRetentionRejectsOversizedResults(t *testing.T) {
Files: []*gatewayv1.PreparedFileMutation{{
Path: "main.go", Operation: gatewayv1.PreparedFileOperation_PREPARED_FILE_OPERATION_MODIFY,
BeforeSha256: contentSHA256([]byte("before")), AfterSha256: contentSHA256(after),
BeforeSizeBytes: uint64(len("before")), AfterSizeBytes: uint64(len(after)),
}},
PreparedAt: timestamppb.New(now), ExpiresAt: timestamppb.New(now.Add(preparedMutationLifetime)),
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/gateway/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,7 @@ func (s *Server) Fix(ctx context.Context, req *gatewayv1.FixRequest) (*gatewayv1
Success: r.GetSuccess(), Content: r.GetContent(), Error: codeFailureMessage(resp), Actions: r.GetActions(),
Changed: r.GetChanged(), BeforeSha256: r.GetBeforeSha256(), AfterSha256: r.GetAfterSha256(),
Wrote: r.GetWrote(), Output: r.GetOutput(),
BeforeSizeBytes: r.GetBeforeSizeBytes(), AfterSizeBytes: r.GetAfterSizeBytes(),
}, nil
}

Expand Down Expand Up @@ -1012,6 +1013,7 @@ func (s *Server) applyEdit(
Strategy: result.GetStrategy(), FixActions: result.GetFixActions(), Changed: result.GetChanged(),
BeforeSha256: result.GetBeforeSha256(), AfterSha256: result.GetAfterSha256(),
Wrote: result.GetWrote(), Output: result.GetOutput(),
BeforeSizeBytes: result.GetBeforeSizeBytes(), AfterSizeBytes: result.GetAfterSizeBytes(),
}, nil
}

Expand Down Expand Up @@ -1058,6 +1060,7 @@ func (s *Server) applyEditWithReceipt(
Strategy: rawResult.GetStrategy(), FixActions: rawResult.GetFixActions(), Changed: rawResult.GetChanged(),
BeforeSha256: rawResult.GetBeforeSha256(), AfterSha256: rawResult.GetAfterSha256(),
Wrote: rawResult.GetWrote(), Output: rawResult.GetOutput(),
BeforeSizeBytes: rawResult.GetBeforeSizeBytes(), AfterSizeBytes: rawResult.GetAfterSizeBytes(),
}
stage := executionv1.ExecutionStage_EXECUTION_STAGE_FAILED
statusValue := "failed"
Expand Down Expand Up @@ -1160,6 +1163,7 @@ func gatewaySymbolPatchResponse(raw *codev0.CodeResponse) *gatewayv1.ApplySymbol
BeforeSha256: result.GetBeforeSha256(), AfterSha256: result.GetAfterSha256(),
DeclarationSha256: result.GetDeclarationSha256(), Wrote: result.GetWrote(), Output: result.GetOutput(),
Failure: failures.Clone(raw.GetFailure()), FailureReason: result.GetFailureReason(),
BeforeSizeBytes: result.GetBeforeSizeBytes(), AfterSizeBytes: result.GetAfterSizeBytes(),
}
}

Expand Down
7 changes: 6 additions & 1 deletion pkg/gateway/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ func editingMock(t *testing.T, dir string) *mockCodeClient {
return &codev0.ApplyEditResponse{
Success: true, Content: content, Strategy: "exact", Changed: content != string(original), Wrote: !in.GetDryRun(),
BeforeSha256: hex.EncodeToString(beforeDigest[:]), AfterSha256: hex.EncodeToString(afterDigest[:]),
BeforeSizeBytes: uint64(len(original)), AfterSizeBytes: uint64(len(content)),
}, nil
}}
}
Expand Down Expand Up @@ -615,6 +616,7 @@ func TestFix(t *testing.T) {
Content: "package main\n\nimport \"fmt\"\n\nfunc main() { fmt.Println(\"hello\") }",
Actions: []string{"goimports", "gofmt"},
BeforeSha256: "before", AfterSha256: "after",
BeforeSizeBytes: 12, AfterSizeBytes: 13,
}, nil
},
}
Expand All @@ -634,7 +636,7 @@ func TestFix(t *testing.T) {
if resp.Actions[0] != "goimports" {
t.Errorf("expected first action 'goimports', got %s", resp.Actions[0])
}
if !resp.GetChanged() || resp.GetWrote() || resp.GetBeforeSha256() != "before" || resp.GetAfterSha256() != "after" {
if !resp.GetChanged() || resp.GetWrote() || resp.GetBeforeSha256() != "before" || resp.GetAfterSha256() != "after" || resp.GetBeforeSizeBytes() != 12 || resp.GetAfterSizeBytes() != 13 {
t.Fatalf("gateway dropped fix evidence: %+v", resp)
}
}
Expand All @@ -657,6 +659,9 @@ func TestApplyEdit(t *testing.T) {
if resp.Strategy != "exact" {
t.Errorf("expected strategy 'exact', got %s", resp.Strategy)
}
if resp.GetBeforeSizeBytes() != uint64(len("package main\n\nold code\n")) || resp.GetAfterSizeBytes() != uint64(len("package main\n\nnew code\n")) {
t.Fatalf("gateway dropped edit sizes: %+v", resp)
}
content, err := os.ReadFile(filepath.Join(dir, "main.go"))
if err != nil {
t.Fatal(err)
Expand Down
Loading