Skip to content

Align the private-data remote workflow with the team flowchart - #60

Closed
denniswu28 wants to merge 10 commits into
ram/clean-upfrom
feat/private-data-v2
Closed

denniswu28 wants to merge 10 commits into
ram/clean-upfrom
feat/private-data-v2

Conversation

@denniswu28

Copy link
Copy Markdown

Summary

Brings the remote-data path of ram/clean-up in line with the team's updated flowchart (Phase 1: private-data workflow). This replaces porting feat/private-data (#52) commit by commit. Ram's restructure and the flowchart agree on the model (hm add <url-or-pattern> stages a catalogue, one pattern per branch, and clone prompts then downloads), so this builds on Ram's code and ports by hand only a few pieces of #52 (2eb1635's URL checks and no-match error, 7da898c's unknown-path check). Stacked on ram/clean-up; Ram's branch is untouched.

One commit per flowchart rule, each with tests and docs (10 commits):

  1. Find the repository from any folder inside it. Repo.find; CLI download paths are relative to the current folder. The CLI runs hm add . (and ./, .., or the absolute root) from the worktree root, so subfolder use cannot expose hm add . from a subfolder drops staged files outside it #53.
  2. Explain damaged repositories and stop without writing. Missing or corrupt state files raise a clear DothmError, and opening a repository never writes.
  3. Keep credentials out of remote catalogue and clone URLs. Passwords, HTTP usernames, query strings and fragments are rejected before any network access, and errors never echo the URL. Clone sources with HTTP credentials are refused too; previously the token was saved in .hm/.git/config.
  4. Report remote scans that match nothing, and keep the previous catalogue.
  5. Refuse remote patterns that would drop catalogued files. There is no force option; a broader pattern is accepted.
  6. Select downloads by catalogued paths and folders. Unknown paths and empty folders are rejected before any server is contacted. --filter/--fmt alone is a usage error.
  7. Skip verified files and refuse downloads that would replace different contents. Existing files with the same checksum are skipped. Without a checksum, a file whose catalogued size matches is skipped as "present, not verified", so interrupted downloads from manifest-less SSH exports can resume. A different size or different contents is a conflict, and then nothing is downloaded. Sizes are compared before hashing, unknown digests are typed by length, and the hashing pool is capped.
  8. Clone into a default folder and refuse unsafe destinations. [DIRECTORY] is optional and an empty folder is accepted; nested or overlapping destinations are refused. A failed clone removes only what it created and never follows a symlink.
  9. Refuse to clone repositories that track local files. "Nothing was created" (MVP rule), including any parent folders it made; empty repositories still clone. Source branch names are validated, so option-like names such as --output=x cannot reach git.
  10. Copy branch pointers and validate the catalogue when cloning. Every branch is copied and validated, and an incomplete clone says how to retry.

Decisions for the team

  • An existing file with no catalogue checksum is skipped when its catalogued size matches, and is a conflict when the size differs or no size is recorded.
  • A local catalogue with a mirror remote_url counts as "tracks local files" and cannot be cloned.
  • --dry-run exits 0 even when it reports unknown paths or conflicts.
  • CLI download paths are relative to the current folder.
  • A file that appears at its destination mid-download fails alone; the others continue.
  • download --tsv is kept for published catalogues with several tables.

Open questions

  1. Demo server checksums. The second download in each demo notebook assumes the shared SSH demo server publishes checksums; otherwise existing files become conflicts. The notebooks were not run (they need the private server).
  2. Git clone credentials. Cloning from https://token@… stores the token in .hm/.git/config; commit 3 covers data URLs only.
  3. Detached HEAD sources. Cloning a source on a directly selected commit was not changed; it belongs with Phase 2.

Not in this PR (Phase 2/3, for follow-up branches): no automatic first commit, hm config user.*, hm branch <name>, detached checkout, add by path, restore --staged, catalogue browsing (undecided).

Known limitations (not changed here)

  • The mid-transfer guard is check-then-replace, so a file created in that instant can still be overwritten.
  • hm set-config --fmt can set a pattern that catalogued files do not fit (local pattern rules are Phase 3).
  • Validating every branch on clone is per-row: about 5 s per 100k rows.
  • A source on a detached commit clones as detached, but status/branches then fail (Phase 2).
  • hm init inside an existing repository creates a nested one.
  • The Repo.add(".") API bug from a subfolder (hm add . from a subfolder drops staged files outside it #53) remains; only the CLI is guarded.

Testing

  • pytest: 756 passed, 18 skipped (baseline 622/18). With HALLMARK_RUN_SSH_TESTS=1: 774 passed (baseline 640). Each commit passes pytest and ruff on its own.
  • An independent code review's findings were fixed in the commits they concern, each with a regression test: resume without checksums, clone credentials, clone rollback scope, option-like branch names, root-equivalent hm add inputs, and dangling .hm links.
  • Sphinx -W and ruff pass.
  • Merged with Automate CI coverage, chaos and survey workflow tests #59 (CI, chaos and survey tests): 836 passed on Python 3.13 and 835 on 3.9 with SSH enabled, 7 strict expected failures and no unexpected passes. The survey and fault-injection suites hold under these changes.
  • CI on GitHub runs here once Automate CI coverage, chaos and survey workflow tests #59's trigger change (pull requests into any branch) is in the base. Until then this was verified locally as above.

Supersedes #52.

🤖 Generated with Claude Code

denniswu28 and others added 10 commits October 4, 2026 21:54
Commands now open the nearest enclosing repository with Repo.find, so hm
works from subfolders of a worktree, and explain when no repository exists
instead of failing on a missing .hm. Repo(path) still opens exactly path.
Download paths are taken relative to the current folder and must stay inside
the worktree, outside .hm.

Repo.add(".") rebuilds the catalog from the current folder only, so from a
subfolder it would drop every row outside it. The CLI therefore runs
hm add . from the worktree root, staging the whole repository as the
workflow specifies, and treats any other path to the root, such as .. from
a subfolder or the absolute path, the same way instead of as a new pattern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening a repository whose .hm is not a Git worktree, lacks config.yml,
meta.yml or data.tsv, or holds a file that cannot be parsed now raises a
DothmError naming the problem instead of a traceback, and leaves every
file untouched. Opening no longer creates the objects link of a linked
worktree; add_worktree creates it when the worktree is made.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remote add and set-config --remote-url now refuse URLs carrying a password,
an HTTP(S) username or token, a query string or a fragment before any
network access, so no secret reaches config.yml. Clone refuses a source URL
with a password or an HTTP(S) username before creating anything, since Git
would save it in .hm/.git/config. The errors do not repeat the URL. SSH
usernames remain allowed. The private data guide says where credentials
belong instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A remote add whose URL, filter and pattern match no files now fails with
an error naming the selection and source instead of succeeding silently.
Connection and access failures during the scan are reported as a failed
scan. In both cases nothing is staged and the previous catalogue stays as
it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A branch keeps one filename pattern. Remote add now refuses, before
contacting the server, a new pattern that some catalogued file does not
fit, listing a few of them; there is no force option. A broader pattern
that fits every file replaces the old one and re-derives the fields of
kept rows. Adding the bare URL again rescans with the saved pattern
instead of dropping it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nes offline

A download path may now name a catalogued file or a folder, which selects
every catalogued file below it. Paths that match no catalogued file are
recorded in DownloadPlan.unknown_paths: --dry-run lists them, and the CLI
and execute_download_plan refuse such a plan before contacting the server
instead of fetching unlisted files without checksums. The CLI requires
paths, --tsv or --all; --filter and --fmt only narrow that selection and
show usage on their own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… contents

Planning now checks every selected file that already exists at its
destination, first by its recorded size and then by its catalog checksum,
hashing in a small parallel pool. Files with their catalog checksum are
skipped, so repeating a download transfers only what is missing. When the
catalog has no checksum, a file with the recorded size is skipped as present
but unverified, so catalogs without checksum manifests can resume too.
Checksums whose algorithm is "unknown" are identified by digest length.
Files of another size or contents, files with neither a checksum nor a size
to check, and anything that is not a regular file become
DownloadPlan.conflicts, and such a plan downloads nothing; the CLI lists them
with a hint to delete them first. A destination that appears while a
transfer runs is no longer overwritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hm clone SOURCE without a directory, and Repo.clone without a path, now
create a folder in the current folder named after the source, ignoring a
.git suffix and naming a .hm folder after its worktree. An existing empty
folder is accepted. If the clone fails, only what it created is removed:
the .hm it added to an existing worktree folder, or the contents of an
existing bare folder, never following a symbolic link. Destinations
inside a Hallmark repository or overlapping a local source are refused
before the source is read. Snapshot imports into a bare .hm destination
no longer collide with the folder created for them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Local-file repositories keep their contents in .hm/objects, which clone
cannot copy yet. Clone now refuses a source with any branch whose
committed catalog records local file versions, with or without
--no-download, and creates nothing: local sources are inspected read-only
before anything is written, while Git-host clones and published snapshots
are checked once their metadata is read, and the partial copy is removed
together with any parent folders the clone created. Source branch names
are validated first, so a name such as --output=x is refused rather than
read by Git as an option. Empty repositories and remote catalogs still
clone. Tests that cloned local-file repositories now use remote catalogs,
and the remote-branch checkout test copies its source with Git.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Git clone now creates a local branch for every branch of the source,
not only the one it starts on, and still starts on the source's selected
branch. Branch names are validated before any branch is created, and Git
is told where its options end. Every branch's committed catalog is checked
with the validator shared with published snapshots, which now also
rejects malformed or unsupported checksums; an invalid branch stops the
clone and removes the copy. When the download after cloning fails, the
error reports the clone as incomplete, where its catalogue is, and the
hm download command that retries the same selection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@denniswu28
denniswu28 requested a balanced review from Copilot October 5, 2026 06:22

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.

@denniswu28

Copy link
Copy Markdown
Author

This is still waiting on clean up of the original repo, and several design choices will need to be reconciled. Mostly is the pattern recognition and how to deal with multiple patterns.

@denniswu28

Copy link
Copy Markdown
Author

Retiring this draft. Work has moved from ram/clean-up to codex/function-atomic-replay-20260928. The commits stay on feat/private-data-v2 for anything worth porting to the new base.

@denniswu28 denniswu28 closed this Oct 10, 2026
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.

2 participants