Skip to content

Add project-root-aware path type - #1098

Draft
shunichironomura wants to merge 5 commits into
hook-outcome-fail-closedfrom
project-relative-path
Draft

shunichironomura wants to merge 5 commits into
hook-outcome-fail-closedfrom
project-relative-path

Conversation

@shunichironomura

@shunichironomura shunichironomura commented Jul 7, 2026 •

Copy link
Copy Markdown
Member

Summary

  • introduce ProjectRelativePath and ResolvedProjectPath for canonical, project-contained path resolution
  • use it for capture-command cwd and capture-git-repo path configs
  • update resolve_relative and tests for absolute/relative escape/symlink cases

Testing

  • just test
  • just lint

Closes #575


This is part 2 of 4 in a stack made with GitButler:

@shunichironomura shunichironomura mentioned this pull request Jul 7, 2026
1 of 2 tasks
@codecov-commenter

codecov-commenter commented Jul 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.29851% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.61%. Comparing base (7d291d9) to head (0806b44).

Files with missing lines Patch % Lines
crates/capsula-core/src/project_path.rs 90.09% 11 Missing ⚠️
crates/capsula-capture-command/src/lib.rs 75.00% 1 Missing ⚠️
crates/capsula-core/src/util.rs 93.33% 1 Missing ⚠️
Additional details and impacted files
@@                     Coverage Diff                      @@
##           hook-outcome-fail-closed    #1098      +/-   ##
============================================================
+ Coverage                     53.57%   54.61%   +1.03%     
============================================================
  Files                            41       42       +1     
  Lines                          4166     4283     +117     
