fix(dotc1z): drop malformed grants (nil principal/entitlement) at write time#917
fix(dotc1z): drop malformed grants (nil principal/entitlement) at write time#917arreyder wants to merge 2 commits into
Conversation
…te time
A grant's principal and entitlement are required by the proto contract
(grant.proto marks both (validate.rules).message{required:true}), and every
other layer already treats them as required: NewGrant() panics on a nil
principal, and the grant task handler rejects malformed grants as
non-retryable. The local write path was the lone gap.
baseGrantRecord extracts the principal/entitlement identity columns straight
off the live proto using nil-safe opaque getters, so a grant with a nil
principal or entitlement was not rejected and did not panic — it was silently
persisted with empty principal_resource_id / entitlement_id columns. That's
unqueryable junk and worse than a loud failure because nothing surfaced it.
Drop such grants in upsertGrantsInternal (covering both PutGrants and
StoreExpandedGrants), logging the grant id and incrementing a new
c1z_grant_malformed_dropped_total counter. We drop-and-continue rather than
failing the batch so one buggy connector grant can't abort an entire sync's
grant write; the metric + log make the bad data visible.
Verified pre-fix: PutGrants(nilPrincipal, good, nilEntitlement) persisted all
three rows (two with empty identity columns); post-fix only the well-formed
grant is written.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
General PR Review: fix(dotc1z): drop malformed grants (nil principal/entitlement) at write timeBlocking Issues: 0 | Suggestions: 1 | Threads Resolved: 0 Review SummaryThe new commits broaden the malformed-grant check from nil-only to nil-or-empty, extract it into the Security IssuesNone found. Correctness IssuesNone found. Suggestions
Prompt for AI agents |
…ging Follow-up from a multi-agent code review of the original drop. Two substantive additions: 1. Broaden the check (grantHasValidIdentity): the nil-only test let through grants with a non-nil but EMPTY identity — e.g. a principal with an empty ResourceId, or an empty entitlement/grant id — which baseGrantRecord then persisted as the same unqueryable empty-column junk the drop exists to prevent. An empty grant id additionally collides on the (external_id, sync_id) unique index. Now rejects nil OR empty principal resource type/id, entitlement id, and grant id. 2. Cap per-batch drop logging to the first 10 grants plus one aggregate line, so a broken connector emitting a run of malformed grants can't flood the logs. The exact count remains on c1z_grant_malformed_dropped_total. Tests: parameterized the drop test over full-blob + slim writers, added a StoreExpandedGrants (PreserveExpansion) drop test, added empty-identity fixtures, and a direct grantHasValidIdentity boundary test. Fixed the all-valid fast-path test to assert slice identity (require.Same) rather than just contents. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update — review-driven additions (626ff10)Ran a multi-agent review (bugs / perf / test) over the diff. Core logic came back clean (fast-path correctness, replace-mode has no stale-row risk, nil-element safety, no caller depends on written count). Two substantive additions came out of it:
Test additions: parameterized the drop test over full-blob and slim writers, added a Consciously deferred: asserting the The drop-and-continue vs. hard-fail policy question for @ggreer is unchanged. |
|
I think we should reject these and error out when the connector surfaces them to the SDK? @ggreer thoughts? |
Summary
Drop malformed grants (nil principal or nil entitlement) at the c1z write path instead of silently persisting them with empty identity columns.
Fixes #916.
Problem
baseGrantRecordextracts a grant's principal/entitlement identity columns straight off the live proto using nil-safe opaque getters. A grant with a nil principal or entitlement therefore does not panic and is not rejected — it's written withprincipal_resource_id = ""/entitlement_id = ""(empty satisfies thenot nullconstraint). The result is an unqueryable junk row that nothing surfaces.Every other layer already treats these fields as required:
grant.protomarksprincipalandentitlement(validate.rules).message = {required: true}NewGrant()panics on a nil principalpkg/tasks/c1api/grant.go:35) rejects nil principal/entitlement as a non-retryable "malformed grant task"The local write path was the only place that let them through. Surfaced during review of #863.
Fix
upsertGrantsInternal(which backs bothPutGrantsandStoreExpandedGrants) now runsdropMalformedGrantsfirst:c1z_grant_malformed_dropped_totalmetricPolicy choice — drop-and-continue vs. hard-fail. This PR drops-and-continues so one buggy connector grant can't abort an entire sync's grant write, matching the graceful-degradation already used in grant expansion. The strict alternative (fail the batch, matching the task handler) is a one-line change. @ggreer — flagging for your call; happy to flip it.
Test plan
TestPutGrants_DropsMalformedGrants— interleavednilPrincipal, good, nilEntitlementbatch → only the well-formed grant persistsTestPutGrants_AllMalformedIsNoop— all-malformed batch writes nothing, no errorTestDropMalformedGrants_AllValidReturnsSameSlice— fast path returns input untouchedgo test -short ./pkg/dotc1z/...— passgolangci-lint run pkg/dotc1z/...— 0 issues🤖 Generated with Claude Code