urn

package module
v0.0.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 16, 2026 License: MIT Imports: 4 Imported by: 0

README

urn-lib-go

Hadron URN compose / parse / normalize for Go.

Paired with urn-lib-js. Both implementations run the same conformance corpus (fixtures/corpus.json) — the corpus is the contract, so the two languages cannot drift. This exists to replace the copy-pasted URN parsers that had already drifted across hadron-server / portal / docs / cli (hadron-server#239, #693).

Status

Increment 1 — v1-parity core. Ports the pure, self-contained slice of hadron-server/src/lib/urn.ts verbatim, behind the shared corpus:

  • scheme — HasSchemePrefix, NormalizeScheme, CanonicalScheme, LegacyScheme
  • registry — the locked type registry (CoreTypes, RoleMarkers, reserved slugs, …)
  • normalize — NormalizeUrnForLookup, LegacyMemoryUrnToCanonical, AgentSlugFromUrn
  • slug — ValidateAtomShape, ValidateUserSlug, ValidateOrgSlug, DeriveSlugFromName
  • legacy — ParseUrnInput, FormatUrn, ValidateUrnType (the pre-021 surface)
  • parser — ParseUrn (the canonical-form parser, D11 cats 1 + 4), IsParserCanonical, ToParserCanonical
  • compose — FormatCanonicalUrn, ComposeNodeUrn, ComposeEdgeUrn
  • errors — ParseError + the machine-stable Reason codes

Not yet ported (later increments, gated by the same corpus): the qualification family (AssertFullyQualifiedUrn, SplitNodeUrn) and the install/migration helpers, then the grammar-v2 flat forms (hadron-server#694).

Usage

import urn "github.com/hadron-memory/urn-lib-go"

urn.NormalizeUrnForLookup("acme.com::specs::cor:urn") // "acme.com:specs:cor:urn"

if err := urn.ValidateOrgSlug("Acme.com"); err != nil {
    var pe *urn.ParseError
    if errors.As(err, &pe) {
        fmt.Println(pe.Reason) // "slug-not-lowercase"
    }
}

The conformance corpus

fixtures/corpus.json is a synced copy of the canonical corpus in urn-lib-js. Each case is { fn, in, out?, throws? }; corpus_test.go runs every case. Add behavior by adding a corpus case, not by editing a test in one language.

go test ./...

License

MIT © Baragaun, Inc.

Documentation

Overview

Package urn is the Go implementation of the Hadron URN library, paired with @hadron-memory/urn-lib-js. Both run the SAME conformance corpus (fixtures/corpus.json) — the corpus is the contract, so the two cannot drift.

This is increment 1: the pure "scheme + registry + normalize + slug" slice, ported verbatim (v1 parity) from hadron-server/src/lib/urn.ts. parse/format/ compose and the grammar-v2 flat forms land in later increments.

Index

Constants

View Source
const (
	CanonicalScheme = "hrn"
	LegacyScheme    = "urn"
)

Scheme prefixes (issue #239). hrn: is canonical; legacy urn: is accepted on input forever. Every emission path composes hrn:.

View Source
const MaxAtomLen = 64

MaxAtomLen is the maximum length of a single URN atom / slug, in bytes (spec cor:urn:010:01, FR-017). Exported as the single source of truth so lib internals and server-side minters stop re-typing the bare literal 64 (#715).

Variables

View Source
var CoreTypes = []string{
	"agent", "app", "app-user", "ai-config", "asset", "edge",
	"license", "memory", "node", "org", "platform", "reference",
	"secret", "session", "subscription", "usage", "user",
}

CoreTypes are the core URN types.

View Source
var DisplayURNTypes = []string{"org", "memory", "agent", "app", "node", "user", "apprun", "worker"}

DisplayURNTypes — the visible URN kinds rendered as a typed chip. Narrower than the canonical registry on purpose: a new visible kind must be added here too, or ParseDisplayUrn renders it as hrn:unknown:...

View Source
var NodeParts = []string{"condition", "data"}

NodeParts are the node parts.

View Source
var NodeRoles = []string{
	"chat", "chat-message", "config", "conversation", "event", "goal", "stage",
}

NodeRoles are the node roles.

View Source
var NodeTypes = []string{
	"abstract", "partial", "parent", "plan", "prompt", "record", "task", "review",
}

NodeTypes are the node types.

View Source
var RoleMarkers = []string{"system", "app-mem", "app-user", "group-mem", "priv", "anon"}

RoleMarkers are the memory-role markers (slug validation reserves their prefixes).

View Source
var TypeMarkers = map[string]bool{"app": true, "agent": true, "memory": true}

TypeMarkers are type-marker words that may appear inside path-segments.

View Source
var V2URNTypes = []string{
	"org", "user", "mem", "agent", "app", "node", "edge", "asset", "secret",
	"apprun", "worker", "noderev", "appkey", "aiconf", "tool", "server", "userapikey",
	"agentschedule", "agentwebhook", "license", "subscription", "usage",
	"reference", "session", "platform",
}

V2URNTypes — the v2 type registry (SEEDED; finalized in #698). Note the renames vs v1 (memory->mem) and additions (apprun, noderev, appkey, ...); `data` is demoted to a #fragment and is NOT a type.

Functions

func AgentSlugFromUrn

func AgentSlugFromUrn(agentURN string) string

AgentSlugFromUrn extracts an agent's slug path, stripping the leading org segment. Robust to both legacy single-colon and canonical double-colon forms.

func AssertFullyQualifiedUrn

func AssertFullyQualifiedUrn(input, expectedType string) error

AssertFullyQualifiedUrn rejects inputs that lack the fully-qualified shape for expectedType. Checks SHAPE, not full canonical grammar.

func ComposeAppRunUrnV2

func ComposeAppRunUrnV2(root, app, runID string) (string, error)

ComposeAppRunUrnV2 composes hrn:apprun:<root>:<app>:<run-id>.

func ComposeDataFragmentV2

func ComposeDataFragmentV2(parentURN string) (string, error)

ComposeDataFragmentV2 appends the #data fragment to a v2 node or apprun URN.

func ComposeEdgeUrn

func ComposeEdgeUrn(memURN, loc string) (string, error)

ComposeEdgeUrn builds a canonical hrn:edge:<mem>::<loc> URN.

func ComposeInstalledAgentUrn

func ComposeInstalledAgentUrn(appURN, agentURN string) (string, error)

ComposeInstalledAgentUrn composes the R2 canonical install URN for an Agent installed in an App: hrn:agent:<installing-org>::<app-slug>::<author-org>:<agent-slug>. Inputs MUST be 2-segment (org + slug) URNs. Install-by-self collapses via cat 1.

func ComposeNodeRevUrnV2

func ComposeNodeRevUrnV2(root, mem, loc, rev string) (string, error)

ComposeNodeRevUrnV2 composes hrn:noderev:<root>:<mem>:<loc...>:<rev>. `loc` may be a multi-atom node loc (colon-joined); it is split into its atoms so the terminal rev stays end-anchored.

func ComposeNodeUrn

func ComposeNodeUrn(memURN, loc string) (string, error)

ComposeNodeUrn builds a canonical hrn:node:<mem>::<loc> URN.

func ComposeSecretUrnV2

func ComposeSecretUrnV2(root, name string) (string, error)

ComposeSecretUrnV2 composes hrn:secret:<root>:<name> (org/user root + name).

func ComposeUrnV2

func ComposeUrnV2(typ, root string, segments ...string) (string, error)

ComposeUrnV2 composes a canonical grammar-v2 flat URN.

func ComposeWorkerUrnV2

func ComposeWorkerUrnV2(root, app, workerSlug string) (string, error)

ComposeWorkerUrnV2 composes hrn:worker:<root>:<app>:<worker-slug> — a Worker, the named casting of an Agent into an App (cor:dmo:050:11). The leaf atom is the Worker's stored `slug` column, DERIVED from its name at cast time and iterated to uniqueness; it is never the display name, which carries no charset constraint and may collide after slugification (cor:agt:020:02).

func DeriveSlugFromName

func DeriveSlugFromName(name string) (string, error)

DeriveSlugFromName derives a valid, lowercase slug atom from a free-form name. Slugification only — it does NOT reject reserved words. Returns ReasonEmptyDerivedSlug when nothing usable survives.

func FormatCanonicalUrn

func FormatCanonicalUrn(typ, bareValue string) (string, error)

FormatCanonicalUrn composes and canonicalizes a full URN from an entity type and a bare-stored value (legacy single-colon or canonical :: hierarchy). Always returns the canonical hrn:<type>: form.

func FormatUrn

func FormatUrn(typ, value string) string

FormatUrn formats a bare value as a typed canonical URN string.

func HasSchemePrefix

func HasSchemePrefix(input string) bool

HasSchemePrefix reports whether input leads with a scheme prefix.

func IsFlatV2

func IsFlatV2(input string) bool

IsFlatV2 reports whether input is already a canonical grammar-v2 flat URN.

func IsParserCanonical

func IsParserCanonical(input string) bool

IsParserCanonical reports whether ParseUrn(input).ParserCanonical == input with no rewrites.

func IsValidSlug

func IsValidSlug(slug string) bool

IsValidSlug is the boolean form of ValidateUserSlug: true when slug is a legal NEW entity slug (charset + length + reserved-word + lowercase), false on any violation. Convenience predicate for callers that don't want an error (#715).

func LegacyMemoryUrnToCanonical

func LegacyMemoryUrnToCanonical(bareLegacyURN string) string

LegacyMemoryUrnToCanonical converts a bare legacy single-colon memory URN to canonical :: hierarchy, preserving the marker:id boundary for role-marker memories. Input MUST be the bare legacy form.

func NormalizeScheme

func NormalizeScheme(input string) string

NormalizeScheme rewrites a leading legacy urn: scheme to canonical hrn:. Bare and already-canonical inputs pass through.

func NormalizeUrnForLookup

func NormalizeUrnForLookup(bareURN string) string

NormalizeUrnForLookup collapses the canonical :: hierarchy form to the bare single-colon lookup form.

func ToParserCanonical

func ToParserCanonical(input string) (string, error)

ToParserCanonical parses and returns the parser-layer canonical form.

func ValidateAtomShape

func ValidateAtomShape(input, atom string) error

ValidateAtomShape checks the FR-016 charset + FR-017 length only. Deliberately case-lenient (the read/parse path); the lowercase rule is enforced at create in ValidateUserSlug.

func ValidateOrgSlug

func ValidateOrgSlug(slug string) error

ValidateOrgSlug validates an org slug: it must be bare (no scheme prefix, no colon), reuse the shared slug rules, and — for a NEW org root (#692) — be a dotted domain. Create/rename rule only; the parse/read path stays lenient.

func ValidateUrnType

func ValidateUrnType(parsed LegacyParsedURN, expected string) *string

ValidateUrnType returns nil if the parsed type matches expected (with the unknown-always-ok and loc-where-node-expected exceptions), otherwise a pointer to the mismatch message. A *string (nil = ok) mirrors the JS `string | null`.

func ValidateUserHandle

func ValidateUserHandle(handle string) error

ValidateUserHandle validates a user handle at create/rename (#692): a ValidateUserSlug slug that must additionally be dot-free, so it stays charset-disjoint from a dotted org root in the shared principal pool. Registration policy, not a parse rule.

func ValidateUserSlug

func ValidateUserSlug(slug string) error

ValidateUserSlug validates a slug at create/rename: charset + length + reserved-word rejection + the lowercase-canonical rule (#575).

Types

type LegacyParsedURN

type LegacyParsedURN struct {
	Type  string `json:"type"`
	Value string `json:"value"`
}

LegacyParsedURN is the result of ParseUrnInput. JSON tags match the corpus.

func ParseUrnInput

func ParseUrnInput(input string) LegacyParsedURN

ParseUrnInput strips the hrn:/urn:<type>: or loc: prefix and returns the type and bare value. The v2 `mem` type word is normalized to `memory`. Unprefixed inputs are returned as type "unknown".

type NodeURNParts

type NodeURNParts struct {
	MemoryURN string `json:"memoryUrn"`
	Loc       string `json:"loc"`
}

NodeURNParts is the result of SplitNodeUrn. JSON tags match the corpus.

func SplitNodeUrn

func SplitNodeUrn(input string) (NodeURNParts, error)

SplitNodeUrn splits a fully-qualified node URN into its memory URN and loc. Self-validating (AssertFullyQualifiedUrn(input, "node")).

type NotQualifiedError

type NotQualifiedError struct {
	OffendingValue string
	ExpectedType   string
}

NotQualifiedError is returned when a non-ID-shaped input fails URN qualification. Code() returns the stable cross-language contract handle.

func (*NotQualifiedError) Code

func (e *NotQualifiedError) Code() string

Code is the stable contract handle (mirrors urn-lib-js UrnNotQualifiedError.code).

func (*NotQualifiedError) Error

func (e *NotQualifiedError) Error() string

type ParseError

type ParseError struct {
	Input            string
	Reason           Reason
	OffendingSegment string
}

ParseError is returned by every validation/parse entry point on any acceptance violation.

func (*ParseError) Error

func (e *ParseError) Error() string

type ParseForResult

type ParseForResult struct {
	Canonical string `json:"canonical"`
	IsLegacy  bool   `json:"isLegacy"`
}

ParseForResult is the ParseFor envelope. JSON tags match the corpus.

func ParseFor

func ParseFor(row UrnRow) (ParseForResult, error)

ParseFor dispatches per-row: a normalized row is parsed canonically and its stored URN returned unchanged (drift throws; scheme differences tolerated); a legacy row is validated with the legacy parser (unknown shape throws).

type ParsedDisplayURN

type ParsedDisplayURN struct {
	Type      string `json:"type"`
	BareValue string `json:"bareValue"`
	FullURN   string `json:"fullUrn"`
}

ParsedDisplayURN is the result of ParseDisplayUrn. JSON tags match the corpus.

func ParseDisplayUrn

func ParseDisplayUrn(value, typeHint string) ParsedDisplayURN

ParseDisplayUrn parses a displayable URN for chip rendering. `typeHint` is "" for none. Auto-detects a scheme-prefixed display kind; else applies the hint to a BARE value only; else falls back to "unknown".

type ParsedNodeRevURNV2

type ParsedNodeRevURNV2 struct {
	Scheme string `json:"scheme"`
	Type   string `json:"type"`
	Root   string `json:"root"`
	Memory string `json:"memory"`
	Loc    string `json:"loc"`
	Rev    string `json:"rev"`
}

ParsedNodeRevURNV2 is the END-ANCHORED decomposition of a hrn:noderev URN.

func ParseNodeRevUrnV2

func ParseNodeRevUrnV2(input string) (ParsedNodeRevURNV2, error)

ParseNodeRevUrnV2 parses and END-ANCHORED-decomposes a hrn:noderev URN (#696). The LAST atom is the revision id; the FIRST post-root atom is the memory slug; everything between is the variable-length, opaque node loc.

type ParsedURN

type ParsedURN struct {
	Type                          string   `json:"type"`
	PathSegments                  []string `json:"pathSegments"`
	ParserCanonical               string   `json:"parserCanonical"`
	InputForm                     string   `json:"inputForm"`
	ParserRewrites                []string `json:"parserRewrites"`
	NeedsResolverCanonicalization bool     `json:"needsResolverCanonicalization"`
}

ParsedURN is the result of ParseUrn. JSON tags match the conformance corpus.

func ParseUrn

func ParseUrn(input string) (ParsedURN, error)

ParseUrn parses a URN string, applying parser-layer canonicalization (D11 cats 1, 4). Returns a *ParseError on any acceptance violation. The v1 grammar is tried first; a v1-rejected input that is a valid flat grammar-v2 URN (single-colon, pool-rooted, `mem` type word — #697) is delegated to ParseUrnV2 and mapped into the ParsedURN shape. v1-accepted inputs keep their exact v1 result, so no existing behavior changes.

type ParsedURNV2

type ParsedURNV2 struct {
	Scheme   string   `json:"scheme"`
	Type     string   `json:"type"`
	Root     string   `json:"root"`
	Segments []string `json:"segments"`
	// Fragment is present only when the URN carried a #<fragment> suffix.
	Fragment string `json:"fragment,omitempty"`
}

ParsedURNV2 is the result of ParseUrnV2. JSON tags match the corpus.

func ParseUrnV2

func ParseUrnV2(input string) (ParsedURNV2, error)

ParseUrnV2 parses a STRICT grammar-v2 flat URN. Rejects v1 constructs (the :: hierarchy separator and the @/user: root sigil). Enforces the #696 per-entity arity and the #data fragment rules.

type Reason

type Reason string

Reason is a machine-stable failure code. These ARE part of the fixture contract (shared with urn-lib-js); the human message text is not.

const (
	ReasonUnknownType          Reason = "unknown-type"
	ReasonMalformedGrammar     Reason = "malformed-grammar"
	ReasonEmptySegment         Reason = "empty-segment"
	ReasonTrailingDoubleColon  Reason = "trailing-double-colon"
	ReasonReservedWordSlug     Reason = "reserved-word-slug"
	ReasonInvalidCharset       Reason = "invalid-charset"
	ReasonSlugNotLowercase     Reason = "slug-not-lowercase"
	ReasonSlugTooLong          Reason = "slug-too-long"
	ReasonLocSegmentRejected   Reason = "loc-segment-rejected"
	ReasonInvalidSegmentShape  Reason = "invalid-segment-shape"
	ReasonEmptyBareValue       Reason = "empty-bare-value"
	ReasonAlreadyPrefixedValue Reason = "already-prefixed-bare-value"
	ReasonOrgUrnNotBare        Reason = "org-urn-not-bare"
	ReasonOrgRootNotDotted     Reason = "org-root-not-dotted"
	ReasonHandleHasDot         Reason = "handle-has-dot"
	ReasonEmptyDerivedSlug     Reason = "empty-derived-slug"
)

type UrnRow

type UrnRow struct {
	URN             string
	URNNormalizedAt *time.Time
}

UrnRow is the minimal row shape for ParseFor. A nil URNNormalizedAt means the row is pre-normalization (legacy shape).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL