Skip to content

Add typed credential issuance inputs - #1120

Open
c1-squire-dev[bot] wants to merge 35 commits into
mainfrom
highb/IGA-4093/typed-credential-inputs
Open

c1-squire-dev[bot] wants to merge 35 commits into
mainfrom
highb/IGA-4093/typed-credential-inputs

Conversation

@c1-squire-dev

@c1-squire-dev c1-squire-dev Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Squire (openai/gpt-5.6-sol):

Summary

  • add a typed request schema to credential issuer capability descriptors
  • carry structured request data through credential issuance requests
  • validate schema declarations and request values in connectorbuilder before provider mutation
  • export validation helpers for host applications that narrow or resolve connector schemas

This is the Baton SDK half of IGA-4093.

Verification

  • GOTOOLCHAIN=go1.25.2 go test -race ./pkg/field ./pkg/connectorbuilder ./pkg/tasks/c1api
  • GOTOOLCHAIN=go1.25.2 go test ./... (exit 0, no failing packages)
  • GOTOOLCHAIN=go1.25.2 golangci-lint run --timeout=10m (0 issues)
  • buf lint
  • buf breaking --against '.git#tag=v0.26.0'
  • make protogen (clean tree afterward)

The compatibility harness commands still fail under the default Go 1.27
toolchain: they recursively build the v0.26.0 baseline, whose
cockroachdb/swiss dependency excludes Go 1.27. make lint under the
default toolchain hits the same class of limit (the installed
golangci-lint 2.9.0 binary is built with Go 1.26 and panics on Go 1.27
export data). Both reproduce without these changes; the equivalent full
suite and full-repo lint above pass under the matching GOTOOLCHAIN.

highb and others added 7 commits September 2, 2026 23:15
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
@linear-code

linear-code Bot commented Sep 2, 2026

Copy link
Copy Markdown

IGA-4093

if !ok || math.IsNaN(kind.NumberValue) || math.IsInf(kind.NumberValue, 0) || math.Trunc(kind.NumberValue) != kind.NumberValue || kind.NumberValue < -maxSafeJSONInteger || kind.NumberValue > maxSafeJSONInteger {
return fmt.Errorf("request data field %q must be an integer", name)
}
if err := field.ValidateIntRules(schemaField.GetIntField().GetRules(), int(kind.NumberValue), name); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Bug: The pkg/field validators were written for config parsing, where a zero value means "unset" and rules are skipped unless validate_empty is set (pkg/field/validation.go:31, :141, :263). Reusing them for explicitly submitted request values means a caller can bypass every declared rule by sending the zero value: {"ttl_seconds": 0} passes Gte: 60, Lte: 3600, {"region": ""} passes the pattern rule, and a non-required string-slice field accepts [] despite min_items. Set validate_empty on the rules before delegating (or apply the comparisons directly here) so a present-but-zero value is validated rather than skipped; note this also makes presence semantics inconsistent, since present[name] counts "" as present for constraints while the rule validators treat it as absent. Confidence: high.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Squire (openai/gpt-5.6-sol): Addressed in 23ce127. Request validation clones rules with validate_empty enabled, including repeated item rules, and covers zero integers plus empty strings, lists, and maps.

