Uh oh!
There was an error while loading. Please reload this page.
feat(librarystore): versioned library store + GitHub Go strategy (#446) - #447
Merged
Conversation
antoinetoussaint-byteforce-pushed
the
feat/library-store
branch
from
August 22, 2026 15:58
f76dbc6 to
fa48ee4Compareantoinetoussaint-byte
commented
Aug 22, 2026
ContributorAuthor
CI red herring: all 5 checks failed in ~3s with zero steps executed — GitHub Actions annotation: "The job was not started because your account is locked due to a billing issue." Same failure hit every run in the repo since ~15:35 UTC (including main's own push CI for #449 and PR #451). Branch is verified green locally on this exact commit (build + vet + 16/16 tests + golangci-lint 0 issues under Go 1.27). Once billing is unlocked: |
antoinetoussaint-byteforce-pushed
the
feat/library-store
branch
from
August 22, 2026 21:48
fa48ee4 to
375eeddCompare1 task
… strategy First step of the durable library-publishing epic (#446): a backend-agnostic Store that publishes and resolves library exports as versioned artifacts a native package manager can consume without the codefly toolchain. - Store interface (Publish/Resolve/List) over Coordinates{language,name,version} → Published{importPath, immutable ref, digest, install hint}. - GitHubStore Go strategy: publishes each Go export to its own repo tagged vX.Y.Z, so `go get github.com/<owner>/<name>-go@vX.Y.Z` resolves natively. Published versions are immutable (republish is refused); Resolve selects the highest tag satisfying a semver constraint and pins the tag's commit. - Python/TypeScript return an explicit not-implemented error (P1). Tested end-to-end against a real local bare git repository (publish → list → resolve → immutability), no mocks. Part of #446. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…review fixes
Review of the store found the product promise itself was unverified, and four
ways real inputs break the publish/resolve path. Fix the causes:
- Module-path validation: `go get <importPath>@<tag>` only works when the
published go.mod declares exactly that module path, and versions are
immutable — so a mismatched release could never be corrected. Publish now
requires go.mod to declare the derived import path, failing before any git
operation. The e2e test previously enshrined the broken case (published
example.com/authkit into an authkit-go repo); it now publishes the matching
path, and a dedicated test proves rejection fires pre-clone.
- Source .git collision: replaceTrackedTree's os.CopyFS rewrite dropped the
.git skip, so publishing a library that is itself a git checkout would
interleave its .git into the clone's .git before failing. Restore the skip
(root-scoped walk, symlinks still rejected).
- Identical-content releases: a version bump with no code change staged an
empty diff and `git commit` failed. Commit with --allow-empty.
- Silent empty Ref: Resolve read the tag commit from a second ls-remote whose
peeled `^{}` line does not exist for lightweight tags, yielding Ref:"" with
no error. One ls-remote now feeds both List and Resolve (peeled commit wins,
lightweight hash falls back, no listing/lookup race) and the ref is
validated as a commit hash.
- Digest parity and modes: Resolve returned Digest:"" though Published
documents it; it now computes the digest from a shallow clone at the tag.
treeDigest folds in the git-normalized mode (755/644) so an executable-bit
flip changes the digest — the same lesson the docker-build-recipe v2
contract encoded — while staying identical between the publish-side source
and a resolve-side clone; .git is excluded on both sides.
- Publish honors the repository's default branch (symbolic-ref) instead of
forcing main; a missing remote repo now errors with create-it-first
guidance; Published.Version is canonicalized (no leading v) on both paths;
prerelease exclusion under plain constraints is pinned by test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>Follow-up review of the store surfaced three remaining defects: - Digest domain mismatch: Publish hashed the raw source dir while Resolve hashed a clone, so a .gitignore'd file made the two disagree — breaking the Published "the two agree" contract. treeDigest now hashes the git-tracked file set (`git ls-files`) on both paths, so it describes exactly the published content and Publish/Resolve always match. - Non-hermetic git: commit/tag inherited ambient config, so a global commit.gpgsign=true (or a push credential prompt) could fail or hang a real publish. Signing is now disabled per-invocation and git runs with GIT_TERMINAL_PROMPT=0, while push credentials are preserved. - Owner foot-gun: the default remoteFor closed over the constructor argument, so reassigning GitHubStore.Owner silently published to the original owner. It now reads s.Owner live. Tests: digest ignores untracked/ignored files and tracks the exec bit; Publish succeeds under a global config that forces signing with a bogus gpg program; Owner reassignment retargets the remote. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… last
Second-pass review found three ways a publish could go wrong before or
after the irreversible push, all confirmed empirically:
- Path traversal: owner/name were interpolated into the remote URL
unvalidated, and git's HTTP client normalizes "owner/../../evil/repo"
to a different repository — a crafted library name could redirect a
credentialed publish. Both atoms are now validated as plain tokens at
every entry point, before any git command runs.
- Digest instability: treeDigest hashed working-tree bytes, so ambient
core.autocrlf (deliberately preserved for credentials) made the
publish-side digest differ from a resolve-side clone of the same
release — verification would flag legitimate content as tampered. The
digest is now over the index's blob object IDs, which are
content-addressed after git's own normalization and provably identical
on both sides (and require no file reads).
- Unfetchable releases: build-metadata semver ("1.0.0+build.1") parses,
tags, and pushes fine, but Go module versions discard build metadata,
so the immutable tag could never be `go get`-ed. Publish now refuses it.
Also: rev-parse/digest moved before the pushes so every failure leaves
the remote untouched and retryable; the verbatim tag name is carried
through resolution instead of being reconstructed from normalized
semver (a human-added "v1.0" no longer breaks Resolve); output() folds
git's stderr into errors instead of an opaque "exit status 128"; commit
runs --no-verify so ambient hooks cannot fail or mutate a release.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>antoinetoussaint-byteforce-pushed
the
feat/library-store
branch
from
August 22, 2026 22:57
375eedd to
590877dCompareUh oh!
There was an error while loading. Please reload this page.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes#446.
First step of the library-publishing epic (P0). Mirrors the #443 durability principle for libraries: publish them as durable, versioned artifacts a native package manager can pull without the codefly toolchain or local source.
Summary
pkg/librarystore— a backend-agnosticStore(Publish / Resolve / List) overCoordinates{language,name,version}→Published{importPath, immutable ref, digest, install hint}. Keeps PyPI/Artifactory/npm-registry/Go-proxy swappable later.vX.Y.Z, sogo get github.com/<owner>/<name>-go@vX.Y.Zresolves natively (a git repo with semver tags is a Go module source). Published versions are immutable (republish refused);Resolvepicks the highest tag satisfying a semver constraint and pins the tag's commit;treeDigestgives a deterministic content digest.not implemented yeterror — deferred to P1 (PEP 503 index / GitHub Packages).Scope
This is the store foundation only. Follow-up P0 steps (own PRs): the
codefly export librarycommand, the library-packaging agent RPC, and the resolver "published mode" that wiresgo get/pip/npminto consumer manifests (implementinginstall library).Test plan
go build/go vet/golangci-lintcleanListnewest-first →Resolve ^1.0.0→ 1.2.0 (commit-pinned); republish same version rejected; non-semver + unsupported-language rejected;treeDigestdeterministic + content-sensitive.