Repository navigation
Seal function — the Starlark-callable resource #662
Description
Feature: #625 · split out of #643 (USER, 2026-08-24), which now covers json, yaml, and mem only.
function is the outlier among the Unpacker four and does not fit the template the earlier phases established.
Why it is separate
Six exported fields, against one for service and three for git:
Compiled []byte `json:"-" yaml:"-"`
CompilerVersion uint32 `json:"-" yaml:"-"`
FuncName string
ParamNames []string
NumParams int
OriginalPos stringFour of them carry no codec tag, so unlike git's ref/head they participate in the default marshaling of any structure that embeds them.
It carries the Starlark-callable path. A function resource holds a compiled *starlark.Function, and its bytecode travels in the graph document's content section — pkg/op/starlarkbridge/invoker.go exists for it. That makes its Unpack the most load-bearing of the four: rehydration has to reconstitute an executable, not just an identity.
Compiled and CompilerVersion are runtime state, not identity. Whether they belong on a sealed interface at all is a design question the other providers do not raise.
What the survey found — smaller than the file counts suggest
Five files outside the package name function.Resource. Four are doc comments (pkg/op/planner.go, pkg/op/receiver_registry.go, pkg/op/starlarkbridge/invoker.go, pkg/op/provider/mem/resource.go) — prose, not field reads.
The one real external use is a test assertion:
arrivedFunction, ok := transported[functionResource.URI()].(*function.Resource)pkg/op/provider/plan/lifecycle_api_test.go:774 — a concrete type assertion that becomes .(function.Resource) under sealing. That is the first out-of-package consumer any phase has had to change, and the plan anticipated it: "a later phase with an out-of-package caller will have to decide differently."
Inherited from earlier phases
- Register both
op.RegisterResourceImplementationandop.RegisterResourceMintfrom the provider's init. - Identity governs the type id and the registry name; the implementation governs only reflection (Seal git and appnet #642).
- A codec cannot set unexported fields —
functiondoes not use thetype aliasembed, so that specific trap does not apply, but any decode path assigning through exported fields needs the same scrutiny. - The generator carries method metadata across on its own since The generator must derive resource method metadata from the implementation, not the interface #658; a method that must be dispatchable still has to be declared on the interface.
Exit
function's resource is a sealed interface over an unexported struct; its content-section round trip is pinned (bytecode in, executable out); the lifecycle_api_test.go assertion reads through the interface; and the announced method metadata is unchanged.
Activity
David-Noble-at-work commentedon Aug 25, 2026
memmoves into this issue (USER, 2026-08-24). It cannot be sealed separately.function.Resourceembedsmem.Resourcecross-package:Sealing
memmakesmem.Resourcean interface and its struct unexported, and Go cannot embed an unexported type from another package. The two are coupled at compile time.Verified as isolated rather than a pattern: this is the only cross-package resource embed in the tree.
The shape agreed
function.resourceembeds themem.Resourceinterface rather than the struct. That is legal and preserves the method set — but it is a real semantic change worth reviewing carefully:functiongoes from is-amem.Resourceby value to holds-amem.Resource. Storage, zero-value behaviour, and anything relying on the embedded struct being addressable all shift.memitself is small — one exported field,Hash— and four files namemem.Resourceoutside its package. Check which of those are doc comments before assuming consumer changes; every phase so far has found them to be prose.Scope is now: seal
mem, sealfunction, and resolve the embedding, in one PR.