WDP Part 7: Namespaces and Error Boundaries
Disambiguation and collision prevention for multi-service architectures
Abstract#
This document specifies Namespaces and the Error Boundary concept for the Waddling Diagnostic Protocol (WDP). Namespaces provide a mechanism for organizing error codes across multiple libraries, services, and applications, enabling disambiguation while allowing independent development. An error boundary defines the scope within which error codes are guaranteed to be unique, solving collision problems in multi-library applications and microservice architectures.
Specification Navigation: See STRUCTURE for an overview of all WDP specification parts.
Conformance#
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
1. Introduction#
1.1 Purpose#
The core WDP specification (Parts 1-5) defines error code structure within a single context. However, real-world systems face disambiguation challenges:
Problem 1: Multi-Library Applications
my_app/
ββ uses auth_lib β E.Auth.Token.001
ββ uses payment_lib β E.Auth.Token.001 (same code!)
ββ logs show: [E.Auth.Token.001] β which library?Problem 2: Microservice Architectures
UI β calls user_service β E.Database.Connection.021
UI β calls order_service β E.Database.Connection.021 (same code!)
UI sees: {"h":"Xy8Qz"} β which service?Namespaces solve these problems by defining error boundaries that provide disambiguation while enabling independent development.
1.2 Design Goals#
- Disambiguation: Identify the source of an error code
- Independence: Allow libraries/services to define codes without global coordination
- Automation: Enable error routing, catalog loading, and monitoring
- Backward Compatibility: Core 5-part codes remain valid (namespaces are optional)
- Simplicity: Flat namespace structure, no hierarchy
2. Error Boundary Concept#
2.1 Definition#
An error boundary is a named scope that guarantees the uniqueness of WDP error codes. It represents the ownership and context of diagnostic codes.
Formal Definition:
Error Boundary := A named scope B where all error codes within B are unique
Within boundary B:
- E.Auth.Token.001 β E.Auth.Token.002 (different codes)
- E.Auth.Token.001 β E.Database.Query.017 (different codes)
Across boundaries B1 and B2:
- B1:E.Auth.Token.001 is distinct from B2:E.Auth.Token.001
- Same structured code, different context2.2 Properties#
Uniqueness Scope:
- All error codes within a boundary MUST be unique
- Same code in different boundaries is permitted (different contexts)
Ownership:
- Each boundary MUST have exactly one owner (team, library, service)
- Owner is responsible for ensuring uniqueness within the boundary
Independence:
- Boundaries can define codes without coordination
- Changes within a boundary don't affect other boundaries
Global Identity:
- Boundary name + error code = globally unique identifier
- Enables disambiguation across the entire system
2.3 Relationship to Namespaces#
In WDP, namespaces are the implementation mechanism for error boundaries.
Error Boundary (abstract concept)
β
Namespace (concrete implementation)Every namespace defines an error boundary. The terms are used interchangeably in this specification.
3. Namespace Specification#
3.1 Definition#
A namespace is a string identifier that names an error boundary. It appears as a prefix to WDP error codes.
Format:
namespace:SEVERITY.COMPONENT.PRIMARY.SEQUENCE -> namespace_hash-code_hashExample:
auth_lib:E.Auth.Token.001 -> h4tYw2-81E9g3.2 Namespace Types#
Namespaces can represent different organizational units:
| Type | Example | Owner | Use Case |
|---|---|---|---|
| Library/Crate | auth_lib, tokio, serde | Library maintainers | External dependencies |
| Service | user_service, order_service | Service team | Microservices |
| Application | my_app, admin_panel | Application developers | Top-level applications |
| Module | backend, frontend, mobile | Module team | Monorepo components |
3.3 Namespace Selection Guidelines#
SHOULD:
- Use the library/crate/package name as namespace
- Keep namespace concise (8-16 characters ideal)
- Reflect ownership (who maintains the code)
SHOULD NOT:
- Include version numbers (e.g.,
auth_lib_v2) - Use generic names (e.g.,
lib,service) - Create hierarchical namespaces (e.g.,
org/service/module)
Examples:
Rust crate: tokio, serde, auth_lib
NPM package: express β express (or my_org_express if namespaced)
Python package: django, flask, user_auth
Maven artifact: com.acme.auth β acme_auth
Service: user_service, payment_gateway
Application: my_app, waddling_lang_tool4. Namespace Hash#
4.1 Purpose#
The namespace hash is a compact representation of the namespace for efficient transmission and global uniqueness.
4.2 Algorithm#
| Property | Value |
|---|---|
| Input | Namespace string (UTF-8 encoded) |
| Hash Function | xxHash64 |
| Seed | "wdp-ns-v1" (UTF-8 bytes: [0x77, 0x64, 0x70, 0x2D, 0x6E, 0x73, 0x2D, 0x76, 0x31]) |
| Encoding | Base62 |
| Output Length | Exactly 5 characters |
4.3 Computation#
Step 1: Encode namespace as UTF-8 bytes
"auth_lib" β [0x61, 0x75, 0x74, 0x68, 0x5F, 0x6C, 0x69, 0x62]
Step 2: Hash with xxHash64
hash_value = xxHash64(bytes, seed="wdp-ns-v1")
Step 3: Encode as Base62 (5 characters, left-padded with '0' if needed)
Base62(hash_value) β "h4tYw2"4.4 Seed Rationale#
Why "wdp-ns-v1" instead of "wdp-v1"?
Using a different seed for namespace hashes prevents collisions between namespace hashes and error code hashes:
Error code seed: "wdp-v1"
Namespace seed: "wdp-ns-v1"
Prevents:
hash("auth_lib", "wdp-ns-v1") β hash("E.AUTH.TOKEN.001", "wdp-v1")This eliminates any possibility of a namespace hash colliding with an error code hash.
4.5 Collision Probability#
With 5-character Base62 encoding (916,132,832 combinations):
| Namespaces | Collision Probability | Assessment |
|---|---|---|
| 1,000 | 0.05% | Negligible |
| 10,000 | 5.5% | Low |
| 100,000 | 68% | High (unlikely scale) |
For realistic scales (< 10,000 namespaces across an entire ecosystem), collisions are rare and acceptable.
5. Combined Error Identity#
5.1 Full Error Identity
A complete WDP error identity with namespace consists of 6 parts:
namespace:SEVERITY.COMPONENT.PRIMARY.SEQUENCE -> namespace_hash-code_hash
β β β β β β β
β β β β β β ββ Part 5: Code hash
β β β β β ββββββββββββββββ Namespace hash
β β β β ββββββββββββββββββββββββββ Part 4: Sequence
β β β βββββββββββββββββββββββββββββββββββ Part 3: Primary
β β βββββββββββββββββββββββββββββββββββββββββββββ Part 2: Component
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ Part 1: Severity
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Part 0: Namespace5.2 Dual Hash Format
Format: namespace_hash-code_hash
Example: h4tYw2-81E9g
Components:
- namespace_hash: 5 Base62 characters
- separator: single hyphen '-'
- code_hash: 5 Base62 characters
Total length: 11 characters5.3 Parsing
Input: "h4tYw2-81E9g"
if contains single '-':
parts = split('-')
namespace_hash = parts[0] # "h4tYw2"
code_hash = parts[1] # "81E9g"
else:
# Legacy format (no namespace)
namespace_hash = None
code_hash = input # "81E9g"5.4 Backward Compatibility
Systems using WDP without namespaces (Level 0-1 conformance) continue to work:
Without namespace (5-part):
[E.Auth.Token.001] -> 81E9g
Valid, no namespace required
With namespace (6-part):
auth_lib:[E.Auth.Token.001] -> h4tYw2-81E9g
Adds global context, core code unchanged6. Uniqueness Guarantees#
6.1 Within a Namespace (GUARANTEED)
All error codes within a single namespace MUST be unique.
Namespace: auth_lib
E.Auth.Token.001 β Unique
E.Auth.Token.002 β Unique (different from 001)
E.Auth.Session.001 β Unique (different component.primary)
W.Auth.Token.001 β Unique (different severity)Implementation Requirement: Namespace owners MUST ensure codes don't collide within their boundary.
6.2 Across Namespaces (PERMITTED)
The same error code structure MAY appear in different namespaces with different meanings.
Namespace: auth_lib
E.Auth.Token.001 β "Authentication token missing"
Namespace: payment_lib
E.Auth.Token.001 β "Payment authorization token missing"This is healthy collision - different contexts, different meanings, disambiguated by namespace.
6.3 Global Uniqueness (ACHIEVED)
Global uniqueness is achieved via the combined identifier:
auth_lib:E.Auth.Token.001 β h4tYw2-81E9g
payment_lib:E.Auth.Token.001 β k9Xmq-81E9g
^^^^^^^^^^ Different!The namespace hash ensures these resolve to different identifiers despite having the same structured code.
7. Scope and Ownership#
7.1 Ownership Model
Each namespace MUST have exactly one owner:
- Library namespace: Library maintainers
- Service namespace: Service team
- Application namespace: Application developers
Owner Responsibilities:
- Ensure all codes within the namespace are unique
- Document error codes in a catalog (RECOMMENDED)
- Maintain backward compatibility within the namespace
- Assign namespace to appropriate software artifact
7.2 Boundary Selection
Library Authors:
// Rust crate: auth-lib
pub const NAMESPACE: &str = "auth_lib";Service Developers:
# Kubernetes service
metadata:
name: user-service
wdp:
namespace: user_serviceApplication Developers:
// package.json
{
"name": "my-app",
"wdp": {
"namespace": "my_app"
}
}7.3 Multi-Artifact Considerations
Question: If my library has multiple crates/packages, should each have its own namespace?
Answer: Depends on error space:
Option A: Shared namespace (RECOMMENDED)
auth_lib_core β namespace: auth_lib
auth_lib_jwt β namespace: auth_lib
(All errors under one boundary)
Option B: Separate namespaces
auth_lib_core β namespace: auth_lib_core
auth_lib_jwt β namespace: auth_lib_jwt
(Independent error spaces)Use Option A unless crates are truly independent with no shared error semantics.
8. Format Specification#
8.1 Namespace Format
Pattern: [a-z][a-z0-9_]{0,31}
Character Set:
- Lowercase ASCII letters:
a-z - ASCII digits:
0-9 - Underscore:
_
Rules:
- MUST start with a lowercase letter
- MUST contain only lowercase letters, digits, and underscores
- MUST be 1-32 characters in length
- MUST NOT contain hyphens, periods, slashes, or other special characters
8.2 Regex Pattern
^[a-z][a-z0-9_]{0,31}$Explanation:
^- Start of string[a-z]- Must start with lowercase letter[a-z0-9_]{0,31}- Followed by 0-31 lowercase letters, digits, or underscores$- End of string
Total length: 1-32 characters
8.3 Valid Examples
a // valid: 1 char minimum
auth_lib // valid: typical library name
user_service // valid: typical service name
waddling_lang_tool // valid: 18 chars
my_app_v2 // valid: with version, if truly needed
tokio // valid: short, well-known name
acme_payment_gateway // valid: 31 chars - near limit8.4 Invalid Examples
Auth_lib // invalid: uppercase not allowed
auth-lib // invalid: hyphen not allowed
auth.lib // invalid: period not allowed
auth/lib // invalid: slash not allowed
_auth_lib // invalid: cannot start with underscore
2auth_lib // invalid: cannot start with digit
auth lib // invalid: space not allowed
this_is_a_very_long_namespace_name // invalid: 33 chars - too long9. Validation Rules#
9.1 Format Validation
Implementations MUST validate that namespaces:
- Start with a lowercase ASCII letter (
a-z) - Contain only lowercase letters, digits, and underscores
- Are between 1 and 32 characters in length
- Match the regex pattern
^[a-z][a-z0-9_]{0,31}$
9.2 Validation Examples
fn is_valid_namespace(ns: &str) -> bool {
if ns.is_empty() || ns.len() > 32 {
return false;
}
let mut chars = ns.chars();
// First character must be lowercase letter
if !matches!(chars.next(), Some('a'..='z')) {
return false;
}
// Remaining characters must be lowercase letter, digit, or underscore
chars.all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_'))
}
assert!(is_valid_namespace("auth_lib"));
assert!(!is_valid_namespace("Auth_lib"));
assert!(!is_valid_namespace("auth-lib"));10. Usage Patterns#
10.1 Library Pattern
// Define namespace constant
pub const NAMESPACE: &str = "auth_lib";
// Use in error definitions
#[derive(WdpError)]
#[wdp(namespace = "auth_lib")]
pub enum AuthError {
#[wdp(code = "E.Auth.Token.001")]
TokenMissing,
#[wdp(code = "E.Auth.Token.018")]
TokenExpired,
}
// Generated methods
impl AuthError {
pub fn namespace(&self) -> &str {
NAMESPACE
}
pub fn full_code(&self) -> String {
format!("{}:{}", NAMESPACE, self.code())
}
pub fn full_id(&self) -> String {
format!("{}-{}",
namespace_hash(NAMESPACE),
self.compact_id()
)
}
}10.2 Service Pattern
# service-config.yml
service:
name: user-service
wdp:
namespace: user_service
catalog: /etc/wdp/user_service_catalog.json10.3 Application Pattern
// Initialize with multiple namespaces
import { registerNamespace } from '@wdp/core';
// Register dependencies
registerNamespace('auth_lib');
registerNamespace('payment_lib');
registerNamespace('my_app');
// Errors carry namespace
const error = new WdpError({
namespace: 'my_app',
code: 'E.Application.Startup.001',
});
console.log(error.fullCode()); // "my_app:E.Application.Startup.001"
console.log(error.fullId()); // "pQ7Zw-mN3Yr"11. Automation and Routing#
11.1 Error Routing
Namespaces enable automatic routing based on error source:
# routing-config.yml
namespaces:
user_service:
team: "User Team"
slack: "#user-service-alerts"
pagerduty: "user-service-oncall"
payment_lib:
team: "Payment Team"
slack: "#payment-alerts"
pagerduty: "payment-oncall"fn route_error(error: &WdpError) {
let config = ROUTING_CONFIG.get(error.namespace())?;
match error.severity() {
Severity::Error | Severity::Critical => {
page_oncall(config.pagerduty);
post_to_slack(config.slack, error);
}
_ => log_only(error),
}
}11.2 Catalog Loading
// Smart catalog loading
async function expandError(compactId) {
const [nsHash, codeHash] = compactId.split('-');
// Resolve namespace from hash
const namespace = await resolveNamespace(nsHash);
// Load only the required catalog
const catalog = await loadCatalog(namespace);
// Expand error
return catalog.lookup(codeHash);
}11.3 Monitoring and Alerting
-- Group errors by namespace
SELECT namespace, COUNT(*) as error_count
FROM error_logs
WHERE timestamp > NOW() - INTERVAL '1 hour'
GROUP BY namespace
ORDER BY error_count DESC;
-- Namespace-specific SLA monitoring
SELECT
namespace,
COUNT(*) FILTER (WHERE severity = 'E') as error_count,
COUNT(*) as total_count,
(COUNT(*) FILTER (WHERE severity = 'E')::float / COUNT(*)) as error_rate
FROM logs
WHERE timestamp > NOW() - INTERVAL '1 day'
GROUP BY namespace
HAVING error_rate > 0.01; -- Alert if > 1% error rate12. Presentation Guidelines#
12.1 Context-Aware Display
Namespace visibility SHOULD depend on context:
| Context | Show Namespace? | Format |
|---|---|---|
| User-facing UI | No | [E.Auth.Token.001] |
| Developer console | Metadata | Separate line or field |
| Structured logs | Inline | auth_lib:E.Auth.Token.001 |
| JSON API | Field | {"namespace":"auth_lib","code":"..."} |
| Monitoring | Dimension | Group/filter by namespace |
12.2 Console Display Examples
Normal Mode (User-Facing):
β [E.Parser.Argument.001] -> 81E9g
error: Expected 'domain' keyword
--> state.travel:10:5Verbose Mode (Developer):
β [E.Parser.Argument.001] -> h4tYw2-81E9g
namespace: waddling_lang_tool
error: Expected 'domain' keyword
--> state.travel:10:5Structured Logs:
{
"level": "error",
"namespace": "waddling_lang_tool",
"code": "E.Parser.Argument.001",
"compact_id": "81E9g",
"namespace_hash": "h4tYw2",
"full_id": "h4tYw2-81E9g",
"message": "Expected 'domain' keyword"
}13. Relationship to Other Parts#
13.1 To Core Specification (Parts 1-5)
Namespaces are an optional extension to the core 5-part error structure.
Without namespace (Level 0-1):
[E.Auth.Token.001] -> 81E9g
With namespace (Level 4):
auth_lib:[E.Auth.Token.001] -> h4tYw2-81E9gThe core 5-part code remains unchanged; namespace is a prefix.
13.2 To Sequence Conventions (Part 6)
Sequence conventions apply within namespace boundaries:
auth_lib:E.Auth.Token.001 (001 = MISSING, per convention)
auth_lib:E.Auth.Token.018 (018 = STALE, per convention)
payment_lib:E.Auth.Token.001 (also MISSING, same convention)Conventions are namespace-independent.
13.3 To I18N (Part 8)
Namespaces and localization are orthogonal:
Namespace: auth_lib (fixed)
Language: en, es, ja (varies)
English: auth_lib:E.Auth.Token.001 -> "Token missing"
Spanish: auth_lib:E.Auth.Token.001 -> "Token faltante"
Japanese: auth_lib:E.Auth.Token.001 -> "γγΌγ―γ³γγγγΎγγ"Same namespace, different languages.
13.4 To Catalogs (Part 9)
Catalogs MAY be organized by namespace:
catalogs/
ββ auth_lib_catalog.json
ββ payment_lib_catalog.json
ββ user_service_catalog.jsonEach catalog includes its namespace:
{
"namespace": "auth_lib",
"namespace_hash": "h4tYw2",
"errors": { ... }
}13.5 To Presentation (Part 10)
Presentation layer uses namespace for context-aware display (see Section 12).
14. Design Rationale#
14.1 Why Flat Namespace (No Hierarchy)?
Considered: org/service/module (hierarchical)
Rejected:
- Complex parsing and validation
- Verbose (
acme_corp/user_service/auth_module) - Abuse potential (unlimited depth)
- Unnecessary (single-level sufficient)
Chosen: Flat snake_case
- Simple to parse and validate
- Concise (single token)
- Sufficient for disambiguation
14.2 Why snake_case Instead of kebab-case?
Considered: auth-lib (kebab-case)
Rejected:
- Collision with hash separator:
h4tYw2-81E9g(hyphen separates hashes) - Ambiguity: Is
auth-lib-81E9gnamespace-code or namespace-hash-code?
Chosen: auth_lib (snake_case)
- No collision (underscore β hyphen)
- Familiar (Python/Rust package naming)
- Clear parsing:
auth_lib:E.Auth.Token.001orh4tYw2-81E9g
14.3 Why Maximum 32 Characters?
Prevents abuse:
acme_corp_v2_payment_gateway_production_service_auth_module_v3
(Too long, unclear, poor naming)
acme_payment_gateway
(Concise, clear, descriptive)32 characters is sufficient for descriptive names while discouraging overly complex identifiers.
14.4 Why Not Make Namespace Core?
Namespaces are optional because:
- Simple systems don't need them (single app, no dependencies)
- Core should be minimal (only essential parts)
- Opt-in adoption (use when needed)
- Backward compatible (existing codes remain valid)
15. Examples#
15.1 Complete Error Identity Examples
Library error:
auth_lib:E.Auth.Token.001 -> h4tYw2-81E9g
Service error:
user_service:E.Database.Query.017 -> pQ7Zw-kN8Qz
Application error:
waddling_lang_tool:E.Parser.Syntax.003 -> mN3Yr-xY9Kp15.2 Multi-Library Application
// Application with multiple dependencies
// Dependency 1: auth_lib
let auth_error = auth_lib::TokenMissing;
assert_eq!(auth_error.full_code(), "auth_lib:E.Auth.Token.001");
assert_eq!(auth_error.full_id(), "h4tYw2-81E9g");
// Dependency 2: payment_lib (same code structure!)
let payment_error = payment_lib::TokenMissing;
assert_eq!(payment_error.full_code(), "payment_lib:E.Auth.Token.001");
assert_eq!(payment_error.full_id(), "k9Xmq-81E9g"); // Different hash!
// No collision - namespace disambiguates
assert_ne!(auth_error.full_id(), payment_error.full_id());15.3 Microservice Error Propagation
ββββββββββββββββ E.Database.Query.017 ββββββββββββββββ
β user_service β βββββββββββββββββββββββββ> β API Gateway β
ββββββββββββββββ ββββββββββββββββ
β
ββ Parse namespace
ββ user_service β User Team
ββ Route to #user-service-alerts
ββββββββββββββββββ E.Database.Query.017 ββββββββββββββββ
β order_service β βββββββββββββββββββββββββ> β API Gateway β
ββββββββββββββββββ ββββββββββββββββ
β
ββ Parse namespace
ββ order_service β Order Team
ββ Route to #order-service-alertsSame code structure, different namespaces, different routing!
Appendix A: Quick Reference#
Namespace Format
Pattern: [a-z][a-z0-9_]{0,31}
Regex: ^[a-z][a-z0-9_]{0,31}$
Length: 1-32 characters
Required: NO (optional extension)Namespace Hash
Algorithm: xxHash64
Seed: "wdp-ns-v1"
Encoding: Base62
Length: 5 charactersCombined Format
Full code: namespace:SEVERITY.COMPONENT.PRIMARY.SEQUENCE
Full hash: namespace_hash-code_hash
Example code: auth_lib:E.Auth.Token.001
Example hash: h4tYw2-81E9gAppendix B: Normative References#
- STRUCTURE - WDP error code structure overview
- 1-SEVERITY - Part 1: Severity field
- 2-COMPONENT - Part 2: Component field
- 3-PRIMARY - Part 3: Primary field
- 4-SEQUENCE - Part 4: Sequence field
- 5-COMPACT-IDS - Part 5: Compact ID generation
- 6-SEQUENCE-CONVENTIONS - Part 6: Sequence conventions
- 9-CATALOGS - Part 9: Error catalogs
- RFC 2119 - Key words for RFCs