============================================================
+ Hits                           2232     2339     +107     
- Misses                         1934     1944      +10     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@shunichironomura
shunichironomura marked this pull request as draft July 7, 2026 12:57
@shunichironomura shunichironomura self-assigned this Jul 7, 2026
@shunichironomura
shunichironomura force-pushed the project-relative-path branch 2 times, most recently from 5ef29d9 to f3ebdfc Compare July 7, 2026 13:15
@shunichironomura
shunichironomura force-pushed the hook-outcome-fail-closed branch from 93a5223 to 085d153 Compare July 7, 2026 14:24
@shunichironomura
shunichironomura force-pushed the project-relative-path branch from f3ebdfc to eb92bb7 Compare July 7, 2026 14:24
@shunichironomura
shunichironomura force-pushed the hook-outcome-fail-closed branch from 085d153 to 52aeb41 Compare July 20, 2026 23:09
@shunichironomura
shunichironomura force-pushed the hook-outcome-fail-closed branch from 52aeb41 to 46ef647 Compare July 22, 2026 06:29
@shunichironomura
shunichironomura force-pushed the project-relative-path branch 2 times, most recently from 5a73c17 to 276c96e Compare July 22, 2026 06:44
@shunichironomura
shunichironomura force-pushed the hook-outcome-fail-closed branch from e36bd14 to 6a82a85 Compare July 22, 2026 06:49
@shunichironomura shunichironomura linked an issue Jul 22, 2026 that may be closed by this pull request
@shunichironomura
shunichironomura force-pushed the hook-outcome-fail-closed branch from 6a82a85 to 985d14e Compare July 22, 2026 10:42
@shunichironomura
shunichironomura force-pushed the hook-outcome-fail-closed branch from 985d14e to 7d291d9 Compare July 22, 2026 22:53
@shunsuke-shimomura shunsuke-shimomura mentioned this pull request Aug 4, 2026
1 of 2 tasks
shunsuke-shimomura added a commit that referenced this pull request Aug 5, 2026
Add a pull command that downloads a run from a Capsula server and
restores it under the local vault, as the counterpart of capsula push
(#1177):

- Extend GET /api/v1/runs/{id} with a files array (path, size, hash);
  the server-internal storage_path is not exposed
- Reconstruct the run directory deterministically from the run's ULID
  and name via the new capsula_core::run::run_dir_relative_path (shared
  with local run creation)
- Verify downloaded files against server-recorded SHA-256 hashes;
  restore _capsula metadata best-effort from the server's structured
  data and write a _capsula/pulled.json origin marker
- Download into a temporary directory and rename atomically; existing
  run directories require --force to replace
- Restore only relative paths contained in the run directory (syntactic
  check); full path-safety hardening follows the ResolvedProjectPath
  work (#1098, #1146)
- Encode URL file paths segment-wise with NON_ALPHANUMERIC (allowlist)
  so arbitrary filenames round-trip without a curated character list
shunsuke-shimomura added a commit that referenced this pull request Sep 8, 2026
Add a pull command that downloads a run from a Capsula server and
restores it under the local vault, as the counterpart of capsula push
(#1177):

- Extend GET /api/v1/runs/{id} with a files array (path, size, hash);
  the server-internal storage_path is not exposed
- Reconstruct the run directory deterministically from the run's ULID
  and name via the new capsula_core::run::run_dir_relative_path (shared
  with local run creation)
- Verify downloaded files against server-recorded SHA-256 hashes;
  restore _capsula metadata best-effort from the server's structured
  data and write a _capsula/pulled.json origin marker
- Download into a temporary directory and rename atomically; existing
  run directories require --force to replace
- Restore only relative paths contained in the run directory (syntactic
  check); full path-safety hardening follows the ResolvedProjectPath
  work (#1098, #1146)
- Encode URL file paths segment-wise with NON_ALPHANUMERIC (allowlist)
  so arbitrary filenames round-trip without a curated character list
shunsuke-shimomura added a commit that referenced this pull request Sep 8, 2026
Add a pull command that downloads a run from a Capsula server and
restores it under the local vault, as the counterpart of capsula push
(#1177):

- Extend GET /api/v1/runs/{id} with a files array (path, size, hash);
  the server-internal storage_path is not exposed
- Reconstruct the run directory deterministically from the run's ULID
  and name via the new capsula_core::run::run_dir_relative_path (shared
  with local run creation)
- Verify downloaded files against server-recorded SHA-256 hashes;
  restore _capsula metadata best-effort from the server's structured
  data and write a _capsula/pulled.json origin marker
- Download into a temporary directory and rename atomically; existing
  run directories require --force to replace
- Restore only relative paths contained in the run directory (syntactic
  check); full path-safety hardening follows the ResolvedProjectPath
  work (#1098, #1146)
- Encode URL file paths segment-wise with NON_ALPHANUMERIC (allowlist)
  so arbitrary filenames round-trip without a curated character list
shunsuke-shimomura added a commit that referenced this pull request Sep 9, 2026
Add a pull command that downloads a run from a Capsula server and
restores it under the local vault, as the counterpart of capsula push
(#1177):

- Extend GET /api/v1/runs/{id} with a files array (path, size, hash);
  the server-internal storage_path is not exposed
- Reconstruct the run directory deterministically from the run's ULID
  and name via the new capsula_core::run::run_dir_relative_path (shared
  with local run creation)
- Verify downloaded files against server-recorded SHA-256 hashes;
  restore _capsula metadata best-effort from the server's structured
  data and write a _capsula/pulled.json origin marker
- Download into a temporary directory and rename atomically; existing
  run directories require --force to replace
- Restore only relative paths contained in the run directory (syntactic
  check); full path-safety hardening follows the ResolvedProjectPath
  work (#1098, #1146)
- Encode URL file paths segment-wise with NON_ALPHANUMERIC (allowlist)
  so arbitrary filenames round-trip without a curated character list
shunsuke-shimomura added a commit that referenced this pull request Sep 9, 2026
* feat: add capsula pull command

Add a pull command that downloads a run from a Capsula server and
restores it under the local vault, as the counterpart of capsula push
(#1177):

- Extend GET /api/v1/runs/{id} with a files array (path, size, hash);
  the server-internal storage_path is not exposed
- Reconstruct the run directory deterministically from the run's ULID
  and name via the new capsula_core::run::run_dir_relative_path (shared
  with local run creation)
- Verify downloaded files against server-recorded SHA-256 hashes;
  restore _capsula metadata best-effort from the server's structured
  data and write a _capsula/pulled.json origin marker
- Download into a temporary directory and rename atomically; existing
  run directories require --force to replace
- Restore only relative paths contained in the run directory (syntactic
  check); full path-safety hardening follows the ResolvedProjectPath
  work (#1098, #1146)
- Encode URL file paths segment-wise with NON_ALPHANUMERIC (allowlist)
  so arbitrary filenames round-trip without a curated character list

* fix: address PR #1178 review findings (F1-F4, F6-F10, F13-F17)

Merge blockers:
- F1/F3/F6: --force now only replaces runs carrying the
  _capsula/pulled.json marker; locally produced runs are never replaced
  and the already-exists errors distinguish the two cases. push (and
  push --all) refuses/skips pulled runs so lossy reconstructions cannot
  degrade the server's copy. The existence check is re-done at
  replacement time, so a directory appearing during the download is no
  longer clobbered.
- F2: the download temp directory now lives directly under the vault
  root (same filesystem, atomic rename) instead of vault/<date>/, so an
  interrupted pull cannot leave a phantom run at the depth the vault
  scanners walk.

Non-blockers taken along:
- F4 (minimum): non-ULID pull arguments fail fast with a specific error
- F7/F8: run name and vault name are validated as single path
  components before being joined into paths
- F9: reuse capsula_core::util::hex_encode instead of local copies
- F10: rename the pull-side sanitizer to checked_run_relative_path to
  stop sharing a name with the server's normalizing variant
- F13: percent-encoding no longer escapes RFC 3986 unreserved chars
- F14: shared fetch_run_files used by both run-detail handlers
- F15: verify downloaded sizes; warn on negative duration_ms; align
  duration_ms width with the server model
- F16: document the round-trip fidelity gaps in the CLI reference
- F17: regression tests for all of the above (16 integration tests)

* fix: address PR #1178 review comments

Pull runs of a non-configured vault into the default vault location.

The configured `[vault] path` describes the configured vault only, so
restoring another vault's run next to it placed the run in a directory
that was never chosen for it (an external directory, for instance).
The default `.capsula/<name>` layout only existed as an inline format
string inside `VaultConfig`'s Deserialize impl, which is why `pull`
could not ask for it. Extract it as `DEFAULT_VAULT_ROOT` plus
`default_vault_path()` in capsula-config and use it from both the
config default and `pull`.

A run of another vault now defaults to `.capsula/<vault>/` under the
project root. An explicit `--vault-path` / `CAPSULA_VAULT_PATH`
override still wins, so `resolve_vault_path()` reports its source
through `VaultPathSource` and `LoadedConfig` carries it. The decision
lives in `resolve_pull_vault_dir()` so it is unit-testable.

Also centralize the pulled-run check: `is_pulled_run()` is now public
and replaces the open-coded `pulled.json` checks in `push_single_run()`
and in the `push --all` skip.
@shunichironomura
shunichironomura force-pushed the hook-outcome-fail-closed branch 3 times, most recently from 709e249 to 0d7ac32 Compare September 17, 2026 00:08
@shunichironomura
shunichironomura force-pushed the hook-outcome-fail-closed branch from 0d7ac32 to 8ed6f9f Compare October 1, 2026 07:49
@shunichironomura
shunichironomura added this pull request to stack #1271 October 1, 2026 07:53
@shunichironomura
shunichironomura force-pushed the hook-outcome-fail-closed branch from 8ed6f9f to 6833c58 Compare October 1, 2026 08:24
@shunichironomura
shunichironomura removed this pull request from stack #1271 October 1, 2026 08:24

This branch has not been deployed

No deployments
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.

"If releative, resolve against the project root" path type

2 participants