Skip to content

Commit be3179d

Browse files
committed
fix(contract): 字段引用统一 lowerCamelCase——清理 proto 风格 snake_case 泄漏
按 CLAUDE.md 契约字段命名规则修正此前引入的泄漏: - schemadiff Finding.Source:input_schema/output_schema → inputSchema/outputSchema(流入 Diagnostics field + 告警消息, 为 UI/API 可见的契约暴露面;isInput 判定同步) - 文件下发错误消息:transfer_id/content_sha256 → transferId/contentSha256 (Go/JS/Python 三语言 + 测试断言) - registrationguard 位置串:input_schema.x-menu → inputSchema.x-menu (错误消息文本,非机器码;同步既有测试断言) - 机器标识码(schema_breaking_change 等)按契约豁免保留 测试:schemadiff/server/service 全绿 + SDK file push 全绿 + web 13 例 + guard PASSED
1 parent eab1514 commit be3179d

13 files changed

Lines changed: 48 additions & 48 deletions

File tree

internal/function/schemadiff/diff.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
type Finding struct {
1515
// Severity: "breaking" | "compatible"
1616
Severity string `json:"severity"`
17-
// Source: "input_schema" | "output_schema"
17+
// Source: "inputSchema" | "outputSchema"
1818
Source string `json:"source"`
1919
// Path JSON Pointer(如 "/playerId")
2020
Path string `json:"path"`
@@ -188,17 +188,17 @@ func diffRequired(source, path string, oldMap, newMap map[string]interface{}, fi
188188
}
189189

190190
// diffEnum 枚举方向性判定(审查修正):破坏方向取决于数据流向——
191-
// - input_schema:收窄 = breaking(旧调用方发被删的值会被服务端拒绝),
191+
// - inputSchema:收窄 = breaking(旧调用方发被删的值会被服务端拒绝),
192192
// 扩张 = compatible(旧调用方不受影响);
193-
// - output_schema:扩张 = breaking(消费方会见到新值),
193+
// - outputSchema:扩张 = breaking(消费方会见到新值),
194194
// 收窄 = compatible(消费方只会见到更少的值)。
195195
func diffEnum(source, path string, oldMap, newMap map[string]interface{}, findings *[]Finding) {
196196
oldEnum, okOld := oldMap["enum"].([]interface{})
197197
newEnum, okNew := newMap["enum"].([]interface{})
198198
if !okOld || !okNew {
199199
return
200200
}
201-
isInput := source == "input_schema"
201+
isInput := source == "inputSchema"
202202
oldValues := make(map[string]bool, len(oldEnum))
203203
for _, item := range oldEnum {
204204
oldValues[fmt.Sprint(item)] = true

internal/function/schemadiff/diff_test.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func findByPath(findings []Finding, path string) (Finding, bool) {
2323
func TestDiffRequiredAdded(t *testing.T) {
2424
oldRaw := mustRaw(t, `{"type":"object","properties":{"a":{"type":"string"}}}`)
2525
newRaw := mustRaw(t, `{"type":"object","properties":{"a":{"type":"string"}},"required":["a"]}`)
26-
findings := DiffSchemas("input_schema", oldRaw, newRaw)
26+
findings := DiffSchemas("inputSchema", oldRaw, newRaw)
2727
if !HasBreaking(findings) {
2828
t.Fatalf("expected breaking finding, got %+v", findings)
2929
}
@@ -36,7 +36,7 @@ func TestDiffRequiredAdded(t *testing.T) {
3636
func TestDiffPropertyRemoved(t *testing.T) {
3737
oldRaw := mustRaw(t, `{"type":"object","properties":{"a":{"type":"string"},"b":{"type":"integer"}}}`)
3838
newRaw := mustRaw(t, `{"type":"object","properties":{"a":{"type":"string"}}}`)
39-
findings := DiffSchemas("input_schema", oldRaw, newRaw)
39+
findings := DiffSchemas("inputSchema", oldRaw, newRaw)
4040
if !HasBreaking(findings) {
4141
t.Fatalf("expected breaking finding, got %+v", findings)
4242
}
@@ -49,7 +49,7 @@ func TestDiffPropertyRemoved(t *testing.T) {
4949
func TestDiffTypeChanged(t *testing.T) {
5050
oldRaw := mustRaw(t, `{"type":"object","properties":{"a":{"type":"string"}}}`)
5151
newRaw := mustRaw(t, `{"type":"object","properties":{"a":{"type":"integer"}}}`)
52-
findings := DiffSchemas("input_schema", oldRaw, newRaw)
52+
findings := DiffSchemas("inputSchema", oldRaw, newRaw)
5353
if !HasBreaking(findings) {
5454
t.Fatalf("expected breaking finding, got %+v", findings)
5555
}
@@ -65,20 +65,20 @@ func TestDiffEnumDirectional(t *testing.T) {
6565
narrowed := mustRaw(t, `{"type":"object","properties":{"level":{"type":"string","enum":["low"]}}}`)
6666
expanded := mustRaw(t, `{"type":"object","properties":{"level":{"type":"string","enum":["low","high","critical"]}}}`)
6767

68-
// input_schema:收窄 = breaking(旧调用发被删值会被拒)
69-
if findings := DiffSchemas("input_schema", oldRaw, narrowed); !HasBreaking(findings) {
68+
// inputSchema:收窄 = breaking(旧调用发被删值会被拒)
69+
if findings := DiffSchemas("inputSchema", oldRaw, narrowed); !HasBreaking(findings) {
7070
t.Fatalf("input enum narrowing should be breaking, got %+v", findings)
7171
}
72-
// input_schema:扩张 = compatible(旧调用方不受影响)
73-
if findings := DiffSchemas("input_schema", oldRaw, expanded); HasBreaking(findings) {
72+
// inputSchema:扩张 = compatible(旧调用方不受影响)
73+
if findings := DiffSchemas("inputSchema", oldRaw, expanded); HasBreaking(findings) {
7474
t.Fatalf("input enum expansion should be compatible, got %+v", findings)
7575
}
76-
// output_schema:扩张 = breaking(消费方会见到新值)
77-
if findings := DiffSchemas("output_schema", oldRaw, expanded); !HasBreaking(findings) {
76+
// outputSchema:扩张 = breaking(消费方会见到新值)
77+
if findings := DiffSchemas("outputSchema", oldRaw, expanded); !HasBreaking(findings) {
7878
t.Fatalf("output enum expansion should be breaking, got %+v", findings)
7979
}
80-
// output_schema:收窄 = compatible
81-
if findings := DiffSchemas("output_schema", oldRaw, narrowed); HasBreaking(findings) {
80+
// outputSchema:收窄 = compatible
81+
if findings := DiffSchemas("outputSchema", oldRaw, narrowed); HasBreaking(findings) {
8282
t.Fatalf("output enum narrowing should be compatible, got %+v", findings)
8383
}
8484
}
@@ -87,7 +87,7 @@ func TestDiffEnumDirectional(t *testing.T) {
8787
func TestDiffCompatibleChanges(t *testing.T) {
8888
oldRaw := mustRaw(t, `{"type":"object","properties":{"a":{"type":"string","title":"A"}},"required":["a"]}`)
8989
newRaw := mustRaw(t, `{"type":"object","properties":{"a":{"type":"string","title":"A","description":"field a"},"b":{"type":"integer"}},"required":["a"]}`)
90-
findings := DiffSchemas("input_schema", oldRaw, newRaw)
90+
findings := DiffSchemas("inputSchema", oldRaw, newRaw)
9191
if HasBreaking(findings) {
9292
t.Fatalf("expected only compatible findings, got %+v", findings)
9393
}
@@ -100,18 +100,18 @@ func TestDiffCompatibleChanges(t *testing.T) {
100100
func TestDiffStructureTypeChanged(t *testing.T) {
101101
oldRaw := mustRaw(t, `{"type":"object","properties":{"profile":{"type":"object"}}}`)
102102
newRaw := mustRaw(t, `{"type":"object","properties":{"profile":{"type":"string"}}}`)
103-
findings := DiffSchemas("input_schema", oldRaw, newRaw)
103+
findings := DiffSchemas("inputSchema", oldRaw, newRaw)
104104
if !HasBreaking(findings) {
105105
t.Fatalf("expected breaking finding, got %+v", findings)
106106
}
107107
}
108108

109109
// 首次注册(旧 schema 为空)与非法 JSON 不产生差异
110110
func TestDiffEmptyAndInvalid(t *testing.T) {
111-
if findings := DiffSchemas("input_schema", nil, mustRaw(t, `{"type":"object"}`)); len(findings) != 0 {
111+
if findings := DiffSchemas("inputSchema", nil, mustRaw(t, `{"type":"object"}`)); len(findings) != 0 {
112112
t.Fatalf("expected no findings for initial registration, got %+v", findings)
113113
}
114-
if findings := DiffSchemas("input_schema", mustRaw(t, `not-json`), mustRaw(t, `{"type":"object"}`)); len(findings) != 0 {
114+
if findings := DiffSchemas("inputSchema", mustRaw(t, `not-json`), mustRaw(t, `{"type":"object"}`)); len(findings) != 0 {
115115
t.Fatalf("expected no findings for invalid old schema, got %+v", findings)
116116
}
117117
}
@@ -120,7 +120,7 @@ func TestDiffEmptyAndInvalid(t *testing.T) {
120120
func TestDiffNestedBreaking(t *testing.T) {
121121
oldRaw := mustRaw(t, `{"type":"object","properties":{"profile":{"type":"object","properties":{"city":{"type":"string"}}}}}`)
122122
newRaw := mustRaw(t, `{"type":"object","properties":{"profile":{"type":"object","properties":{}}}}`)
123-
findings := DiffSchemas("input_schema", oldRaw, newRaw)
123+
findings := DiffSchemas("inputSchema", oldRaw, newRaw)
124124
if !HasBreaking(findings) {
125125
t.Fatalf("expected nested breaking finding, got %+v", findings)
126126
}
@@ -132,8 +132,8 @@ func TestDiffNestedBreaking(t *testing.T) {
132132
// source 区分 input/output
133133
func TestDiffSourceLabels(t *testing.T) {
134134
oldRaw := mustRaw(t, `{"type":"object","properties":{"a":{"type":"string"}}}`)
135-
findings := DiffSchemas("output_schema", oldRaw, mustRaw(t, `{"type":"object","properties":{}}`))
136-
if len(findings) != 1 || findings[0].Source != "output_schema" {
137-
t.Fatalf("expected single output_schema finding, got %+v", findings)
135+
findings := DiffSchemas("outputSchema", oldRaw, mustRaw(t, `{"type":"object","properties":{}}`))
136+
if len(findings) != 1 || findings[0].Source != "outputSchema" {
137+
t.Fatalf("expected single outputSchema finding, got %+v", findings)
138138
}
139139
}

internal/server/control_handler.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -540,9 +540,9 @@ func (s *ControlService) handleRegisterRequest(ctx context.Context, req *agentv1
540540
if !ok {
541541
continue
542542
}
543-
findings := schemadiff.DiffSchemas("input_schema", json.RawMessage(oldInput), json.RawMessage(f.GetInputSchema()))
543+
findings := schemadiff.DiffSchemas("inputSchema", json.RawMessage(oldInput), json.RawMessage(f.GetInputSchema()))
544544
findings = append(findings,
545-
schemadiff.DiffSchemas("output_schema", json.RawMessage(oldOutput), json.RawMessage(f.GetOutputSchema()))...)
545+
schemadiff.DiffSchemas("outputSchema", json.RawMessage(oldOutput), json.RawMessage(f.GetOutputSchema()))...)
546546
for _, finding := range findings {
547547
if finding.Severity != schemadiff.SeverityBreaking {
548548
continue
@@ -916,10 +916,10 @@ func descriptorPresentationField(f *agentv1.FunctionDescriptor) (string, bool) {
916916
// formily, ...). Reject the whole descriptor instead of silently
917917
// persisting a smuggled UI hint.
918918
if field, _, ok := registrationguard.ScanJSON(f.GetInputSchema()); ok {
919-
return "input_schema." + field, true
919+
return "inputSchema." + field, true
920920
}
921921
if field, _, ok := registrationguard.ScanJSON(f.GetOutputSchema()); ok {
922-
return "output_schema." + field, true
922+
return "outputSchema." + field, true
923923
}
924924
return "", false
925925
}

internal/server/control_handler_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,7 +1340,7 @@ func TestValidateAndNormalizeFunctions(t *testing.T) {
13401340
assert.Len(t, warnings, 1)
13411341
assert.Equal(t, "function_presentation_field_not_allowed", warnings[0].Code)
13421342
assert.Equal(t, "game.player.get", warnings[0].FunctionID)
1343-
assert.Contains(t, warnings[0].Message, "input_schema.x-table-columns")
1343+
assert.Contains(t, warnings[0].Message, "inputSchema.x-table-columns")
13441344
})
13451345

13461346
t.Run("presentation extension in output schema is rejected", func(t *testing.T) {
@@ -1356,7 +1356,7 @@ func TestValidateAndNormalizeFunctions(t *testing.T) {
13561356
assert.Empty(t, functions)
13571357
assert.Len(t, warnings, 1)
13581358
assert.Equal(t, "function_presentation_field_not_allowed", warnings[0].Code)
1359-
assert.Contains(t, warnings[0].Message, "output_schema.x-menu")
1359+
assert.Contains(t, warnings[0].Message, "outputSchema.x-menu")
13601360
})
13611361

13621362
t.Run("full capability contract fields pass", func(t *testing.T) {

internal/service/contract_projection_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ func TestFunctionSpecFromContract_Diagnostics(t *testing.T) {
1616
{
1717
"code": "schema_breaking_change",
1818
"severity": "warning",
19-
"message": "input_schema$/reason: 已声明的字段被删除",
20-
"field": "input_schema",
19+
"message": "inputSchema$/reason: 已声明的字段被删除",
20+
"field": "inputSchema",
2121
},
2222
}
2323
raw, err := json.Marshal(diagnostics)

internal/service/contract_service.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -775,9 +775,9 @@ func mergeSchemaDiffDiagnostics(
775775
return base, nil, false
776776
}
777777

778-
findings := schemadiff.DiffSchemas("input_schema", json.RawMessage(existing.InputSchema), json.RawMessage(contract.InputSchema))
778+
findings := schemadiff.DiffSchemas("inputSchema", json.RawMessage(existing.InputSchema), json.RawMessage(contract.InputSchema))
779779
findings = append(findings,
780-
schemadiff.DiffSchemas("output_schema", json.RawMessage(existing.OutputSchema), json.RawMessage(contract.OutputSchema))...)
780+
schemadiff.DiffSchemas("outputSchema", json.RawMessage(existing.OutputSchema), json.RawMessage(contract.OutputSchema))...)
781781
if !schemadiff.HasBreaking(findings) {
782782
return base, findings, true
783783
}

internal/service/contract_service_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ func TestContractService_RebuildContractRejectsPresentationSchema(t *testing.T)
121121
})
122122

123123
require.Error(t, err)
124-
assert.ErrorContains(t, err, `forbidden presentation field "x-menu" at input_schema.x-menu`)
124+
assert.ErrorContains(t, err, `forbidden presentation field "x-menu" at inputSchema.x-menu`)
125125
contracts, listErr := service.ListContracts(ctx, "demo-game", "development")
126126
require.NoError(t, listErr)
127127
assert.Empty(t, contracts)
@@ -1566,7 +1566,7 @@ func TestContractService_SchemaBreakingChangeWritesDiagnostics(t *testing.T) {
15661566
contract, err = service.GetContract(ctx, "demo-game", "development", "player.ban")
15671567
require.NoError(t, err)
15681568
assert.Contains(t, string(contract.Diagnostics), "schema_breaking_change")
1569-
assert.Contains(t, string(contract.Diagnostics), "input_schema$/reason")
1569+
assert.Contains(t, string(contract.Diagnostics), "inputSchema$/reason")
15701570

15711571
// 兼容性变更(新增可选字段):不产生告警
15721572
compatible := base

sdks/go/pkg/croupier/file_push.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ package croupier
1111
// hotpatch runner(备份→替换→自检→回滚)单独编排。
1212
//
1313
// wire(protobuf 兼容,手写编解码避免全 SDK 再生成):
14-
// FilePushRequest { 1: transfer_id, 2: file_name, 3: content_sha256(hex), 4: data }
15-
// FilePushResponse { 1: transfer_id, 2: ok, 3: stored_path, 4: error }
14+
// FilePushRequest { 1: transferId, 2: fileName, 3: contentSha256(hex), 4: data }
15+
// FilePushResponse { 1: transferId, 2: ok, 3: storedPath, 4: error }
1616

1717
import (
1818
"crypto/sha256"
@@ -150,7 +150,7 @@ func (m *TCPManager) validateFilePush(req *filePushRequest) error {
150150
return errors.New("file transfer is disabled on this provider")
151151
}
152152
if strings.TrimSpace(req.transferID) == "" {
153-
return errors.New("transfer_id is required")
153+
return errors.New("transferId is required")
154154
}
155155
if _, err := safeStagingPath(m.fileStagingDir(), req.fileName); err != nil {
156156
return err
@@ -166,7 +166,7 @@ func (m *TCPManager) validateFilePush(req *filePushRequest) error {
166166
return fmt.Errorf("file size %d exceeds max %d", len(req.data), maxSize)
167167
}
168168
if strings.TrimSpace(req.contentSha256) == "" {
169-
return errors.New("content_sha256 is required")
169+
return errors.New("contentSha256 is required")
170170
}
171171
sum := sha256.Sum256(req.data)
172172
if !strings.EqualFold(hex.EncodeToString(sum[:]), strings.TrimSpace(req.contentSha256)) {
@@ -227,7 +227,7 @@ type filePushResponse struct {
227227
}
228228

229229
// encodeFilePushResponse 手写 protobuf 兼容编码:
230-
// { 1: transfer_id(string), 2: ok(bool), 3: stored_path(string), 4: error(string) }。
230+
// { 1: transferId(string), 2: ok(bool), 3: storedPath(string), 4: error(string) }。
231231
func encodeFilePushResponse(resp filePushResponse) []byte {
232232
var out []byte
233233
if resp.transferID != "" {

sdks/go/pkg/croupier/file_push_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,14 @@ func TestFilePushValidation(t *testing.T) {
8484
errSubstr string
8585
}{
8686
{"disabled flag", false, "t-1", "patch.lua", validSha, validFile, "file transfer is disabled"},
87-
{"empty transfer_id", true, "", "patch.lua", validSha, validFile, "transfer_id is required"},
87+
{"empty transfer_id", true, "", "patch.lua", validSha, validFile, "transferId is required"},
8888
{"path traversal ../", true, "t-1", "../evil.lua", validSha, validFile, "bare basename"},
8989
{"absolute path", true, "t-1", "/etc/evil.lua", validSha, validFile, "bare basename"},
9090
{"subdir escape", true, "t-1", "sub/dir/evil.lua", validSha, validFile, "bare basename"},
9191
{"dotdot name rejected", true, "t-1", "..evil", validSha, validFile, "bare basename"},
9292
{"empty payload", true, "t-1", "patch.lua", validSha, nil, "file payload is empty"},
9393
{"oversize", true, "t-1", "patch.lua", validSha, make([]byte, 2048), "exceeds max"},
94-
{"missing sha", true, "t-1", "patch.lua", "", validFile, "content_sha256 is required"},
94+
{"missing sha", true, "t-1", "patch.lua", "", validFile, "contentSha256 is required"},
9595
{"checksum mismatch", true, "t-1", "patch.lua", strings.Repeat("ab", 32), validFile, "checksum mismatch"},
9696
}
9797

sdks/js/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,7 +1466,7 @@ export class BasicClient implements CroupierClient {
14661466
const contentSha256 = (decoded.contentSha256 ?? "").trim();
14671467
const data = Buffer.from(decoded.data ?? new Uint8Array());
14681468

1469-
if (!transferId) return fail("transfer_id is required");
1469+
if (!transferId) return fail("transferId is required");
14701470
if (!fileName || fileName.includes("/") || fileName.includes("\\") || fileName.includes("..")) {
14711471
return fail(`file name must be a bare basename: "${fileName}"`);
14721472
}
@@ -1475,7 +1475,7 @@ export class BasicClient implements CroupierClient {
14751475
if (data.length > maxSize) {
14761476
return fail(`file size ${data.length} exceeds max ${maxSize}`);
14771477
}
1478-
if (!contentSha256) return fail("content_sha256 is required");
1478+
if (!contentSha256) return fail("contentSha256 is required");
14791479
const actualSha = createHash("sha256").update(data).digest("hex");
14801480
if (actualSha.toLowerCase() !== contentSha256.toLowerCase()) {
14811481
return fail("checksum mismatch");

0 commit comments

Comments
 (0)