Skip to content

Commit 61276f7

Browse files
committed
fix: refactor and splitting modules
1 parent 2fab196 commit 61276f7

47 files changed

Lines changed: 4281 additions & 1591 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.fluxel/config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"backend":"local","format_version":1,"dataset_root":"/Users/londogard/git/strand","default_branch":"main","identity":"blake3","transfer_backend":null}

.fluxel/refs/HEAD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
refs/heads/main

.fluxel/refs/heads/main

Whitespace-only changes.

.fluxel/refs/heads/main.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"commit_id": null, "version_token": "1783593278287833390-0"}

.github/workflows/ci.yml

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
test-and-build:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- name: Check out repository
13+
uses: actions/checkout@v4
14+
15+
- name: Set up Python
16+
uses: actions/setup-python@v5
17+
with:
18+
python-version: "3.12"
19+
20+
- name: Set up uv
21+
uses: astral-sh/setup-uv@v4
22+
23+
- name: Install dependencies
24+
run: uv sync --group dev
25+
26+
- name: Reject macOS junk files
27+
run: |
28+
if git ls-files | grep -E '(^__MACOSX/|(^|/)\.DS_Store$|(^|/)\._)'; then
29+
echo "Remove macOS archive/junk files before merging."
30+
exit 1
31+
fi
32+
33+
- name: Run non-integration tests
34+
run: uv run pytest tests -m "not integration"
35+
36+
- name: Smoke-test CLI help
37+
run: uv run fluxel --help
38+
39+
- name: Build package
40+
run: uv build
41+
42+
- name: Clean-tree check
43+
run: |
44+
if [ -n "$(git status --porcelain)" ]; then
45+
echo "Working tree is dirty after build:"
46+
git status
47+
git diff
48+
exit 1
49+
fi
50+
51+
- name: Upload wheel artifact
52+
uses: actions/upload-artifact@v4
53+
with:
54+
name: wheel
55+
path: dist/*.whl
56+
if-no-files-found: error
57+
58+
s3-integration:
59+
runs-on: ubuntu-latest
60+
61+
steps:
62+
- name: Check out repository
63+
uses: actions/checkout@v4
64+
65+
- name: Set up Python
66+
uses: actions/setup-python@v5
67+
with:
68+
python-version: "3.12"
69+
70+
- name: Set up uv
71+
uses: astral-sh/setup-uv@v4
72+
73+
- name: Install dependencies
74+
run: uv sync --group dev
75+
76+
- name: Run S3 integration tests against MiniStack
77+
run: bash scripts/run_s3_integration.sh
78+
79+
isolated-install-smoke:
80+
needs: [test-and-build]
81+
runs-on: ubuntu-latest
82+
83+
steps:
84+
- name: Set up Python
85+
uses: actions/setup-python@v5
86+
with:
87+
python-version: "3.12"
88+
89+
- name: Download wheel artifact
90+
uses: actions/download-artifact@v4
91+
with:
92+
name: wheel
93+
path: dist
94+
95+
- name: Isolated install and smoke
96+
run: |
97+
set -euo pipefail
98+
WHEEL=$(ls dist/*.whl | head -1)
99+
python -m venv /tmp/smoke-venv
100+
/tmp/smoke-venv/bin/pip install "$WHEEL"
101+
echo "=== fluxel --help ==="
102+
/tmp/smoke-venv/bin/fluxel --help
103+
echo "=== fluxel --version ==="
104+
/tmp/smoke-venv/bin/fluxel --version 2>/dev/null || true
105+
echo "=== basic commit smoke ==="
106+
TMPDIR=$(mktemp -d)
107+
echo "smoke-test-content" > "$TMPDIR/smoke.txt"
108+
/tmp/smoke-venv/bin/fluxel commit --repo "$TMPDIR" -m "smoke" > /dev/null
109+
echo "Isolated install smoke test passed."
110+
111+
release-gate:
112+
needs: [test-and-build, s3-integration, isolated-install-smoke]
113+
runs-on: ubuntu-latest
114+
steps:
115+
- run: echo "All release gates passed."

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ dmypy.json
148148

149149
# Pyre type checker
150150
.pyre/
151+
.pyrefly_cache/
151152

152153
# pytype static type analyzer
153154
.pytype/

ISSUES.MD

Lines changed: 0 additions & 3 deletions
This file was deleted.

LAUNCH_TODO.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Pre-launch TODOs
2+
3+
This list is deliberately launch-oriented rather than a feature roadmap.
4+
5+
## P0 — complete before users can share repositories
6+
7+
- [X] Make `push` and `pull` reject divergent history and check the CAS result.
8+
They currently copy objects and then move the destination branch without
9+
proving that the destination head is an ancestor; they also report success
10+
when `compare_and_set_branch_ref` returns `False`. CAS prevents a race, not
11+
a destructive non-fast-forward update. Add two-client divergence and
12+
concurrent-update tests for both directions.
13+
- [x] Add a release gate that runs: non-integration tests, real S3 integration,
14+
an isolated install of the built wheel, CLI smoke tests, and a clean-tree
15+
check. Implemented in `.github/workflows/ci.yml`:
16+
`test-and-build` (non-integration tests + build + clean-tree + artifact upload),
17+
`s3-integration` (real S3 against MiniStack),
18+
`isolated-install-smoke` (wheel install in fresh venv + CLI smoke),
19+
and `release-gate` (unified gate depending on all three).
20+
The `release-gate` job can be set as the single required status check
21+
in branch-protection rules.
22+
- [x] Decide and document the durability contract for `--identity meta`.
23+
Source URIs can change after a metadata-only import; label such revisions as
24+
unverifiable until `verify`, warn in CLI output, and document the required
25+
source-retention policy.
26+
Implemented:
27+
- `ManifestEntry.is_verified` property distinguishes blob-backed from
28+
metadata-only entries.
29+
- CLI warns to stderr on `add --identity meta`, `commit` with meta config,
30+
and `verify` when unverifiable entries remain.
31+
- Durability contract and source-retention policy documented in README
32+
under "Identity Modes → Durability contract for --identity meta".
33+
- [x] Publish operator runbooks for S3 IAM, encryption, bucket versioning,
34+
lifecycle/retention, backups, lock recovery, and incident recovery. Add a
35+
supported lock inspection/cleanup command before relying on shared S3 repos.
36+
Implemented:
37+
- `docs/operator-runbook.md` covers IAM policy, SSE-S3/KMS encryption,
38+
bucket versioning, lifecycle rules, backup/restore strategies, lock
39+
recovery procedures, and five incident recovery scenarios.
40+
- `fluxel lock list` and `fluxel lock cleanup` CLI commands with `--json`,
41+
`--force`, and `--repo` flags.
42+
- `S3RepositoryStore.list_branch_locks()`, `.branch_lock_info()`, and
43+
`.force_release_branch_lock()` plus `FluxelRepository.list_locks()`,
44+
`.lock_info()`, `.force_release_lock()`, and `.lock_timeout_seconds`.
45+
46+
## P1 — strongly recommended for the first public release
47+
48+
- [ ] Make the configured type-check gate useful. `uv run pyrefly check`
49+
currently reports 89 errors (including exported names, protocol typing, and
50+
filesystem return types), while the config keys emit warnings. Either fix the
51+
errors or scope/configure the check deliberately, then add it to CI.
52+
- [ ] Update the README's MVP status: it says remote sync CLI is not wired,
53+
but `push`, `pull`, and `fetch` now exist. Document their safety semantics,
54+
supported remotes, and recovery workflow.
55+
- [ ] Replace whole-object sync copies with streamed transfers. The current
56+
sync helper reads each complete blob into memory and writes a temporary file,
57+
which makes large-object sync memory- and disk-heavy.
58+
- [ ] Change fsspec dataset resolution to fail for an unknown dataset instead
59+
of falling back to the current directory. A typo can otherwise read from an
60+
unintended local repository.
61+
- [ ] Test the supported-version matrix: Python 3.11 and 3.12, current fsspec,
62+
AWS S3, and the configured S3-compatible endpoint. Include interrupted
63+
transfer, stale-lock, access-denied, and corrupted-object scenarios.
64+
- [ ] Add privacy/security release checks: dependency/vulnerability scan,
65+
license review of runtime dependencies, documentation on secrets/credential
66+
handling, and a statement of telemetry behavior.
67+
68+
## P2 — early operational follow-ups
69+
70+
- [ ] Add remote configuration (`remote add`) and clone; requiring a full S3
71+
URI on every sync is error-prone.
72+
- [ ] Add garbage collection with a dry-run and retention policy for unreachable
73+
blobs/manifests.
74+
- [ ] Add machine-readable release notes, versioning/migration policy, and a
75+
reproducible PyPI publishing workflow.
76+
77+
78+
----
79+
80+
Architecture?
81+
82+
CLI / fsspec
83+
84+
Application use cases
85+
(commit, add, verify, sync, restore, merge)
86+
87+
Domain
88+
(commits, manifests, paths, identity, commit graph, errors)
89+
90+
Ports
91+
(RepositoryStore, ClientState, Workspace, BlobTransfer)
92+
93+
Adapters
94+
(local filesystem, S3, fsspec, CLI formatting)
95+
96+
**Concretely:**
97+
* Keep ManifestEntry, path validation, hashing rules, commit graph ancestry, and domain errors pure—no Path, boto3, or CLI imports.
98+
* Make RepositoryStore the shared-object port, ClientState the local mutable-state port, and add a streaming BlobTransfer port.
99+
* Move each command into a small use case with explicit input/result objects: CommitUseCase, SyncUseCase, RestoreUseCase, etc.
100+
* Make ref updates go through one operation such as advance_ref(expected_head, new_head). The application layer must prove fast-forward ancestry and must treat a failed CAS as a conflict.
101+
* Keep immutable-object publication separate from the final ref update: upload blobs → manifest → commit → CAS branch ref.
102+
* Add one contract-test suite that every RepositoryStore implementation must pass; run it against local storage and S3-compatible storage.
103+
* Keep fsspec as a thin read adapter, not part of repository business logic.

README.md

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Fluxel is intentionally in MVP mode.
3434
- Commit snapshots over a dataset root (`fluxel commit`).
3535
- Repository URI support via `--repo <path|s3://bucket/prefix>` and `open_repository(...)`.
3636
- Repository initialization (`fluxel init`) with local or S3 backend.
37+
- Remote sync workflow (`fluxel fetch`, `fluxel pull`, `fluxel push`).
38+
- Operator-facing S3 lock inspection and cleanup (`fluxel lock list`, `fluxel lock cleanup`).
3739
- Streaming S3 imports (`fluxel import`) with `blake3` or metadata identity modes.
3840
- Branch-scoped staging workflow (`fluxel add`, `fluxel rm`, `fluxel status`, `fluxel commit --staged`).
3941
- Incremental ingress paths that preserve existing manifest entries while adding only new content metadata/blobs.
@@ -49,11 +51,6 @@ Fluxel is intentionally in MVP mode.
4951
- Local + S3 storage backend abstractions available in code.
5052
- Human-readable output by default; `--json` flag on all commands for programmatic use.
5153

52-
### Not Wired Yet
53-
54-
- No remote sync CLI (`push/pull/fetch`) yet.
55-
- No operator-facing S3 lock inspection or cleanup command.
56-
5754
## Technical Stack
5855

5956
- Python 3.11+
@@ -185,6 +182,36 @@ In `--identity meta` snapshots, Fluxel reads from `source_uri` when no canonical
185182

186183
This is useful for large bootstrap imports where strong content verification can be deferred.
187184

185+
### Durability contract for `--identity meta`
186+
187+
Metadata-only (`--identity meta`) revisions are **unverifiable**: the entry's
188+
identity is derived from path and size, not from content bytes. Until you run
189+
`fluxel verify`, Fluxel cannot prove that the content at `source_uri` matches
190+
what was originally imported.
191+
192+
**Warnings.** The CLI emits a warning to stderr whenever you stage or commit
193+
with `--identity meta`, and after `verify` reports how many unverifiable entries
194+
remain.
195+
196+
**Source-retention policy.** Because metadata-only entries have no canonical
197+
blob, you **must** retain the source objects at their original `source_uri`
198+
until the entry has been promoted via `fluxel verify`. If a source object is
199+
deleted, overwritten, or moved before verification, the corresponding manifest
200+
entry becomes irrecoverable — no content can be read and no hash can be
201+
validated.
202+
203+
**Promotion to verifiable.** Run `fluxel verify` to read every metadata-only
204+
entry's source blob, compute a Blake3 content hash, store the canonical blob,
205+
and rewrite the manifest entry in `blake3` mode. After promotion the source
206+
retention requirement is lifted for those entries.
207+
208+
**Lifecycle summary:**
209+
210+
| State | `identity_mode` | `blob_hash` | Can read? | Can prove integrity? | Source required? |
211+
|---|---|---|---|---|---|
212+
| Metadata-only | `meta` | `null` | ✅ (from `source_uri`) |||
213+
| Verified | `blake3` | hash | ✅ (from `blobs/`) |||
214+
188215
## Verify Command
189216

190217
`fluxel verify` promotes metadata-only (`--identity meta`) manifest entries into canonical `blake3` blob-backed entries:

0 commit comments

Comments
 (0)