google.protobuf.Timestamp expires_at = 5;
// Values for the selected descriptor's request_schema. The SDK validates
// these before invoking the connector implementation.
google.protobuf.Struct request_data = 6;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Bug: IssueCredentialRequest.request_data has no counterpart on Task.IssueCredentialTask (proto/c1/connectorapi/baton/v1/baton.proto:131), and pkg/tasks/c1api/issue_credential.go:41 — the only in-repo producer of IssueCredentialRequest — does not populate it. On the c1 task path typed inputs are therefore always dropped, so any connector that declares a request_schema field with is_required: true will fail every hosted issuance with request data field "x" is required. Add request_data to IssueCredentialTask and forward t.GetRequestData() in the handler; if that is intentionally deferred, say so in the PR so connectors do not declare required typed fields yet. Confidence: high.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Squire (openai/gpt-5.6-sol): Addressed in 23ce127 and 54021bd. IssueCredentialTask now carries request_data, the hosted task handler forwards it, and the forwarding test compares the exact struct.

}
}
present := make(map[string]bool, len(values))
for name, schemaField := range fields {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: This iterates the fields map, so when a request violates more than one field the returned error is chosen by Go's randomized map order. That makes the host-visible InvalidArgument message vary between identical calls and weakens the plan's O1 "stable error message" oracle. Iterating schema.GetFields() in declaration order (looking values up by name) makes the first reported failure deterministic. Confidence: high.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Squire (openai/gpt-5.6-sol): Addressed in 23ce127. Known fields now validate in schema declaration order; unknown names are sorted before reporting.

if !ok {
return fmt.Errorf("request data field %q must be a string", name)
}
if err := field.ValidateStringRules(schemaField.GetStringField().GetRules(), kind.StringValue, name); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: field.ValidateStringRules embeds the rejected value in its message (value must match pattern %s but got '%s', and likewise for in/not_in/eq), and that error is returned to the host as InvalidArgument in credentials.go:218 and recorded via RecordTaskFailure. c1.config.v1.Field carries is_secret, so a connector declaring a secret typed input would have submitted secret material echoed into gRPC errors and task-failure records. Consider redacting the value for fields with is_secret set (or omitting values from request-data validation errors entirely). Confidence: medium.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Squire (openai/gpt-5.6-sol): Addressed in 23ce127. Credential request schemas now reject is_secret fields at publication/validation, and the proto documents that request_data is observable host task data.

Comment thread pkg/connectorbuilder/credential_issue_validation.go Outdated
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

General PR Review: Add typed credential issuance inputs

Blocking Issues: 0 | Suggestions: 1 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 01296f45de4c.
Review mode: incremental since 353c7d48
View review run

Review Summary

The new commits repin docs/verification/typed-credential-inputs/evidence.md to 935cf7e1 and rework the division-guard probe in TestCredentialIssueFieldMinSizeListSaturates, which addresses the prior finding's arithmetic error (the per-item contribution is now correctly stated as 4, and 16380/16381 are the real fit/saturate boundary). The full PR diff was scanned for security and correctness: the proto changes are additive-only new field numbers (request_schema = 11, request_data = 6, request_data = 5) with matching regenerated pb/ output, CredentialIssueInput.RequestData is an additive struct field, ValidateIntRules keeps its signature by delegating to the new ValidateInt64Rules, go.mod/go.sum are untouched, and request data is bounded (64 fields, 64 collection items, 64 KiB via proto.Size) and validated before any provider mutation. One suggestion remains on the reworked test: the probe still misses the arm it claims to pin.

Risk triage per docs/BUG_CATCHING.md §2 — silent: yes (a wrong lower bound rejects or admits schemas quietly); durable: yes (proto wire fields read by future SDK versions); uncontrolled dimensions: no version-pair dependence, additive fields only; consumer distance: high (downstream connectors). Verdict HIGH on the wire surface, but the PR carries the instruments: TestCredentialIssueRequestFieldMinSizeMatchesProtoSize is a differential oracle against proto.Size, and credential_issue_request_data_test.go is a permutation table over schema and request shapes. buf breaking against v0.26.0 is reported clean in the PR body.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • pkg/connectorbuilder/credential_issue_request_data_test.go:1004-1009 — the 16381 probe saturates through the entry-framing add, not the b > limit/a arm it claims to pin; limit/a is 16384 so the division guard first fires at 16385 (high confidence).
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/connectorbuilder/credential_issue_request_data_test.go`:
- Around line 1004-1009: The comment claims MinItems=16381 saturates via the
  `b > limit/uint64(a)` arm of credentialIssueSaturatingMul, but it does not.
  With empty items the per-item contribution a is 4 and
  credentialIssueRequestSizeLimit is 65537, so limit/a is 16384 and the
  division guard first triggers at 16385. For 16381 the multiply returns
  65524 unsaturated and the cap+1 sentinel comes from the outer
  credentialIssueSaturatingAdd inside credentialIssueStructEntryMinSize, so
  the division-bound guard is still not covered by any test. Change the probe
  to proto.Uint64(16385) and reword the comment to state that limit/a is
  16384 and 16385 is the first count that saturates via the b > limit/a arm.
  Leave the 16380 entry in the second loop as-is; it is correctly the largest
  empty-item count that stays under the sentinel.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking issues found — see review comments.

highb and others added 2 commits September 3, 2026 16:14
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
}
continue
}
present[name] = true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: presence and emptiness use different notions here, so constraints can be satisfied by values that requiredness rejects. present[name] is set for any non-null key, while line 288 treats ""/[]/{} as absent for is_required. Concretely: fields a,b with no rules and AT_LEAST_ONE(a,b) — submitting {"a": ""} passes the constraint while giving the connector no usable value; conversely a host that always serializes zero values (global: false) trips MUTUALLY_EXCLUSIVE spuriously (that's exactly the "constraint" table case at credential_issue_request_data_test.go:144). Consider computing present[name] as !credentialIssueRequestValueIsEmpty(value) so both checks agree, and add table cases for empty-value-vs-constraint. (confidence: high on the behavior, medium on which semantics you want)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Squire (openai/gpt-5.6-sol): Addressed in a9265e0. Constraint presence now uses the same empty-value predicate as requiredness, with empty string/list/map regression coverage.

if constraint == nil {
return fmt.Errorf("request schema constraint is required")
}
if constraint.GetKind() == config.ConstraintKind_CONSTRAINT_KIND_UNSPECIFIED {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: schema validation only rejects CONSTRAINT_KIND_UNSPECIFIED, but validateCredentialIssueConstraint has a default: that returns "unknown request schema constraint kind". A schema carrying a kind this build doesn't know (a newer connector's descriptor validated by an older host via the exported ValidateCredentialIssueRequestSchema) therefore passes publication and then fails every issuance request instead of failing fast. Reject unrecognized ConstraintKind values here so the failure surfaces at capability validation rather than per-request. (confidence: high)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Squire (openai/gpt-5.6-sol): Addressed in a9265e0. Schema validation now allow-lists every supported constraint kind and rejects unknown enum values before request validation.

Comment on lines +328 to +336
minInt, maxInt := -maxSafeJSONInteger, maxSafeJSONInteger
if strconv.IntSize == 32 {
minInt, maxInt = float64(-1<<31), float64(1<<31-1)
}
if !ok || math.IsNaN(kind.NumberValue) || math.IsInf(kind.NumberValue, 0) || math.Trunc(kind.NumberValue) != kind.NumberValue || kind.NumberValue < -maxSafeJSONInteger || kind.NumberValue > maxSafeJSONInteger || kind.NumberValue < minInt || kind.NumberValue > maxInt {
return fmt.Errorf("request data field %q must be an integer", name)
}
rules := cloneIntRulesForRequest(schemaField.GetIntField().GetRules())
if err := field.ValidateIntRules(rules, int(kind.NumberValue), name); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: the 32-bit clamp removes the implementation-defined conversion, but two things remain. (1) The accepted range is now architecture-dependent in a validator whose doc comment says host and connector share it "so host and connector validation cannot drift" — ttl_seconds: 3e9 is accepted on amd64 and rejected on a 32-bit build of the same schema. (2) The rejection message is wrong for that case: 3e9 is an integer, it's just outside the platform int. Prefer a distinct message (e.g. must be within the supported integer range) and, if you want arch-independence, range-check against ±(2^53-1) only and pass an int64 down. Relatedly, ValidateCredentialIssueRequestSchema (line 183) doesn't reject Int64Rules bounds outside ±(2^53-1), so Gte: 1<<60 publishes a silently unsatisfiable field. (confidence: high)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Squire (openai/gpt-5.6-sol): Addressed in a9265e0. Validation stays architecture-independent by evaluating int64 directly, reports out-of-range values distinctly, and rejects schema rule bounds outside the JSON-safe range.

Comment thread pkg/connectorbuilder/credential_issue_request_data_test.go

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Comment on lines +404 to +433
func validateCredentialIssueIntRules(rules *config.Int64Rules, value int64, name string) error {
if rules == nil {
return nil
}
if rules.GetIsRequired() && value == 0 {
return fmt.Errorf("request data field %q is required", name)
}
if rules.HasEq() && value != rules.GetEq() {
return fmt.Errorf("request data field %q must equal %d", name, rules.GetEq())
}
if rules.HasLt() && value >= rules.GetLt() {
return fmt.Errorf("request data field %q must be less than %d", name, rules.GetLt())
}
if rules.HasLte() && value > rules.GetLte() {
return fmt.Errorf("request data field %q must be less than or equal to %d", name, rules.GetLte())
}
if rules.HasGt() && value <= rules.GetGt() {
return fmt.Errorf("request data field %q must be greater than %d", name, rules.GetGt())
}
if rules.HasGte() && value < rules.GetGte() {
return fmt.Errorf("request data field %q must be greater than or equal to %d", name, rules.GetGte())
}
if len(rules.GetIn()) > 0 && !slices.Contains(rules.GetIn(), value) {
return fmt.Errorf("request data field %q must be one of %v", name, rules.GetIn())
}
if slices.Contains(rules.GetNotIn(), value) {
return fmt.Errorf("request data field %q contains a disallowed value", name)
}
return nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion (high confidence): this hand-rolls the Int64Rules evaluation that field.ValidateIntRules already implements, while the string/bool/slice/map arms of validateCredentialIssueRequestValue still delegate to pkg/field. The file's own doc comment says host and connector validation "cannot drift", but a rule added to Int64Rules later will be enforced for config fields and silently ignored here. Error wording also now diverges (request data field "ttl" must be greater than or equal to 60 vs. the string arm's field ttl: value must match pattern ...). Consider adding an int64-taking variant in pkg/field (the only reason to fork was ValidateIntRules' platform-dependent int parameter) and calling it from both places.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Squire (openai/gpt-5.6-sol): Addressed in 04e19b5. pkg/field now exposes ValidateInt64Rules; ValidateIntRules delegates to it, and credential request validation uses the shared int64 implementation.

}
continue
}
present[name] = !credentialIssueRequestValueIsEmpty(value)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion (medium confidence): the presence rule is now type-asymmetric. credentialIssueRequestValueIsEmpty only treats empty string/list/object as absent, so an explicitly-submitted 0 or false still counts as present for constraints — while validateCredentialIssueIntRules (line 408) treats 0 as the absent/zero value for Int64Rules.is_required. So {"ttl_seconds": 0, "permanent": true} trips MUTUALLY_EXCLUSIVE, and AT_LEAST_ONE is satisfied by 0/false but not by "". Either fold number-0/bool-false into the same rule or document that presence is "explicitly submitted, non-empty for collections/strings only" — the host has to reimplement this rule to narrow schemas consistently, so it should be stated rather than inferred.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Squire (openai/gpt-5.6-sol): Addressed in 04e19b5 by documenting the intentional config-field semantics at the presence calculation: empty strings/collections are absent, while explicitly submitted 0 and false are present. Int64Rules.is_required retains its established zero-value behavior.

Comment on lines +218 to +225

t.Run("rejects integer rules outside the JSON-safe range", func(t *testing.T) {
schema := v2.CredentialIssueRequestSchema_builder{Fields: []*config.Field{
config.Field_builder{Name: "ttl", IntField: config.IntField_builder{Rules: config.Int64Rules_builder{
Gte: proto.Int64(1 << 60),
}.Build()}.Build()}.Build(),
}}.Build()
require.ErrorContains(t, ValidateCredentialIssueRequestSchema(schema), "outside the supported JSON integer range")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion (high confidence): the new int validator is only exercised through Gte. Nothing covers validateCredentialIssueIntRules' eq/lt/lte/gt/in/not_in/is_required branches, validateCredentialIssueIntRuleBounds' in/not_in loop, or the new request-path message must be within the supported integer range (e.g. ttl_seconds: 1e18). Since this replaced the shared field.ValidateIntRules with a fresh implementation, a table over {rule kind} × {below, boundary, above} plus one out-of-range data case would pin it; TestValidateCredentialIssueRequestConstraintTreatsEmptyValuesAsAbsent similarly covers 3 of 5 field kinds and could add number/bool rows.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Squire (openai/gpt-5.6-sol): Addressed in 04e19b5. The shared pkg/field suite already covers every Int64Rules branch; I added direct int64-width coverage, request-path JSON-range coverage, and schema-bound coverage for scalar, in, and not_in rules.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Comment thread pkg/connectorbuilder/credential_issue_validation.go
Comment on lines +288 to +295
value, ok := values[name]
_, isNull := value.GetKind().(*structpb.Value_NullValue)
if !ok || value == nil || value.GetKind() == nil || isNull {
if schemaField.GetIsRequired() {
return fmt.Errorf("request data field %q is required", name)
}
continue
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion (medium confidence): rules-level is_required is only reachable when the key is present. An absent/null field continues here after checking Field.is_required, so validateCredentialIssueRequestValue — and with it Int64Rules.is_required / StringRules.is_required / StringMapRules.is_required — never runs. A connector declaring IntField{Rules:{IsRequired:true}} without Field.is_required gets enforcement for {"ttl":0} but silently none when ttl is omitted, and ValidateCredentialIssueRequestSchema does not warn about the mismatch. Either treat a rules-level is_required as implying field requiredness, or reject the combination at schema validation so the trap surfaces at capability publication.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Squire (openai/gpt-5.6-sol): Addressed by treating rules-level is_required as field requiredness for every input type whose rules expose it. Missing/null and present-but-empty checks now use the combined requirement, with a regression test for Int64Rules.is_required without Field.is_required.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

highb and others added 2 commits September 3, 2026 20:20
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Comment thread pkg/connectorbuilder/credential_issue_validation.go
Comment thread pkg/connectorbuilder/credential_issue_request_data_test.go

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

highb and others added 3 commits September 4, 2026 21:15
Secondary field names are only read by DEPENDENT_ON, but schema
publication accepted them on every constraint kind, silently ignoring
them at request evaluation. Reject nonempty secondary_field_names unless
the kind is DEPENDENT_ON, and document the restriction on
CredentialIssueRequestSchema.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Comment thread pkg/connectorbuilder/credential_issue_validation.go

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

highb and others added 2 commits September 4, 2026 22:56
Reject DEPENDENT_ON constraints whose field_names and
secondary_field_names overlap, matching the public FieldsDependentOn
DSL: a self-satisfied dependency is a malformed schema, and the request
evaluator would otherwise count one submitted field on both sides.

Reject schemas whose unconditionally required string and string-list
fields provably exceed the 65536-byte request-data cap at publication.
The bound is a conservative saturating protobuf lower bound (cap+1
sentinel, clamped uint64 rules, full per-element Value framing,
required-list floor of max(1, min_items)) computed from declared
Len/MinLen/MinItems so every satisfying request would previously fail
request validation anyway. Optional fields contribute zero and
cross-constraint branches are not summed; no solver.

Pin the existing MaxUint64 pre-conversion size guard with direct
regression cases for 65537 and math.MaxUint64, scalar and list item.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Append CO-4 (DEPENDENT_ON lists must be disjoint) and CO-5
(required-field aggregate feasibility at publication) to the frozen
verification plan, and refresh the evidence table and command log to
revision 8232332 with the actual gate results from this run.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Comment thread pkg/connectorbuilder/credential_issue_request_data_test.go Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

Add the reviewer's remaining regressions: the required-list MinItems=0
floor keeps a computed bound matching the [""] minimal fixture exactly,
and a cap-adjacent 64x1020-byte list (wiring 65679 bytes) proves the
aggregate check rejects a schema the pre-fix framing arithmetic admitted.

Drop the version-fragile //nolint:gosec directive: golangci-lint 2.12.2
(the CI pin) does not flag the bounded conversion, so the directive
tripped nolintlint as unused in CI.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Comment thread pkg/connectorbuilder/credential_issue_request_data_test.go
Comment thread pkg/connectorbuilder/credential_issue_request_data_test.go Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

highb and others added 2 commits September 4, 2026 23:50
The MaxUint64 schema subtest exits in the pre-existing per-field
rule-bound guard, so it proves publication rejection but not the new
saturating arithmetic. Add TestCredentialIssueFieldMinSizeSaturates,
which calls credentialIssueClampLength, credentialIssueMinStringLength,
and credentialIssueRequestFieldMinSize directly with MaxUint64 rules and
asserts the cap+1 sentinel, and relabel the schema subtest to say what
it actually exercises.

Move the 64x1020 cap-adjacent list into
TestCredentialIssueSchemaListAggregateBoundary, which owns the
64-item cap crossing, keeping the MinItems-zero-floor test about the
zero floor alone.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Comment thread pkg/connectorbuilder/credential_issue_request_data_test.go

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

The validator caps MinItems at 64 before the list lower-bound helper
runs, so credentialIssueSaturatingMul's huge-operand guards are
unreachable through schema validation. Pin them with a direct table:
MaxUint64 item count, MaxUint64 item bytes, both combined, and a count
just above the sentinel, each asserting the cap+1 result, plus a finite
case confirming the guards do not corrupt below-cap sizes. No giant
lists or strings are allocated.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Comment thread pkg/connectorbuilder/credential_issue_request_data_test.go Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

highb and others added 2 commits September 5, 2026 00:50
The empty-item per-element contribution is
tag1 + SizeBytes(tag3 + SizeBytes(0)) = 4 bytes, so the first
saturating MinItems count is 16381 (16380 still fits once entry framing
is counted), not the 32769 previously claimed. Pin both sides of the
real boundary so the division-bound guard's trip point is exact.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Comment on lines +1004 to +1009
// The division-bound guard specifically: with empty items each
// element contributes tag1 + SizeBytes(tag3 + SizeBytes(0)) = 4, so
// the first saturating count is 16381 (16380 items still fit: the
// 16380x4 payload plus entry framing stays under the sentinel). This
// count saturates via b > limit/a rather than the b >= limit arm.
config.RepeatedStringRules_builder{MinItems: proto.Uint64(16381)}.Build(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: the per-item contribution of 4 is now right, but 16381 still does not reach the b > limit/a arm. With a = 4 and limit = credentialIssueRequestSizeLimit = 65537, limit/a is 16384, so the division guard first fires at 16385; credentialIssueSaturatingMul(4, 16381) returns 65524 unsaturated and the sentinel comes from the outer credentialIssueSaturatingAdd in credentialIssueStructEntryMinSize. The division-bound guard the comment says it pins is still uncovered — 16385 exercises it.

Suggested change
// The division-bound guard specifically: with empty items each
// element contributes tag1 + SizeBytes(tag3 + SizeBytes(0)) = 4, so
// the first saturating count is 16381 (16380 items still fit: the
// 16380x4 payload plus entry framing stays under the sentinel). This
// count saturates via b > limit/a rather than the b >= limit arm.
config.RepeatedStringRules_builder{MinItems: proto.Uint64(16381)}.Build(),
// The division-bound guard specifically: with empty items each
// element contributes tag1 + SizeBytes(tag3 + SizeBytes(0)) = 4, so
// limit/a is 16384 and 16385 is the first count that saturates via
// the b > limit/a arm rather than b >= limit. (Counts from 16381 up
// also saturate, but only later, in the entry-framing add.)
config.RepeatedStringRules_builder{MinItems: proto.Uint64(16385)}.Build(),

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant