multisite

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 5, 2026 License: MPL-2.0 Imports: 8 Imported by: 0

README

Multi-Site resolver

The optional github.com/AChWorks/achrix/multisite package shares the existing Foundation Go module and release. It provides an authorized, immutable exact-authority lookup, returning only a stable SiteID. It changes no mandatory Core concept and acquires no database, storage, network connection or worker. A single-site product may omit it entirely.

Source and Go documentation own exact signatures. The runnable example composes two declared sites through the public Core and Service boundary. It uses a product-owned principal and narrowly scoped policy; products explicitly wire the Service to the Application that composes its Module. A Module instance belongs to one Application.

Public contract Behavior
Config{Sites: []Site} / New Trusted finite inventory, validated and copied before effects. No fixed site-count ceiling. Each site has at least one authority; duplicate IDs and duplicate authority bindings, including repeated aliases for one site, fail with ErrConfiguration.
Site{ID, Authorities, Disabled} IDs match ASCII [A-Za-z0-9][A-Za-z0-9_-]{0,63} and retain exact case. Disabled defaults to false; disabled mappings remain reserved in the inventory.
Module.Descriptor Cheap side-effect-free metadata: achrix.multisite, packaged achrix.Version(), only achrix.multisite.resolve ABI 1, requiring Core authorization ABI 2. Each call returns fresh metadata slices.
Module.Start / Ready / Stop One-shot context-bounded startup, local readiness and atomic terminal admission closure. Stop also closes admission when its context is canceled; there are no acquired resources to clean.
NewService / Service.Resolve Typed operation authorizes the exact canonical authority, then checks owning lifecycle and returns only the stable site ID. No inventory listing or binding data is exposed.

Authorities are exact lower-case ASCII DNS hostname syntax (labels of 1–63 letters/digits/hyphens with alphanumeric ends, name at most 253 bytes, a letter in the final label), canonical dotted IPv4, or bracketed canonical IPv6. IPv6 uses Go net/netip.Addr.String() spelling, including the canonical dotted tail for IPv4-mapped IPv6. ASCII xn-- labels are accepted as bounded DNS syntax; the resolver performs no IDNA conversion or punycode attestation. Products own the correctness of their configured internationalized DNS names.

Each host may have an explicit decimal port from 1 to 65535 with no leading zeros. Absent port, :443 and :8443 are distinct inventory keys; one never substitutes for another. Schemes, paths, userinfo, query/fragment, whitespace, wildcards, trailing dots, Unicode, zones, upper-case/expanded IPv6 and legacy numeric/hexadecimal IPv4 spellings are rejected. There is no normalization, default-port inference, suffix matching, redirect, DNS query or discovery. Syntax validation is bounded by a maximum 259-byte authority before policy; construction uses linear maps and lookup work is independent of configured inventory size.

Resolve derives a context capped at one second, preserving an earlier caller deadline or cancellation. Malformed input returns ErrInput before policy and reveals no mapping existence. Valid input is authorized before binding lookup: policy denial remains Core ErrDenied, evaluation failure is safe ErrAuthorizationUnavailable, and a non-ready Application returns Core ErrNotReady. Unknown and disabled authorities share ErrNotFound; unavailable owning Module state returns ErrUnavailable. Errors return an empty site ID. Actual context cancellation/deadline is preserved. Policies must honor cancellation and support concurrent calls; a synchronous in-process callback cannot be forcibly terminated by this resolver.

The final lifecycle check and lookup serialize with Stop. Calls cannot resolve against a stopped Module, including a Stop during policy evaluation. Core shutdown closes/cancels/drains its policy admission before stopping Modules; a direct Module Stop closes local admission without waiting for an external callback. Products still stop ingress and drain their whole domain operations: resolution linearizes before stop and does not grant permission to continue a domain operation after shutdown. Configuration changes require a new validated composition; mutating the original Config changes nothing in a constructed Module.

Products own actual TLS, Host and trusted-proxy validation; authentication of the principal; identity-to-Application/store bindings; account/grant checks; domain draining; and data, Media, cache, jobs and recovery isolation. Resolving an address supplies neither trusted domain ownership nor account/resource access. A site ID includes no DSN, path, secret, credential or grant. Reusing this Module creates no shared account or store.

The package tests prove immutable construction, exact mapping, authorized failure, independent instances, optional Core composition, cancellation and lifecycle/concurrency behavior. They do not establish real account/database/storage/TLS isolation or preservation of site-owned state under a shared Foundation update. AChrix #4 retains those whole-outcome criteria; the actual product proof belongs to Rixa #3. A pure resolver test cannot close either consumer criterion.

