Skip to content

[CXH-2208] fix: derive CLM hrefs for Grant/Revoke instead of requiring a profile field - #63

Open
FeliLucero1 wants to merge 31 commits into
mainfrom
clm-folder-provisioning-external-id
Open

[CXH-2208] fix: derive CLM hrefs for Grant/Revoke instead of requiring a profile field#63
FeliLucero1 wants to merge 31 commits into
mainfrom
clm-folder-provisioning-external-id

Conversation

@FeliLucero1

@FeliLucero1 FeliLucero1 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Description

  • Bug fix
  • New feature

Grant/Revoke for CLM folders/groups/members previously required a href
profile field to be present on the principal/entitlement resource — which
pebble's identity-only V3EntitlementToV2 hydration doesn't carry, so
provisioning silently failed for any resource synced that way.

This PR drops that requirement. The href to write is now resolved via
clmPreferredHref, in priority order:

  1. A real, server-issued Href already on the resource's own profile, if present
  2. A sample Href from a sibling entry already on hand (e.g. another of the
    member's current groups, another folder-security entry)
  3. client.GroupHref/MemberHref, derived from the discovered CLM base URL

Revoke compares by bare ID instead, so it no longer needs a href at all.

Also includes several rounds of hardening from automated review: empty-ID
guards on every Grant/Revoke path (principal, folder, member/group), rejecting
malformed sample hrefs (trailing-slash/collection-root, bare scheme+host, no
path segment), and consistent codes.InvalidArgument classification on all
the new validation errors.

Useful links:

@FeliLucero1 FeliLucero1 changed the title fix: carry CLM href via ExternalId instead of profile for Grant/Revoke [CXH-2208] fix: carry CLM href via ExternalId instead of profile for Grant/Revoke Aug 7, 2026
@linear-code

linear-code Bot commented Aug 7, 2026

Copy link
Copy Markdown

CXH-2208

Comment thread pkg/connector/clm_groups.go Outdated
Comment thread pkg/connector/clm_folders.go Outdated
Comment thread pkg/connector/clm_members.go Outdated
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Connector PR Review: [CXH-2208] fix: derive CLM hrefs for Grant/Revoke instead of requiring a profile field

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

Review Summary

The new commit is a two-line fix plus a doc correction, and it addresses both prior findings: clm_folders.go Grant/Revoke now propagate getAnnos from their default: principal-type branches (:272, :386), and clmIDFromHref's doc no longer contradicts itself — it now correctly states that pkg/client/clmtest can't import pkg/connector (which would cycle through pkg/connector's own test files) so the single definition lives in pkg/client. I also re-scanned the full PR diff for security and correctness: href derivation (clmHrefWithID/clmPreferredHref/clmSampleHrefsFrom), the empty-ID guards on every Grant/Revoke path, the gRPC status-code classification, the IDFromHref move to pkg/client, and the workflow-level CI concurrency group. No new blocking issues found; go.mod/go.sum are unchanged, and no resource type, entitlement slug, ID derivation, scope, or config field changed, so no docs update is owed.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • pkg/connector/helper.go:126clmHrefWithID splits on the decoded u.Path, so a sample href with a percent-encoded slash in its ID segment would be split at the wrong place; u.EscapedPath() would be exact. Robustness only, practically unreachable with GUID-like CLM IDs.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/connector/helper.go`:
- Around line 113-133: `clmHrefWithID` parses the sample href and then does
  `strings.LastIndex(u.Path, "/")` on `u.Path`, which is the percent-DECODED path. If a
  CLM object ID ever contains an encoded slash (e.g. ".../groups/a%2Fb"), `u.Path`
  becomes "/v2/acct/groups/a/b" and the split replaces only the "b" segment, producing
  ".../groups/a/<newID>" instead of ".../groups/<newID>". To make it exact, operate on
  `u.EscapedPath()` instead: take `esc := u.EscapedPath()`, apply the same trailing-slash
  and `LastIndex` checks to `esc`, build the new escaped path as
  `esc[:idx+1] + url.PathEscape(newID)`, and assign it back with `u.RawPath = newPath`
  plus `u.Path, _ = url.PathUnescape(newPath)` (or use `u.Parse`/`url.JoinPath` on the
  collection prefix). Keep the existing empty-newID, empty-path, trailing-slash, and
  no-separator rejections and the RawQuery/Fragment clearing as they are. This is a
  robustness fix, not a live bug — leave it as-is if CLM IDs are known to be
  URL-safe.

@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.

Comment thread pkg/connector/helper.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.

Comment thread pkg/client/clm_client.go Outdated
Comment thread pkg/connector/clm_folders_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.

Comment thread pkg/connector/clm_folders_test.go
Comment thread pkg/connector/clm_folders.go Outdated
Comment thread pkg/client/clm_client.go Outdated
Comment thread pkg/connector/clm_groups_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.

Comment thread pkg/connector/helper.go
Comment thread pkg/connector/clm_folders_test.go
Comment thread pkg/connector/clm_folders.go Outdated
Comment thread pkg/connector/clm_folders_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.

FeliLucero1 added a commit that referenced this pull request Aug 10, 2026
- Remove clmPreferredHref's redundant empty-string guard — clmHrefWithID
  already rejects an empty sample on its own.
- Rephrase clmPreferredHref's doc away from "no live tenant confirmed this"
  hedging into a factual statement of the write-vs-read Href asymmetry.
- Update two clm_folders_test.go comments that still named the removed
  clmGroupHrefFromResource helper as current write-side behavior.
Comment thread pkg/connector/clm_groups_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.

FeliLucero1 added a commit that referenced this pull request Aug 10, 2026
- Remove the redundant client.GroupHref/client.MemberHref calls from
  clm_folder's Revoke — clmFindGroupSecurityIndex/clmFindUserSecurityIndex
  only ever compare by clmIDFromHref, so principal.Id.Resource (already a
  bare ID) works directly without the extra ensureClmReady round trip and
  discarded error path.
- Update stale comments (clm_folders_test.go, clm_groups.go) that still
  described Grant as deriving the Href straight from the ID, or named the
  deleted clmGroupHrefFromResource/clmMemberHrefFromResource helpers.
- Split TestClmGroupBuilder_Grant_SurvivesIdentityOnlyEntitlementResource
  into two subtests so both of clmPreferredHref's branches (fallback via
  client.GroupHref, and the real-sample branch) get exercised.
- Assert the exact Href a Grant call writes in
  TestClmFolderBuilder_GrantAndRevoke_SurvivesIdentityOnlyPrincipal, not
  just the resulting AccessType.
Comment thread pkg/connector/clm_folders_test.go
Comment thread pkg/connector/clm_groups_test.go
FeliLucero1 and others added 17 commits August 19, 2026 16:42
…efWithID

clmHrefWithID had no check that newID was non-empty — an empty ID
silently produced a trailing-slash href with no ID segment at all,
which would go out in a real PatchFolderSecurity/PatchMemberGroups
request body with no link back to "the ID was empty." Confirmed
reachable in practice: List() never validates that CLM's Href field is
non-empty before deriving a resource ID from it.

clmPreferredHref also discarded every per-sample parse error before
falling back, so an unexpected Href shape (a real bug or an
unconfirmed-API-shape surprise) looked identical to the routine
"no sample available yet" case. Now logged before falling back.

Also: dedupe clmIDFromHref (pkg/connector) and idFromHref (clmtest) —
both reimplemented the identical trailing-path-segment logic — into
one shared client.IDFromHref.
…error prefixes

- Collapse GroupHref/MemberHref (identical bodies except the endpoint
  constant) into a shared hrefFor, and give MemberHref its own
  clmGetMember const instead of reusing clmPatchPutMember.
- Extract clmSampleHrefsFrom, used identically in 3 places
  (clm_folders.go x2, clm_groups.go) to build clmPreferredHref's
  sample list from a principal's profile href plus existing entries.
- Add the missing "baton-docusign: " prefix to every error wrap in
  clm_folders.go/clm_groups.go that was missing it, per this repo's
  error-handling convention (every wrapped error gets the prefix).
- Unify clmFindGroupSecurityIndex/clmFindUserSecurityIndex (identical
  loops differing only by entry type) behind one generic helper.
…preference

TestClmFolderBuilder_Grant_SurvivesIdentityOnlyPrincipal_SampleBranch
asserted against Hrefs that were byte-identical to what
clmPreferredHref's fallback derivation would also produce, so it
passed whether or not the sample branch actually ran — the same gap
already fixed in clm_groups_test.go. Added
Server.SetFolderGroupSecurityHref/SetFolderUserSecurityHref (folder
security entries carry no separate ID field to key an override off
of) to move the existing samples onto a different host first, and
build the expected Href literally instead of via clmHrefWithID.

Also trims doc comments that narrated this PR's fix history instead of
describing current behavior.
- parseIntoClmGroupResource/parseIntoClmMemberResource's doc was stale
  after the clmSampleHrefsFrom refactor: Grant does read the profile
  href off the resource now, as the preferred sample — it just isn't
  required anymore. Reworded both.
- The folder-security SampleBranch test only moved one of
  folder-contracts' two seeded group entries onto the alt host, so the
  assertion silently depended on seed order (group-legal being first).
  Moved group-finance too so it's order-independent, matching what the
  test's own comment already claimed.
- SetFolderGroupSecurityHref/SetFolderUserSecurityHref silently no-op
  on a typo'd folderID/groupID instead of failing the test, matching
  every other fixture-misuse case in this package.
Consistency follow-up: SetFolderGroupSecurityHref/SetFolderUserSecurityHref
were just changed to Fatalf on a miss, but the original SetGroupHref
still silently no-op'd.
- Rename clmGetGroup/clmGetMember to clmGroupPath/clmMemberPath —
  hrefFor never issues a request with them, it only builds a Href
  string locally, so the clmGet* naming misleadingly implied a new GET
  endpoint (and neither appeared in the package doc's endpoint list).
- clmSampleHrefsFrom appended a present-but-empty profile "href" as a
  sample (GetProfileStringValue returns ok=true for ""), which always
  fails clmHrefWithID and made the routine no-sample case look
  identical to a genuinely unexpected Href shape. Added a regression
  test, verified via mutation testing.
- Reworded clmPreferredHref's doc: it claimed the Debug log "avoids
  masking" the wrong-host risk, which isn't true if Debug is below the
  default log level in production. State what it actually achieves
  instead (distinguishable at raised verbosity, not silently discarded
  outright) rather than overclaiming production visibility.
Same reasoning as the profile-href guard: an empty entry Href fails
clmHrefWithID the same way and re-trips clmPreferredHref's
unexpected-shape log on degenerate-but-routine data, not just the
profile side. Added a regression test, verified via mutation testing.
clmHrefWithID's empty-newID guard only protects the sample path. When id is
empty and no sample href is available (or all fail to parse),
clmPreferredHref fell straight through to deriveFallback (typically
client.GroupHref/MemberHref built from that same empty id), producing the
same malformed trailing-slash href the sample-path guard exists to prevent.
Moved the check into clmPreferredHref itself, ahead of both paths, plus a
regression test.

Also fixed clm_groups.go's Grant comment: it said the href source order was
"currentGroups, else client.GroupHref," omitting that ent.Resource's own
profile href (via clmSampleHrefsFrom) is actually tried first when present —
contradicting the very next comment block a few lines down.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reword hrefFor's doc to admit the one-time CLM base-URL discovery it
can trigger via ensureClmReady, and add an explicit empty-ID guard to
Revoke covering all three principal branches — Grant already rejected
an empty ID via clmPreferredHref/clmHrefWithID, but Revoke's shortcut
of comparing principal.Id.Resource directly skipped that check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Grant's role branch used principal.Id.Resource directly as roleName
with no emptiness check, unlike the group/member branches (guarded
incidentally by clmPreferredHref) — an empty ID could overwrite an
existing empty-Item role entry or append a new one, then PATCH it to
the real CLM API uncaught. Hoists the same guard Revoke already has to
the top of Grant, and adds a regression test covering both across all
three principal kinds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same class of bug as clm_folders.go's Grant/Revoke (already guarded):
clmIDFromHref reduces an empty Href to "", so an empty groupID could
match a currentGroups entry with an empty Href — Grant would falsely
return GrantAlreadyExists before clmPreferredHref's own empty-id check
ever ran, and Revoke would silently drop that unrelated membership via
PutMemberGroups' full-replace semantics. Adds the same guard to both,
plus a regression test covering empty member ID, empty group ID, and
both.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same class of bug as the principal-ID guard already added: folderID
(ent.Resource.Id.Resource in Grant, grantObj.Entitlement.Resource.Id.Resource
in Revoke) had no emptiness check. parseIntoClmFolderResource derives a
clm_folder's ID via clmIDFromHref(folder.Href) with no non-empty
validation, so an empty Href would produce a folder resource with an
empty ID — which GetFolderFresh/PatchFolderSecurity would then build
into a URL hitting the folders collection root instead of failing
clearly. Adds the same guard shape to both, plus a regression test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ject trailing-slash sample hrefs

- All the pre-flight validation errors added this PR (clm_folders.go and
  clm_groups.go's Grant/Revoke guards, helper.go's clmHrefWithID/
  clmPreferredHref) were plain fmt.Errorf, which status.Code() reads as
  codes.Unknown. Switched to status.Errorf(codes.InvalidArgument, ...)
  so these surface as permanent bad-input failures, consistent with the
  rest of the file's error-classification style.
- clmHrefWithID silently trimmed a trailing slash off sampleHref before
  deriving, which made a collection-root-shaped sample (".../groups/")
  indistinguishable from a valid item href ending in "groups" — the
  ID-replacement logic then dropped the real collection segment,
  producing ".../<newID>" instead of ".../groups/<newID>". Now rejects
  any sample already ending in "/" outright, with a regression test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…errors

Converts the last 4 validation errors this PR's diff touches (unknown
CLM folder entitlement slug, invalid principal type in clm_folders.go
Grant/Revoke, invalid principal type in clm_groups.go Grant) from plain
fmt.Errorf to status.Errorf(codes.InvalidArgument, ...), matching the
empty-ID guards already converted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
clmHrefWithID located the ID segment to replace via strings.LastIndex
on the raw sampleHref string, even though url.Parse was already called
to validate it. A "/" inside a query string or fragment (e.g.
".../group-old?filter=a/b") would win over the real path separator,
corrupting the query instead of replacing the ID
(".../group-old?filter=a/" + newID). Now splits on the parsed URL's
Path and reassembles via url.URL, so query/fragment content can't be
mistaken for the path separator. Added a regression test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
test-groups/test-signing-groups/test-permission-profiles are
needs-chained within a single workflow run but share no concurrency
group at all, so two different branches' runs against this repo's one
live demo account can still execute concurrently -- one run's mid-cycle
Grant/Revoke corrupting another's "should be zero grants after Revoke"
assertion. Confirmed directly: this same failure just hit test-groups
here while PR #64 (stacked on this branch) ran its own, already-fixed
version of this same job at the same time. Adds a workflow-level
concurrency group so the whole three-job run queues/cancels as one
unit against the shared account.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The all-samples-failed-to-parse branch already logged before falling
back to a derived href; the more common no-samples-at-all branch (a
group/member with no other memberships yet) fell back completely
silently. Since deriveFallback's shape is unverified against a live
tenant, a silently-wrong derived href written into a PatchFolderSecurity
body would leave a grant with no observable trace of why it didn't
take effect. Caught by the automated PR reviewer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@FeliLucero1
FeliLucero1 force-pushed the clm-folder-provisioning-external-id branch from 14806b3 to 1a72aec Compare August 19, 2026 19:42
FeliLucero1 added a commit that referenced this pull request Aug 19, 2026
PR #63 and this branch independently fixed the same tautological-test
finding on TestClmFolderBuilder_Grant_SurvivesIdentityOnlyPrincipal_SampleBranch
(a byte-identical sample-derived vs. fallback-derived Href) via two
different mechanisms — Server.SetFolderGroupSecurityHref/
SetFolderUserSecurityHref (surgical Href override) vs. a full
PatchFolderSecurity re-seed — which the rebase concatenated into one
function body (duplicate sampleHost/wantHref declarations, and the
re-seed silently overriding the override). Keep the surgical version:
it preserves folder-contracts' original seed shape (both group
entries) instead of replacing it.
FeliLucero1 added a commit that referenced this pull request Aug 19, 2026
Rebasing this branch onto PR #63's latest tip silently reapplied an
older PR #64 commit's import-block patch over helper.go's newly-added
codes/status imports (no conflict was flagged since the patch context
didn't include them), breaking the build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread .github/workflows/ci.yaml
Comment on lines +15 to +17
concurrency:
group: docusign-demo-account
cancel-in-progress: false

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 group key is a constant, so every PR run and every push-to-main run shares one queue. With cancel-in-progress: false GitHub still keeps only one pending run per group — "any previously pending job or workflow in the concurrency group will be canceled" — so with 3+ runs in flight the oldest queued run is cancelled rather than eventually run, and its checks land as cancelled needing a manual re-run. That's an acceptable trade for a shared demo account, but worth noting in the comment (or scoping the group to just the sync-test steps) so the cancellations aren't read as flakes.

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.

Same accepted trade-off already discussed on this and PR #64's ci.yaml — one shared demo account, workflow-level concurrency keeps the whole 3-job run serialized as one unit at the cost of an occasional cancelled-and-rerun on a busy day. Leaving as-is.

Comment thread pkg/connector/helper.go
// what the raw-string check used to.
return "", status.Errorf(codes.InvalidArgument, "baton-docusign: cannot derive a sibling href from %q — no path separator found", sampleHref)
}
u.Path = u.Path[:idx+1] + newID

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: only u.Path is rewritten, so the sample's RawQuery and Fragment ride along into the derived href — TestClmHrefWithID pins this (.../group-old?filter=a/b.../group-new?filter=a/b). The result is a write href identifying a different object, so carrying another entry's query/fragment is never wanted; clearing them (u.RawQuery = "", u.Fragment = "") makes the derivation shape-only. Low likelihood in practice since CLM Hrefs don't appear to carry query strings, but it costs two lines to rule out.

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.

