You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
refactor(op): the content-addressed path is the framework's, not a provider's - #668
It was never provider-specific. It derives the subdirectory from the resource's own type id, which is
why a function pack lands under .devlore/function/resource/ — not because mem put it there, but
because the formula reads whatever resource it is handed. It lived in mem because mem was the first
content-addressed resource, not because it belonged there.
Now op.ContentPath(Resource) fsroot.Path and op.ContentReader(Resource) (io.ReadCloser, error), with mem.SourcePath and mem.Reader delegating. The mmap-backed reader and the type-id splitter moved with it.
Why op and not mem
.devlore is already framework territory — RecoverySite owns .devlore/recovery. mem held the
only other production line that knew the layout.
The parameter type is op.Resource, so no provider has to import another to find its own content.
op already packs and unpacks the content section — it calls Pack() and writes the result. Knowing
where that result lives is the same concern.
Path composition still goes through fsroot (Root().NewPath(...)), exactly as before. Nothing joins
strings by hand and fsroot needs no new capability.
One implementation note
ContentPath reads the reachability URI through the sealed base accessor, resource.resourceBase(),
rather than through the public interface. op.Resource does not declare ReachabilityURI — a gap noted in
phase 1 — and widening the public contract for one framework caller would be the wrong trade. The sealed
accessor exists for exactly this.
function.Resource embeds mem.Resource cross-package, the only such embed in the tree, and it has already
forced two schedule changes. The embed exists to borrow three behaviours over function's own identity — function builds its base with reflect.TypeFor[*function.Resource]() and hands it to the embedded value.
That is a mixin, not composition.
With the formula in op, function can drop the embed and call the helpers directly, which is what #662
does next. Of mem's thirteen methods, function genuinely used SourcePath() and the Hash field — Pack and Unpack never delegated, and sourceBytes() already opened its own mmap.
Verification
make test 103 ok / 0 fail · make vet darwin/linux/windows · test-scenario · complexity · gofmt -l — all clean.
David-Noble-at-work
changed the title
refactor(op): the content-address path is the framework's, not a provider's
refactor(op): the content-addressed path is the framework's, not a provider's
Aug 25, 2026
Renamed on review: ContentPath → ContentAddressedPath, ContentReader → ContentAddressedReader,
and the file to pkg/op/content_addressed_path.go.
Content lacked specificity in exactly the package where it hurts: op already has a document content
section — 21 mentions across the tree — so ContentPath reads plausibly as "the path to the content
section," which is a different thing entirely.
Why the spelled-out form over CASPath
CAS was the other candidate and it is not obscure here — 25 uses across pkg/ and the architecture
docs, including inside op itself (digest.go, resource.go). Go's convention would also keep the casing
correct. It lost on two counts: the codebase spells content-address(ed) out 74 times against those 25,
roughly 3:1, and ContentAddressedPath reads grammatically where ContentAddressPath would be an awkward
compound noun.
Two names that were rejected for being wrong, not merely worse
PackPath — tempting, because op.Packer/Pack() already exist and function calls its bytes "the
pack". But mem.Pack()reads from Reader(): the file is Pack's source, not its output. function.sourceBytes() does the same. The name would have described the data flowing the wrong way.
ArchivePath — matches mem's own "archived content" vocabulary, but hides a precondition. The
helper returns a zero Path unless the reachability URI is <algo>:<hex>, so the content-addressing is
part of the contract, not a layout detail. A name that omits it invites callers to expect it to work for
any resource.
Verification
make test 103 ok / 0 fail · make vet darwin/linux/windows · test-scenario · gofmt -l — all clean.
No stale ContentPath or ContentReader references remain.
Removed with the strings and mmap imports only they used. splitTypeID lives in helpers.go, not resource.go where my removal looked, and the resourceReader region did not match the pattern I used, so
both survived a cleanup that reported success.
Why my local run missed it: I verified with vet and test, neither of which sees dead code, having
earlier concluded lint was unavailable locally. It was not — ./build/star lint go ./... works fine and is
the gate CI runs. That was a habit failure, not a tooling one.
2. dist keeps its stamp proof — a hole I opened in #653
dist-all carried this, three lines above the code:
"Depends on build for its stamp check, not for its binaries… build proves on the host that those
flags bind to real symbols — which is the failure that shipped unnoticed until 2026-08-16."
Then #653 taught the stamp check to skip when PLATFORM excludes the host. So make dist PLATFORM=linux/amd64 would have shipped archives with nothing verifying the stamp bound — the
exact defect that shipped silently for months.
dist-all now forces a host build for the proof, independent of what the caller selected. Verified: make dist PLATFORM=linux/amd64 completes with no skip line.
That was making a gate conditional to clear a red build without tracing what depended on it. The answer
was written directly above the code.
make lint pointing at star lint go is correct and belongs to #670, not to this PR. Unifying it turns make check red on four pre-existing cmd/writ findings (#669), and clearing those means deciding whether
a nil-guard can be removed — a bounded question, but not this PR's.
Verification
make test 103 ok / 0 fail · make vet · star lint go ./pkg/...0 issues · gofmt -l clean · make dist PLATFORM=linux/amd64 completes with the stamp proof intact.
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
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.
Groundwork for #662, landed separately because it stands on its own and the rest of that phase is large.
What moved
mem.Resource.SourcePathowned the formula for where content-addressed bytes live on disk:It was never provider-specific. It derives the subdirectory from the resource's own type id, which is
why a
functionpack lands under.devlore/function/resource/— not becausememput it there, butbecause the formula reads whatever resource it is handed. It lived in
membecausememwas the firstcontent-addressed resource, not because it belonged there.
Now
op.ContentPath(Resource) fsroot.Pathandop.ContentReader(Resource) (io.ReadCloser, error), withmem.SourcePathandmem.Readerdelegating. The mmap-backed reader and the type-id splitter moved with it.Why
opand notmem.devloreis already framework territory —RecoverySiteowns.devlore/recovery.memheld theonly other production line that knew the layout.
op.Resource, so no provider has to import another to find its own content.opalready packs and unpacks the content section — it callsPack()and writes the result. Knowingwhere that result lives is the same concern.
Path composition still goes through
fsroot(Root().NewPath(...)), exactly as before. Nothing joinsstrings by hand and
fsrootneeds no new capability.One implementation note
ContentPathreads the reachability URI through the sealed base accessor,resource.resourceBase(),rather than through the public interface.
op.Resourcedoes not declareReachabilityURI— a gap noted inphase 1 — and widening the public contract for one framework caller would be the wrong trade. The sealed
accessor exists for exactly this.
Why this unblocks #662
function.Resourceembedsmem.Resourcecross-package, the only such embed in the tree, and it has alreadyforced two schedule changes. The embed exists to borrow three behaviours over
function's own identity —functionbuilds its base withreflect.TypeFor[*function.Resource]()and hands it to the embedded value.That is a mixin, not composition.
With the formula in
op,functioncan drop the embed and call the helpers directly, which is what #662does next. Of
mem's thirteen methods,functiongenuinely usedSourcePath()and theHashfield —PackandUnpacknever delegated, andsourceBytes()already opened its own mmap.Verification
make test103 ok / 0 fail ·make vetdarwin/linux/windows ·test-scenario·complexity·gofmt -l— all clean.