Documentation
¶
Overview ¶
Package artifactref carries Harbor's in-process pass-by-reference tool-argument routing: a tool declares an artifact-reference PARAMETER in its typed input struct, the model supplies an artifact id, and the runtime resolves that id to the stored bytes at DISPATCH so the tool function reads content the model never saw.
Because in-process tool input schemas are reflection-derived from the Go type (internal/tools/drivers/inproc.RegisterFunc -> internal/tools/schema.Derive), a reference parameter is a declared FIELD TYPE rather than a hand-written schema convention:
type SummarizeArgs struct {
Doc artifactref.Ref `json:"doc"`
MaxWords int `json:"max_words"`
}
func summarize(ctx context.Context, in SummarizeArgs) (Out, error) {
body, err := in.Doc.Bytes() // the store's bytes, never the model's
...
}
The derived schema renders `doc` as a plain string, so the model authors `{"doc": "tool_ab12cd34ef56", "max_words": 200}` and never authors — or reads — content.
The substitution invariant ¶
A resolved artifact value never re-enters the model's context or the observable record. When the runtime substitutes a resolved value into a dispatched tool argument, that value is DISPATCH-LOCAL: it does not appear in the trajectory, in the observation the runtime interleaves into the next chat thread, in any canonical event payload, in an audit payload, or in a log. The model authored an id and continues to see an id; the substitution is the runtime's and it ends at the tool boundary.
The invariant is held three ways, in descending order of durability:
A bound on PRODUCTION. Substitute is the ONE call site at which a resolved value enters a dispatched argument, and ScanSubstitutionSites holds it to one mechanically. An invariant about where a value does not travel is enforced most durably by bounding where it is produced; enumerating every place it might arrive is the check that goes stale.
A projection bound on the CARRIER. Ref keeps the resolved bytes in an unexported field and projects itself as its id through every serialisation door the runtime has: Ref.MarshalJSON emits the id, Ref.String returns the id, and Ref.LogValue renders the id. A `Ref` that reaches `json.Marshal`, `fmt.Sprintf` or `slog` therefore emits an id rather than content, by construction rather than by discipline.
An arrival check at the LLM edge, which lives with the safety net rather than here (internal/llm.findContextLeak).
Extent ¶
This is the IN-PROCESS arm. The consumer runs inside the Runtime, so resolution is a store read and a struct field: no URL, no third party, no egress. Handing a remote consumer something it can dereference is a separate, deliberately unbuilt design — see the routing notes in docs/decisions.md.
Concurrent reuse ¶
Nothing in this package holds mutable package-level state. A Ref is decoded fresh per invocation and lives on the per-call argument value; the resolver rides the dispatch ctx, scoped to the dispatching run's identity, and is never read from a shared artifact.
Index ¶
Constants ¶
const KindSubstitutionValueRef = "substitution_writer_value_reference"
KindSubstitutionValueRef — the substitution writer named as a VALUE rather than called: assigned to a variable or field, passed as an argument, returned, or stored in a map. The bound this scan holds is on call sites, and a function value carries the call anywhere the value goes — so a reference outside call position is reported wherever it appears, INCLUDING from a file on the reviewed list. The list registers a site that substitutes; it does not hand out the writer.
const KindUnregisteredSubstitution = "unregistered_substitution_site"
KindUnregisteredSubstitution — a call to the substitution writer from a site that is not on the reviewed list, or a list entry that names no such call.
const PkgPath = "github.com/hurtener/Harbor/internal/tools/artifactref"
PkgPath is the import path of this package — the home of the substitution writer the scan holds to a reviewed list.
Variables ¶
var ErrEmptyID = errors.New("artifactref: artifact reference id is empty")
ErrEmptyID is returned when an argument supplies an artifact reference whose id is empty. An omitted optional reference and an explicitly empty one are different facts, and only the second is a malformed argument.
var ErrNoResolver = errors.New("artifactref: no artifact resolver on the dispatch context")
ErrNoResolver is returned when a tool argument carries an artifact reference but the dispatch context seats no resolver. It is a loud failure on purpose: a silently unresolved reference would hand the tool function a zero value and let the call proceed against nothing, which is the silent-degradation shape Harbor forbids.
var ErrNotAddressable = errors.New("artifactref: substitution target is not addressable")
ErrNotAddressable is returned by Substitute when it is handed something it cannot write through — anything other than a non-nil pointer to the decoded argument value.
var ErrUnresolved = errors.New("artifactref: reference was not resolved at dispatch")
ErrUnresolved is returned by Ref.Bytes when the reference was never resolved — either the argument omitted it, or the value was built outside a dispatch that ran Substitute.
Functions ¶
func Substitute ¶
Substitute is THE ONE call site at which a resolved artifact value enters a dispatched tool argument.
It walks the decoded argument value behind ptr, and for every supplied Ref it resolves the id through the resolver seated on ctx and binds the stored bytes onto that reference. Everything it writes is dispatch-local: the bytes live on the per-invocation argument value and are unreachable through any of Ref's serialisation doors.
It fails loudly rather than degrading: a supplied reference with no seated resolver is ErrNoResolver, an empty id is ErrEmptyID, and a resolver error is wrapped with the id that produced it. An UNSUPPLIED reference is left alone — an optional parameter the model omitted is not an error, and Ref.Bytes fails loudly if the tool reads it anyway.
Calls to this function are held to a reviewed list by ScanSubstitutionSites. Adding a second call site is a reviewed decision, not an edit.
func TypeContainsRef ¶
TypeContainsRef reports whether t can hold a Ref anywhere a reflective walk could reach and write. Callers precompute it once per registered tool so an argument type with no reference parameter never pays for the walk.
Interface-typed fields report false: a JSON decode cannot produce a Ref inside an `any`, and reflection cannot write through one. The schema deriver treats an empty interface as unconstrained for the same reason, so the two agree on what a reference parameter can be.
func WithResolver ¶
WithResolver seats r on ctx for the dispatch that follows. The dispatching runtime is the only caller: the resolver it seats is closed over the run's identity scope, so seating one elsewhere would be handing a tool a reach its run does not have.
Types ¶
type Ref ¶
type Ref struct {
// contains filtered or unexported fields
}
Ref is an artifact-reference tool PARAMETER.
On the wire (and in the derived JSON Schema) a Ref is a plain string: the artifact id the model authors. In the tool function it is a handle the runtime has already resolved — call Ref.Bytes for the stored content.
The resolved value is unexported and every serialisation door projects the id instead: Ref.MarshalJSON, Ref.String and Ref.LogValue. That is what makes "the resolved value never reaches the observable record" a property of the type rather than a rule contributors remember.
The zero Ref is a valid "not supplied" value: Ref.ID is empty and Ref.Bytes fails with ErrUnresolved.
func NewRef ¶
NewRef builds a Ref carrying id and no resolved value. It exists for callers assembling arguments in Go (tests, typed embedders) rather than decoding them from a model's JSON; the resulting Ref resolves at dispatch exactly as a decoded one does.
func (Ref) Bytes ¶
Bytes returns the stored artifact content the runtime resolved at dispatch. The returned slice is the tool's to read and MUST NOT be retained past the call or mutated — it is the per-invocation buffer, not a copy.
Fails with ErrUnresolved when the reference was never resolved, rather than returning an empty slice a caller could mistake for an empty artifact.
func (Ref) ID ¶
ID returns the artifact reference id the argument carried. It is the only part of a Ref that is safe to log, emit or persist.
func (Ref) LogValue ¶
LogValue renders the reference as its id, so `slog.Any("args", in)` over a tool's argument struct logs ids and never content.
func (Ref) MarshalJSON ¶
MarshalJSON emits the reference id. A Ref that reaches an event payload, a trajectory step, an audit view or a re-encoded argument map therefore serialises as the id the model authored — the substitution invariant expressed as a type property.
func (Ref) Resolved ¶
Resolved reports whether the runtime bound stored content to this reference at dispatch.
func (Ref) Size ¶
Size returns the byte length of the resolved content, or 0 when the reference is unresolved.
func (Ref) String ¶
String renders the reference as its id, so a `%v` or `%s` verb over a tool's argument struct prints ids and never content.
func (Ref) Supplied ¶
Supplied reports whether the argument carried this reference at all. An omitted optional reference reports false.
func (*Ref) UnmarshalJSON ¶
UnmarshalJSON accepts the artifact id as a JSON string. A JSON null leaves the reference unsupplied.
type Resolver ¶
Resolver resolves an artifact reference id to its stored bytes. The dispatching runtime seats one on the ctx, closed over the run's own identity scope, so a tool reaches the bytes its own run's identity reaches and no others — there is no identity logic in any tool.
A resolver reports a missing artifact as an error, never as empty content: a reference the model authored against nothing is a fact the tool must be told about.
type ResolverFunc ¶
ResolverFunc adapts a function to Resolver.
func (ResolverFunc) ResolveArtifact ¶
ResolveArtifact implements Resolver.
type Violation ¶
Violation is one breach found by the scan: the offending file (relative to the scanned root), the 1-based line, the kind of breach, and a detail that names the fix rather than only the mismatch.
func ScanSubstitutionSites ¶
ScanSubstitutionSites walks the Go source under root and flags every call to the substitution writer that is not on the reviewed list.
allow maps a repo-relative file path to the reason that file is permitted to substitute. The list is short by construction: binding a resolved artifact value into a dispatched argument is the runtime's act at the tool boundary, and it happens in one place.
Returns the violations and the number of Go files read.