Good catch — fixed in 71407ba: clmHrefWithID now clears RawQuery and Fragment before returning, since the derived href identifies a different object than the sample's. Updated TestClmHrefWithID's regression case to match (the derived href no longer carries the sample's ?filter=a/b).

FeliLucero1 and others added 2 commits August 19, 2026 16:49
staticcheck doesn't recognize t.Fatalf as terminating the goroutine, so
it flags the found.AccessType dereference just after a "found == nil"
guard as a possible nil pointer dereference. Adding an explicit return
after each Fatalf breaks the control-flow path staticcheck otherwise
sees, the standard fix for this well-known false-positive pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Only Path was rewritten, so a sample carrying a query string or
fragment leaked it into the derived href for a different object.
Since the derived href identifies a distinct target, the sample's
query/fragment is never meaningful there -- clear both so the
derivation is shape-only. Caught by the automated PR reviewer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread pkg/connector/helper.go Outdated
Comment on lines +87 to +90
// clmIDFromHref extracts the trailing path segment from a CLM object's Href — see
// client.IDFromHref's doc. pkg/client/clmtest reimplements the same logic locally to
// avoid depending on pkg/connector, so both packages call the one shared definition in
// pkg/client instead of maintaining two copies.

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 doc contradicts itself — "pkg/client/clmtest reimplements the same logic locally" is exactly what this PR removed (clmtest's idFromHref now just calls client.IDFromHref), yet the next clause correctly says both packages call the one shared definition. The reason clmtest can't import pkg/connector is still worth stating; the "reimplements locally" part is now stale. Something like: "pkg/client/clmtest can't import pkg/connector, so the single definition lives in pkg/client and both packages delegate to it."

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.

Fixed in e395125 — reworded to state the actual reason (clmtest can't import pkg/connector) instead of the stale 'reimplements locally' claim.

Comment thread pkg/connector/clm_folders.go Outdated
}
default:
return nil, nil, fmt.Errorf("baton-docusign: invalid principal type for CLM folder security: %s", principal.Id.ResourceType)
return nil, nil, status.Errorf(codes.InvalidArgument, "baton-docusign: invalid principal type for CLM folder security: %s", principal.Id.ResourceType)

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 PR changed the group/member href-resolution error paths (lines 234, 261) to propagate getAnnos instead of nil, but the default: branch here — and the matching one in Revoke (line 386) — still returns nil, discarding the rate-limit annotations GetFolderFresh already produced. Returning getAnnos in both default branches would make the whole method consistent.

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.

Fixed in e395125 — both default branches (Grant and Revoke) now propagate getAnnos, consistent with the group/member branches.

@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.

Grant/Revoke's invalid-principal-type default branches still returned
nil annotations, discarding the rate-limit annotations GetFolderFresh
already produced -- inconsistent with the group/member branches in
this same file, which already propagate them. Also fixed
clmIDFromHref's doc comment, which claimed clmtest "reimplements the
same logic locally" when this PR made it delegate to the same shared
definition instead. Caught by the automated PR reviewer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread pkg/connector/helper.go
if u.Path == "/" || strings.HasSuffix(u.Path, "/") {
return "", status.Errorf(codes.InvalidArgument, "baton-docusign: cannot derive a sibling href from %q — sample has no ID segment (trailing slash)", sampleHref)
}
idx := strings.LastIndex(u.Path, "/")

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 (low confidence): the split uses u.Path, which is the decoded path, so a sample href whose ID segment contains an encoded slash (e.g. .../groups/a%2Fb) decodes to /v2/acct/groups/a/b and LastIndex would then replace only b, yielding .../groups/a/<newID> instead of .../groups/<newID>. u.EscapedPath() (with the result assigned back via u.RawPath/url.PathUnescape) would be exact. Practically unreachable if CLM IDs are GUID-like, so this is robustness only, not a live bug.

@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.

7 participants