Documentation

Overview

Package multisite resolves an authorized exact authority to a stable site ID. Products own ingress trust, authentication, site bindings and domain isolation.

Index

Examples

Constants

View Source
const Resolve = "achrix.multisite.resolve"

Variables

View Source
var (
	ErrConfiguration = errors.New("invalid multisite configuration")
	ErrInput         = errors.New("invalid multisite input")
	ErrUnavailable   = errors.New("multisite unavailable")
	ErrNotFound      = errors.New("multisite site not found")
)

Functions

This section is empty.

Types

type Config

type Config struct{ Sites []Site }

Config is trusted, finite product composition input. New copies its inventory; replacing configuration requires a new validated Module/Application composition. No arbitrary site-count limit is imposed; products own inventory/resource budgets.

type Module

type Module struct {
	// contains filtered or unexported fields
}

Module owns immutable bindings and one-shot lifecycle state. It acquires no storage/network resources and starts no workers. Do not share a Module between Applications; products explicitly wire its Service to its owning Application.

func New

func New(config Config) (*Module, error)

New validates the complete inventory before any runtime effect. Every site requires at least one authority; duplicate IDs or authorities (even repeated aliases for one site) are rejected. Validation and copying are linear in input.

func (*Module) Descriptor

func (m *Module) Descriptor() achrix.Descriptor

func (*Module) Ready

func (m *Module) Ready(ctx context.Context) error

func (*Module) Start

func (m *Module) Start(ctx context.Context) error

func (*Module) Stop

func (m *Module) Stop(ctx context.Context) error

Stop closes Module admission atomically, including with a canceled context. There are no acquired resources to drain/clean. Core owns admitted policy cancellation/drain; products own ingress and complete domain-operation drain.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service traverses Core authorization and the owning Module lifecycle. app must be the Application composing module; typed collaborators are explicitly product-wired, as with other Foundation Services, never discovered from metadata.

func NewService

func NewService(app *achrix.Application, module *Module) (*Service, error)

func (*Service) Resolve

func (s *Service) Resolve(parent context.Context, actor achrix.Principal, authority string) (SiteID, error)

Resolve validates syntax without revealing binding existence, then authorizes the exact authority before lookup. A derived maximum one-second context retains any earlier caller deadline/cancellation. Policies are trusted synchronous code and must honor cancellation; this API cannot forcibly interrupt callbacks. Unknown and disabled bindings share ErrNotFound. The result is only a site ID: TLS/Host/proxy trust, resource access and site-to-Application/store bindings are separate product responsibilities. No normalization, suffix/default-port fallback or discovery occurs. Lookup is constant time in configured inventory size.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/AChWorks/achrix"
	"github.com/AChWorks/achrix/multisite"
)

func main() {
	resolver, err := multisite.New(multisite.Config{Sites: []multisite.Site{
		{ID: "site-a", Authorities: []string{"a.example", "a.example:443"}},
		{ID: "site-b", Authorities: []string{"b.example"}},
	}})
	if err != nil {
		panic(err)
	}
	// This example's product policy grants only authority resolution. Actual
	// authentication, trusted ingress and site resource authorization are separate.
	policy := achrix.PolicyFunc(func(_ context.Context, actor achrix.Principal, capability, authority string) error {
		if actor == "product-router" && capability == multisite.Resolve && authority == "a.example" {
			return nil
		}
		return achrix.ErrDenied
	})
	app, err := achrix.New(achrix.Config{StartupTimeout: time.Second, ShutdownTimeout: time.Second}, policy, resolver)
	if err != nil {
		panic(err)
	}
	service, err := multisite.NewService(app, resolver)
	if err != nil {
		panic(err)
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	if err = app.Start(ctx); err != nil {
		panic(err)
	}
	id, err := service.Resolve(ctx, "product-router", "a.example")
	if err != nil {
		panic(err)
	}
	fmt.Println(id)
	cleanup, cleanupCancel := context.WithTimeout(context.Background(), time.Second)
	defer cleanupCancel()
	if err = app.Shutdown(cleanup); err != nil {
		panic(err)
	}
}
Output:
site-a

type Site

type Site struct {
	ID          SiteID
	Authorities []string
	Disabled    bool
}

Site declares exact canonical authorities. Disabled sites retain their bindings but resolve like unknown authorities; the default is active.

type SiteID

type SiteID string

SiteID is a stable product-selected ASCII identity, not a permission grant. Valid identities match [A-Za-z0-9][A-Za-z0-9_-]{0,63}.

Jump to

Keyboard shortcuts

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