Skip to content

fix(cloud): preserve literal project identities (#1583) - #1590

Merged
dnlrsls merged 3 commits into
Gentleman-Programming:mainfrom
danielgap:fix/1583-percent-decoding
Oct 1, 2026
Merged

dnlrsls merged 3 commits into
Gentleman-Programming:mainfrom
danielgap:fix/1583-percent-decoding

Conversation

@danielgap

@danielgap danielgap commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Maintainer correction

Closes #1583

The correction in dd7fd7d1 supersedes the original remote-decoding approach below. The original author commit remains in history; this was a normal fast-forward update, not a rewrite.

  • Preserve existing detected/stored project identities, including encoded names and histories without a Git binding. No automatic migration or merge.
  • Add --literal-project for cloud enrollment, unenrollment, and explicit cloud sync. Default single URL decoding remains unchanged.
  • Cover literal local paths, encoded separators, historical session/observation ownership, parsing/help, enrollment isolation, and real loopback HTTP project/target isolation.

Verification for the corrected candidate

  • Observed RED then GREEN for the compatibility regressions.
  • go test ./internal/project -count=1 -timeout=60s: passed (14.267s).
  • Focused cmd/engram literal, default-decoding, parser, help, and cloud-status regressions: passed (3.995s).
  • Independent read-only verification: PASS, no blocking findings.
  • Native four-lens review: approved and acknowledged for the dd7fd7d committed PR slice. The author added test-only errcheck follow-up a6af4ea afterward; that different candidate is not covered by the dd7fd7d receipt.
  • gofmt and git diff --check: clean.
  • Current head a6af4ea: all six required GitHub checks pass, including full Unit Tests, E2E Tests, and Plugin Tests. Optional Lint and CodeRabbit also pass. The earlier local full CLI-package timeout was not retried; current-head CI provides broad test evidence.
  • No production cloud export/import was exercised. Literal transport/target isolation was exercised through a local HTTP test server.

Overall PR diff at a6af4ea: 421 authored changed lines. Maintainer explicitly authorized an exception up to 440; size:exception and type:bug are applied.

Correction AI assistance: Pi coding agent authored the correction and tests under human-selected conservative design, size-exception, and push authorization. An independent verifier inspected the candidate and provider-owned native review reviewed immutable trees.

Original submission, retained for provenance (applies to initial commit 60c7d84 only)

🔗 Linked Issue

Closes #1583


🏷️ PR Type

  • type:bug — Bug fix

Label request: contributor is pull-only and cannot apply type:bug; requesting maintainer application.


📝 Summary

  • extractRepoName now decodes the repo-name segment of a git remote URL exactly once (url.PathUnescape), mirroring the single-unescape policy engram cloud enroll already applies to its argument (cmd/engram/cloud.go). Azure DevOps remotes like https://org.visualstudio.com/My%20Project/_git/My%20Project now detect as my project, so observations and sync enrollment converge on one project identity instead of forking my%20project vs my project and blocking sync.
  • Names with invalid escapes (a literal percent) stay raw, and names that decode to path-like or control characters still fail closed through the existing binding validation (normalizeProjectName).
  • Existing stores are untouched by design: a stored repository binding is reused as-is. Explicit convergence paths for a store that already holds the encoded spelling: set project_name explicitly in .engram/config.json, or enroll the double-encoded form (engram cloud enroll "my%2520project"), which single-unescapes to the encoded identity.

📂 Changes

File Change
internal/project/detect.go Decode the percent-encoded repo-name segment once in extractRepoName, with raw fallback on invalid escapes
internal/project/detect_test.go 5 new extractRepoName table cases (Azure HTTPS/SSH v3 encoded space, double-encoded decodes once, invalid escape raw) + TestDetectProject_GitRemote_PercentEncoded regression proving detection yields the decoded canonical identity

🤖 AI Assistance

Select exactly one option. Do not check both options.

  • None — No material AI assistance was used.
  • Material assistance used — Complete all applicable declaration fields below.

Tool/model (if known): Pi coding agent (Claude Sonnet 4.5).

Material scope: Fix and tests authored by the AI agent under direct human instruction (claim decision, plan approval, and push/PR authorization were human).

Verification performed: Test-first (RED on all 4 new failing assertions reproducing the issue split, then GREEN); full go test ./... exit=0; go vet ./internal/project/ clean; gofmt clean; go build ./... OK.


🧪 Test Plan

Unit Tests

go test ./internal/project/ -run 'TestExtractRepoName|TestDetectProject_GitRemote_PercentEncoded' -count=1   # ok
go test ./... -count=1   # exit=0

Go Format

gofmt -l internal/project/   # empty
go vet ./internal/project/   # clean
  • Unit tests pass (go test ./...)
  • Go format passes
  • Manually verified the regression test reproduces the reported split (my%20project vs my project) before the fix

✅ Contributor Checklist

  • PR is linked to an issue with status:approved (Project name from git remote keeps URL-encoding (%20), but cloud enroll decodes it, so sync is blocked #1583)
  • PR stays within 400 changed lines (61 changed lines)
  • I have added the appropriate type:* label to the PR (cannot: pull-only; requested above)
  • Unit tests pass (go test ./...)
  • Go format passes
  • I have updated documentation if necessary (behavior documented in this body; no user-facing docs surface covers remote-name decoding)
  • My commits follow Conventional Commits format
  • I understand, reviewed, and take responsibility for the complete submission
  • I selected exactly one AI-assistance option and, if material assistance was used, completed all applicable declaration fields
  • My commits do not include Co-Authored-By trailers

Summary by CodeRabbit

  • New Features
    • Cloud enrollment, unenrollment, and explicit project sync decode one layer of URL-encoded project names by default.
    • Use --literal-project to preserve percent-encoded characters. For sync, it requires cloud sync and one explicit, non-empty --project; it cannot be combined with --all.
  • Bug Fixes
    • Malformed URL encoding is reported in default mode and accepted in literal mode. Plus signs remain unchanged in both modes.
    • Project detection continues to retain percent-encoded names, including encoded slashes and backslashes.

…tleman-Programming#1583)

Azure DevOps renders spaces in repo names as %20, so git_remote detection
stored observations under the encoded spelling (my%20project) while
`engram cloud enroll` applies a single url.PathUnescape to its argument
and enrolled the decoded spelling (my project). The two identities never
met and sync stayed blocked.

extractRepoName now decodes the repo-name segment exactly once, mirroring
the enroll CLI's single-unescape policy, and keeps names with invalid
escapes raw. Existing repository bindings are reused unchanged; the
explicit convergence paths for stores that already hold the encoded
spelling are an explicit project_name override or enrolling the
double-encoded form.
Copilot AI balanced review requested due to automatic review settings October 1, 2026 14:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 4aa88431-cd75-47e2-a33e-3d81363a9d75

📥 Commits

Reviewing files that changed from the base of the PR and between dd7fd7d and a6af4ea.

📒 Files selected for processing (1)
  • cmd/engram/cloud_project_input_test.go

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Project detection now decodes valid percent escapes in the final Git remote URL component once. Tests cover encoded Azure DevOps names, double-encoded names, malformed escapes, and decoded project identity.

Changes

Project Name Detection

Layer / File(s) Summary
Remote name decoding and regression coverage
internal/project/detect.go, internal/project/detect_test.go
extractRepoName trims and decodes the final URL component once when its escapes are valid. Tests cover encoded Azure DevOps names, double-encoded names, malformed escapes, and lowercase identity from DetectProject.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: gentleman-programming

Merge Risk: ⚪ Minimal · up to a6af4

Cloud users can explicitly select percent-encoded project identities while default decoding remains available. The supplied coverage checks that literal and decoded targets stay separate, and no actionable merge-blocking risk is identified.

Security Architecture Review

Security architecture risk: 🔵 Low · up to a6af4

Literal project selection retains normalization, enrollment checks, and project-specific synchronization. The inspected behavior keeps encoded and decoded identities distinct, with no demonstrated new security boundary bypass. Production authorization and interrupted end-to-end synchronization were not verified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The changed input controls which normalized project is enrolled or synchronized within the caller's configured local store and cloud endpoint. It does not select a new endpoint or credential. Literal percent identities were already expressible through an additional encoding layer in default mode, so the new flag is not evidence of greater authority or cross-tenant reach.

Trust Boundaries and Controls

  • observed — Caller-controlled project text remains separate from the configured server URL and token. Manifest requests place the project in a URL-encoded query parameter, with the configured bearer token attached separately. The loopback test confirms client-side identity preservation; it does not establish production server authorization.

Resilience and Maintainability Implications

  • observed — Unenrollment preserves data and pending journal rows and explicitly does not cancel an already in-flight push. This behavior predates the flag addition. Literal selection changes the project key passed to that operation, not its cancellation or cleanup guarantees.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Issue #1583 requires Git remote detection and cloud enrollment to use the same project identity. The PR keeps detection literal, such as my%20project, while default cloud input decoding still stores… Make detection and the default cloud project flow produce the same identity, or change the linked issue's requirement to accept explicit literal selection. Add regression tests that exercise the reported Azure DevOps %20 flow through dete…
Docstring Coverage ⚠️ Warning Docstring coverage is 13.04% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 23 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The cloud argument parsing, literal project selection, synchronization isolation, documentation, and related tests address project identity handling for issue #1583. The lint cleanup changes only test…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preserving literal cloud project identities.
Full details: Linked Issues check

Explanation

Issue #1583 requires Git remote detection and cloud enrollment to use the same project identity. The PR keeps detection literal, such as my%20project, while default cloud input decoding still stores my project. --literal-project provides an explicit workaround for enrollment, unenrollment, and sync, but it does not make the default detection and enrollment behavior consistent. The tests validate this literal-selection model rather than the issue's required default normalization.

Resolution

Make detection and the default cloud project flow produce the same identity, or change the linked issue's requirement to accept explicit literal selection. Add regression tests that exercise the reported Azure DevOps %20 flow through detection, enrollment, and sync using the default commands.

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

dnlrsls
dnlrsls previously requested changes Oct 1, 2026

@dnlrsls dnlrsls left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the focused fix. I tested commit 60c7d843 in an isolated checkout: the new Azure regression passes, and existing encoded bindings remain stable. Before merging, please address these identity edge cases:

  • Reject decoded separators before fallback normalization. In internal/project/detect.go, PathUnescape feeds normalize(name), which converts names containing / or \ to unknown before binding validation. Both https://example.test/team/alpha%2Fbeta.git and https://example.test/team/gamma%5Cdelta.git created a persistent unknown binding with no error in my probes. This contradicts the PR's fail-closed claim and can make unrelated repos select the same project bucket. Add full-detection tests that reject these inputs without creating a binding, while preserving valid existing bindings. This is an identity-collision concern, not a demonstrated cloud authorization bypass.
  • Cover history that predates the private binding. Binding absence does not establish that the project is new. With an existing observation under my%20project and no Git binding, first detection now selects my project; a subsequent write leaves one observation in each separate bucket. Please add this upgrade regression and define a safe, explicit policy for this case. Preserving existing bindings and advising configuration changes does not by itself prevent this silent split.
  • Preserve literal local-remote paths. /local/repos/my%20project.git now extracts as my project, although %20 can be part of the actual filesystem name. Distinguish local-path syntax from encoded URI input and add a regression for literal percent sequences.
  • Test the complete detection-to-enrollment contract for double encoding. my%2520project.git detects as my%20project, but enrolling that exact displayed name selects my project. The new extraction test proves a single decode, not end-to-end identity consistency. Please test this boundary and document or explicitly support literal project selection rather than claiming all names converge.

The PR tests and these additional probes were run with go test ./internal/project ./cmd/engram -run '^(TestExtractRepoName|TestDetectProject_GitRemote_PercentEncoded|TestReview1590.*)$' -count=1 -v -timeout=60s. The added probe tests were temporary and are not part of this PR. No user database or external cloud service was modified.

@dnlrsls dnlrsls added type:bug Bug fix size:exception Maintainer-approved exception to the 400-line review budget labels Oct 1, 2026
Copilot AI balanced review requested due to automatic review settings October 1, 2026 17:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dnlrsls dnlrsls changed the title fix(project): decode percent-encoded repo names from git remotes (#1583) fix(cloud): preserve literal project identities (#1583) Oct 1, 2026
Follow-up to dd7fd7d: the rewritten enroll/sync literal-project tests
left four unchecked defer s.Close calls and one unchecked fmt.Fprint,
which the Lint job flags. Check them explicitly.
Copilot AI balanced review requested due to automatic review settings October 1, 2026 17:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@danielgap

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review and for dd7fd7d, which resolves all four edge cases by the stronger route: detection stays literal, existing detected buckets are never migrated, and identity selection moves to the explicit --literal-project flag at the CLI boundary.

Under that design the cases I had started fixing dissolve rather than need patching: decoded separators cannot reach normalization (no decoding), pre-binding history cannot split (detection is unchanged), local filesystem remotes are literal by construction, and the double-encoding boundary is now a documented selection concern in DOCS.md instead of a convergence claim. I dropped my in-progress decoding follow-up accordingly.

Verified on dd7fd7d locally: build, internal/project, internal/store, and the cloud/sync tests in cmd/engram pass. One environment note: TestCmdServeSignalClosesUnixSocket fails on this workstation because the serve socket guard rejects the local uid for the temp hierarchy; CI Unit Tests on dd7fd7d are green, so I read that as local-only.

One thing I could add: the Lint job on dd7fd7d flags four unchecked defer s.Close() calls and one unchecked fmt.Fprint the rewritten project-input tests introduced. Fixed in a6af4ea (errcheck-satisfying, no behavior change), pushed on top. Nothing else outstanding from my side.

@dnlrsls
dnlrsls dismissed their stale review October 1, 2026 19:04

Original findings are addressed in a6af4ea: detected identities remain unchanged and --literal-project provides explicit selection without migrations. Exact-head independent validation and four-lens native review passed; CI is green. Withdrawing my original change request, not approving: I contributed dd7fd7d, so another maintainer must review. No merge performed.

@dnlrsls
dnlrsls added this pull request to the merge queue Oct 1, 2026
Merged via the queue into Gentleman-Programming:main with commit a43183c Oct 1, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:exception Maintainer-approved exception to the 400-line review budget type:bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Project name from git remote keeps URL-encoding (%20), but cloud enroll decodes it, so sync is blocked

3 participants