@manifesto-ai/translator Foundational Design Rationale v1.1.1
Status: Release Version: 1.1.1 Companion To:
@manifesto-ai/translatorSPEC v1.1.1 License: MIT
Changelog
| Version | Changes |
|---|---|
| v1.1.1 | Architecture Decision (FAD-001) + Critical Fixes + MEL Type Definitions |
| v1.1.0 | Added FDR-T022~T029: Manifesto Ecosystem Integration + Memory Architecture decisions |
| v1.0.0 | Initial release: FDR-T001~T021 |
v1.1.1 Architecture Decision Alignment
FAD-001: Translator is a Compiler Frontend with Deterministic Contracts
This version implements the foundational architecture decision that realigns Translator's identity:
| Change | From | To |
|---|---|---|
| Identity | "LLM-based helper" | Compiler frontend with deterministic contracts |
| Determinism | Implicit "fully deterministic" | Explicit: stages 0–2, 6 deterministic; proposer non-deterministic |
| World | Optional context | Premise (cannot operate without) |
| Memory | Optional enhancement | Default path (absence = degradation) |
| Human Escalation | UX feature | Constitutional invariant |
| Type Definitions | TypeScript | MEL (normative) |
v1.1.1 MEL Type Migration
All normative domain types are now defined in MEL for consistency with Manifesto ecosystem:
| Rationale | Description |
|---|---|
| Ecosystem Consistency | Translator operates on MEL-based World; types should be in MEL |
| Domain Expressiveness | MEL provides better domain modeling than TypeScript |
| Single Source | §6 is now the single normative type definition source |
| Implementation Agnostic | MEL types can be transpiled to any implementation language |
TypeScript in SPEC is now informative (implementation examples).
v1.1.1 Final Polish (Post-GO)
| Item | Section | Fix |
|---|---|---|
| §8.8 trace presence rule | §8.8 | Clarified when trace is present vs absent |
obj.fields key uniqueness | §6.2 | Added duplicate key validation rule |
v1.1.1 Critical Issue Fixes (Round 10 — STOPPER)
| Blocker | Issue | Fix |
|---|---|---|
| S6 | MEM-006 vs §8.9 "require" mode conflict | Split MEM-006 into mode-conditional rules |
| S7 | schema.types MUST vs "if present" inconsistency | Clarified schema fields REQUIRED, typeIndex derived |
Details:
- MEM-006a:
mode=default→ MAY skip Stage 4 (degraded) - MEM-006b:
mode=require→ MUST error (§8.9 precedence) - §4.6: All schema fields (types, state, computed, actions) REQUIRED (may be
{}) - §8A.2: Removed "if present", added schema requirements table
- TypeIndex: NOT stored, MUST be derived via
deriveTypeIndex(schema)
v1.1.1 Critical Issue Fixes (Round 9 — STOPPER)
| Blocker | Issue | Fix |
|---|---|---|
| S5 | §8.5 example ↔ §6.14 MemoryStageTrace field mismatch | Aligned both sections |
Details:
- Added
degradeReason?: "SELECTOR_NOT_CONFIGURED" | "SELECTION_EMPTY" | "SELECTOR_ERROR"to §6.14 normative - Added
errorMessage?: stringto §6.14 normative - Fixed §8.5
contentSummarykeys:schemaCount→schemaSnapshotCount,glossaryCount→glossaryTermCount
v1.1.1 Critical Issue Fixes (Round 8 — STOPPER)
| Blocker | Issue | Fix |
|---|---|---|
| S4 | §8.5 example ↔ MemoryStageResult mismatch | Rewrote memoryStage() with full type alignment |
Details:
- Added
degraded: boolean(required field) - Changed
tracefromMemoryTracetoMemoryStageTrace - Added degradation handling (selector not configured, selector error, empty selection)
- Added
averageConfidencecalculation - Fixed
TypeExpr.literal.valuefromJsonValuetoPrimitiveValue
v1.1.1 Critical Issue Fixes (Round 7 — STOPPER)
| Blocker | Issue | Fix |
|---|---|---|
| S2 | sys.path string vs Array | SystemPath = Array<string> |
| S3 | obj.fields Record vs key/value array | Array<{ key: string, value: ExprNode }> |
Additional:
- Added
canonicalizeExprNode()function to §13.2 - Clarified §6.2.1 as "Translator-Level" restrictions (distinct from MEL-level)
v1.1.1 Critical Issue Fixes (Round 6 — STOPPER)
| Blocker | Issue | Fix |
|---|---|---|
| S1 | ExprNode misaligned with MEL v0.3.3 | Changed to 7-kind canonical IR |
Details:
- MEL v0.3.3 canonical IR:
lit | var | sys | get | call | obj | arr lit.valuerestricted toPrimitiveValue(no object/array)$meta.*,$system.*,$input.*usesysnode (notvar)obj/arrnodes for object/array literalsvaris$itemonly (v0.3.3:$accremoved)- Added ExprNode context validation rules (§6.2.1)
- Added
MemoryStageResulttype (§6.5)
v1.1.1 Critical Issue Fixes (Round 5)
| Blocker | Issue | Fix |
|---|---|---|
| #12 | addAction.params Array vs Record | Changed to Record<string, ActionParamSpec> |
| #13 | ActionBody guard-stmt binding | Introduced GuardedBlock, ActionStmt, nested structure |
Bonus:
- Stage 4 wording: "Optional" → "Default; may degrade"
ProposalResult: Changed from nullable fields to discriminated union (one-of)
v1.1.1 Critical Issue Fixes (Round 4)
| Blocker | Issue | Fix |
|---|---|---|
| #9 | freeform text vs input mismatch | §8B.3 examples use text |
| #10 | Human resolution missing escalation | §8B.3 examples include escalation |
| #11 | deriveTypeIndex uses Array | §8A.2 uses Object.entries() for Record |
Bonus: Added explicit MemoryPolicy.mode: "require" failure behavior in §8.9
v1.1.1 Critical Issue Fixes (Round 3)
| Blocker | Issue | Fix |
|---|---|---|
| #6 | opt-cancel MUST vs MAY conflict | Unified to MUST for all ambiguity kinds (§6.8, §11.2.1) |
| #7 | TypeExpr array vs Record mismatch | §4 references §6.3 MEL; fields is Record; §13.2 updated |
| #8 | Glossary type undefined | Added MEL data structure type Glossary = { entries: Array<GlossaryEntry> } |
v1.1.1 Critical Issue Fixes (Round 2 — 5 Blockers)
This version fixes 5 critical modeling blockers that prevented spec closure:
| Blocker | Issue | Fix |
|---|---|---|
| #1 | TypeIndex/ResolvedType missing in §6 | Added §6.3 with normative MEL definitions |
| #2 | ExprNode too loose for call-only IR | Fixed §6.2 with lit/var/get/call variants |
| #3 | ResolutionPrompt field mismatch | Added optionIds to §6.8, unified text field |
| #4 | TraceConfig duplicate definitions | Single normative MEL in §6.18 |
| #5 | updateComputedExpr violates monotonic | Removed from v1 operator set (§6.7.2) |
Additional Fixes:
| Issue | Fix |
|---|---|
MemoryStageTrace duplicate | Single canonical MEL definition (§6.14) |
EscalationTrace duplicate | Single canonical MEL definition (§6.15) |
| Memory v1.0.0 references | Updated to v1.2.0 throughout |
| Config missing fields | Added MemoryPolicy type in MEL |
| Section numbering | Renumbered §6.1-6.18 consistently |
| ErrorCode incomplete | Extended to match §12.2 comprehensive list |
v1.1.0 Decisions
| FDR | Category | Decision |
|---|---|---|
| T022 | Ecosystem | World as Single Source of Truth |
| T023 | Ecosystem | Human Escalation for Ambiguity |
| T024 | Ecosystem | Actor Creates Proposal |
| T025 | Ecosystem | TypeIndex Derived from Schema |
| T026 | Memory | Memory Provides Content (not just refs) |
| T027 | Memory | Memory as Few-Shot Examples |
| T028 | Memory | |
| T029 | Memory | Memory is Optional (graceful degradation) |
Table of Contents
- Overview
- Foundational Architecture Decision (FAD-001)
- Core Philosophy Decisions
- Data Model Decisions
- Pipeline Decisions
- Identity & Determinism Decisions
- Memory Integration Decisions
- Ambiguity & Resolution Decisions
- Configuration Decisions
- Summary Table
- Cross-Reference: Related FDRs
- Manifesto Ecosystem Integration Decisions
- Memory Architecture Decisions
Overview
This document captures the foundational design rationale for the @manifesto-ai/translator package. Each decision (FDR-T*) explains why the specification is designed the way it is, what alternatives were considered, and what consequences follow.
Translator = Manifesto World 위에서 동작하는 결정론적 Compiler Frontend
Translator is NOT an "AI helper", NOT an "LLM-based assistant", NOT a standalone tool. It is a semantic proposal engine that reads World and emits proposals.
Foundational Architecture Decision (FAD-001)
Translator is a Compiler Frontend with Deterministic Contracts
Translator = Manifesto World 위에서 동작하는 Compiler Frontend (deterministic stages 0–2, 6; untrusted/non-deterministic proposer stage 5)
This is the foundational identity of Translator. All other decisions derive from this.
Clarification on Determinism
Translator is NOT "fully deterministic" in the sense that re-running translate() produces identical output. However, contracts and core stages are deterministic:
| Aspect | Deterministic? | Notes |
|---|---|---|
| Semantic Identity (fragmentId) | ✅ Yes | Content-addressed |
| Types, Validation, IR | ✅ Yes | Pure functions |
| Stages 0–2, 6 | ✅ Yes | No LLM |
| Stage 3 (Retrieval) | ⚠️ Should | With tie-breaking |
| Stages 4, 5 | ❌ May not | LLM-based |
Key insight: Proposer (Stage 5) is explicitly untrusted. Its non-determinism is by design—Authority verifies, not Translator.
Core Properties
| Property | Value |
|---|---|
| Input | Natural Language (any human language) |
| Output | PatchFragment[] | AmbiguityReport | TranslationError |
| State | None (stateless) |
| Truth | Only World (Schema + Snapshot) |
| Responsibility | Interpret + Propose + Trace (never Apply, never Approve) |
Three Architectural Pillars
| Pillar | Description | Consequence |
|---|---|---|
| World is Premise | Translator cannot operate without World | Context MUST be World-derived |
| Memory is Default | Memory is structural input, not optional enhancement | Absence triggers degradation |
| Human Escalation is Constitutional | Ambiguity = Human's constitutional right | Agent auto-resolve forbidden |
Forbidden Patterns
| Pattern | Why Forbidden |
|---|---|
| "Standalone Translator" | World is premise, not option |
| "AI helper" / "LLM assistant" framing | Translator is compiler frontend |
| Agent auto-resolving ambiguity | Human escalation is constitutional |
| Memory as "optional enhancement" | Memory is default path |
| Direct context construction | Must derive from World |
Why This Decision Matters
This decision ensures:
- Translator is a reproducible semantic compiler (for deterministic stages)
- Proposer non-determinism is explicitly bounded (untrusted, Authority verifies)
- Manifesto becomes OS layer, not "LLM framework"
- "Why not LangChain/LangGraph?" becomes clear
- Human-in-the-loop is structure, not philosophy
This decision is final and non-negotiable.
Core Philosophy Decisions
FDR-T001: Translator is Frontend Only (No MEL Generation)
Decision
Translator outputs PatchFragment (structured semantic ops), NOT MEL text.
Context
Two possible designs:
| Option | Output | Downstream |
|---|---|---|
| A: Code Generator | MEL source text | Parser needed to consume |
| B: Frontend Only | Structured IR (PatchFragment) | Direct consumption |
Rationale
Frontend-only design enables:
- No round-trip parsing: Downstream (Authority/Host) consumes fragments directly
- Validation before serialization: Structural validation happens on typed data
- Deterministic rendering: MEL text is a pure projection from IR (if needed)
- AI-friendly: LLMs work better with structured constraints than free-form text
Code generation would require:
- MEL parser in every consumer
- Handling of syntax errors from LLM
- Loss of semantic structure
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Generate MEL text | Round-trip parsing, syntax error risk |
| Generate both | Complexity, sync burden |
| Let LLM choose format | Non-deterministic, validation nightmare |
Consequences
- Translator MUST NOT emit MEL text (INV-005)
- Deterministic Renderer is separate (optional, may be bundled)
- PatchFragment becomes the canonical unit of semantic change
FDR-T002: Incremental-First Design
Decision
Every input is treated as an incremental fragment. "Initial generation" is just the first fragment set.
Context
Two mental models for domain authoring:
| Model | Approach |
|---|---|
| Document-First | Generate complete domain from epic spec |
| Incremental-First | Build domain through successive fragments |
Rationale
Incremental-first is more robust because:
- Smaller LLM context: Each fragment is bounded
- Progressive validation: Errors caught early
- Natural collaboration: Human + AI iterate together
- Resumable: Ambiguity pauses, resolution continues
- Auditable: Each fragment has trace
Document-first problems:
- Massive context window needed
- All-or-nothing failure mode
- Hard to attribute errors
- No natural pause points
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Document-first with chunking | Chunks lose cross-reference context |
| Hybrid (detect epic vs incremental) | Complexity, mode confusion |
Consequences
- PatchProgram exists for multi-fragment coordination
- No "generate entire domain" API in v1
- Each fragment has independent identity
FDR-T003: Untrusted Proposer Principle
Decision
Any model output is a proposal only. Translator never applies, approves, or executes.
Context
LLMs are powerful but unreliable. Two trust models:
| Model | LLM Role |
|---|---|
| Trusted Agent | LLM output is applied directly |
| Untrusted Proposer | LLM proposes, Authority verifies |
Rationale
Untrusted Proposer aligns with Manifesto philosophy:
- Separation of concerns: Proposer ≠ Approver ≠ Executor
- Auditability: Every proposal has trace
- Governance: Authority applies policy
- Safety: Bad proposals are rejected, not executed
Trusted Agent problems:
- No governance checkpoint
- LLM errors become system errors
- No audit trail for rejections
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Trust high-confidence outputs | Confidence is not reliability |
| Trust after N successful runs | Gaming, distribution shift |
Consequences
- Translator returns proposals, never side-effects
- Authority is separate layer (World Protocol)
- Confidence is signal, not authorization
FDR-T004: Ambiguity Return (Never Resolve)
Decision
Translator MUST NOT resolve ambiguity. It MUST return a structured AmbiguityReport.
Context
When input is ambiguous, two options:
| Option | Behavior |
|---|---|
| A: Best-effort | Pick most likely interpretation |
| B: Return ambiguity | Ask Actor to decide |
Rationale
Returning ambiguity respects Actor autonomy:
- No silent assumptions: Actor knows there was uncertainty
- Explicit decision: Choice is recorded in trace
- Better UX: Actor can rephrase or clarify
- Governance-friendly: Decision attribution is clear
Best-effort problems:
- Silent wrong interpretations
- No accountability for choice
- Hard to debug "why did it do X?"
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Best-effort with confidence | Actor may not see low confidence |
| Ask for clarification inline | Breaks stateless API |
| Return both interpretations as fragments | Which one applies? |
Consequences
- AmbiguityReport is first-class result type
- resolve() API exists for continuation
- Trace records resolution decision
Data Model Decisions
FDR-T005: ExprNode for Expressions, JsonValue for Literals
Decision
- Computed expressions (constraints, computed, availability):
ExprNode(MEL call-only) - Default values:
JsonValue(static literal)
Context
All values could use ExprNode, or all could use JsonValue, or split by purpose.
| Option | constraint.rule | setDefaultValue.value |
|---|---|---|
| All ExprNode | ExprNode | ExprNode |
| All JsonValue | JsonValue | JsonValue |
| Split by purpose | ExprNode | JsonValue |
Rationale
Split by purpose is semantically correct:
- Constraints need evaluation: They reference
$state.*, comparisons, etc. - Defaults are static: They are literal values, no runtime resolution
- Determinism: Defaults MUST be deterministic; ExprNode allows non-determinism
- MEL alignment: MEL state initializers are literals, not expressions
All-ExprNode problems:
- Temptation to use
$system.now()in defaults (non-deterministic) - Overcomplication for simple literals
- Validation burden
All-JsonValue problems:
- Can't express constraint logic
- Would need separate constraint type anyway
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| All ExprNode | Allows non-deterministic defaults |
| Subset ExprNode for defaults | Still too permissive |
| String DSL for constraints | Parsing burden, less type-safe |
Consequences
- setDefaultValue.value is JsonValue
- addConstraint.rule is ExprNode
- Clear validation: defaults can't reference state
FDR-T006: PatchFragment as Minimal Semantic Unit
Decision
PatchFragment represents a single semantic operation with identity, confidence, and evidence.
Context
What granularity for change proposals?
| Granularity | Unit |
|---|---|
| Fine (field-level) | One field change per fragment |
| Medium (op-level) | One logical operation per fragment |
| Coarse (batch-level) | Multiple operations per fragment |
Rationale
Op-level granularity balances:
- Atomicity: One operation = one decision
- Composability: Combine fragments into programs
- Auditability: Each op has its own trace
- Deduplication: Content-addressed ID enables dedupe
Fine-grained problems:
- Too many fragments for simple changes
- Cross-field operations split unnaturally
Coarse-grained problems:
- Partial rejection is complex
- Identity is unclear
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Field-level fragments | Over-fragmentation |
| Batch fragments | Unclear identity, partial reject |
| Nested fragments | Complexity, ordering issues |
Consequences
- One PatchOp per PatchFragment
- fragmentId is content-addressed (op only)
- PatchProgram coordinates multiple fragments
FDR-T007: v1 Operator Set is Monotonic (No Destructive Ops)
Decision
v1 operators are additive/monotonic: addType, addField, setFieldType, setDefaultValue, addConstraint. No removeType, removeField, renameField, removeConstraint.
Context
Full schema evolution vs safe subset for v1.
| Approach | Operators |
|---|---|
| Full evolution | add, remove, rename, migrate |
| Monotonic only | add, set (no remove/rename) |
Rationale
Monotonic is safer for v1:
- No orphan references: Removing types can orphan fields
- No data loss: Removing fields loses data
- No migration complexity: Rename needs data migration
- Simpler validation: Only check "does target exist?"
Destructive ops require:
- Orphan detection
- Migration planning
- Data preservation strategy
- Governance approval workflows
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Full operator set | Too risky for v1 |
| Soft-delete only | Still needs orphan handling |
| Rename as add+copy+deprecate | Possible in v2 |
Consequences
- v1 is safe for iterative building
- Destructive ops deferred to v2
- "removeField" expressed as deprecation pattern
Pipeline Decisions
FDR-T008: Six-Stage Pipeline with Clear Determinism Boundaries
Decision
Pipeline has 6 stages with explicit determinism requirements:
- Stages 0-2: MUST deterministic
- Stage 3: SHOULD deterministic (cache)
- Stages 4-5: MAY non-deterministic
- Stage 6: MUST deterministic
Context
How to structure the translation pipeline?
| Design | Stages | Determinism |
|---|---|---|
| Monolithic | 1 | Unclear |
| Two-phase | 2 (parse, generate) | Unclear |
| Multi-stage | 6 | Explicit per stage |
Rationale
Multi-stage with explicit determinism enables:
- Caching: Deterministic stages can cache
- Debugging: Trace shows exactly where non-determinism entered
- Testing: Deterministic stages are unit-testable
- Optimization: Each stage can be optimized independently
Monolithic problems:
- Can't cache intermediate results
- Hard to attribute non-determinism
- All-or-nothing testing
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Monolithic | No caching, poor debugging |
| Two-phase | Too coarse |
| Dynamic stages | Unpredictable trace structure |
Consequences
- TranslationTrace has stage-specific traces
- Fast-path can short-circuit to Stage 6
- Each stage has its own trace type
FDR-T009: Fast Path with Candidates (Not Just Match/No-Match)
Decision
FastPathResult includes candidates[] array, not just boolean match.
type FastPathResult = {
matched: boolean;
best: FastPathCandidate | null;
candidates: FastPathCandidate[];
};Context
Fast-path matching can have multiple outcomes:
| Outcome | Old Model | New Model |
|---|---|---|
| No match | matched=false | candidates=[] |
| One match | matched=true | matched=true, candidates=[best] |
| Multiple low-confidence | ??? | matched=false, candidates=[...] |
Rationale
Candidates array enables proper fastPathOnly handling:
- Distinguish "no patterns" from "uncertain patterns"
- fastPathOnly + candidates → intent ambiguity
- fastPathOnly + no candidates → FAST_PATH_MISS error
Boolean-only problems:
- Can't distinguish failure modes
- fastPathOnly always errors on non-match
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Boolean matched only | Can't distinguish failure modes |
| Separate error types | Complex, still loses candidates |
| Always proceed to proposer | Defeats purpose of fast-path only mode |
Consequences
- FastPathCandidate is structured type
- fastPathOnly mode has clear semantics
- Intent ambiguity can include pattern candidates
FDR-T010: Fragment Ordering is Applicator Responsibility
Decision
Translator declares fragment order as semantically insignificant. Applicator (Host/Authority) performs topological ordering.
Context
Who decides fragment application order?
| Option | Responsibility |
|---|---|
| Translator orders | Translator emits in dependency order |
| Applicator orders | Translator emits unordered, applicator sorts |
Rationale
Applicator ordering is cleaner because:
- Translator is frontend: Ordering is execution concern
- Deduplication: Order-independent IDs enable dedupe
- Merge: Multiple proposals can be merged then ordered
- Single responsibility: Applicator already knows schema
Translator ordering problems:
- Duplicates ordering logic
- Merge becomes complex
- Different orderings for same semantic content
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Translator topological sort | Duplicates applicator logic |
| Explicit order field | Extra complexity, sync burden |
| Reject unordered batches | Too strict |
Consequences
- TranslationResult.fragments order is irrelevant
- PatchProgram.dependencies is optional DAG
- Applicator MUST implement topo-sort
Identity & Determinism Decisions
FDR-T011: Content-Addressed fragmentId (No Ordinal)
Decision
fragmentId = sha256(intentId + ':' + canonicalize(op))
No ordinal or sequence number in ID.
Context
How to generate fragment identity?
| Approach | Formula |
|---|---|
| Sequential | intentId + ordinal |
| Content-addressed | intentId + hash(op) |
| Hybrid | intentId + ordinal + hash(op) |
Rationale
Content-addressed IDs enable:
- Deduplication: Same op = same ID, automatic dedupe
- Order independence: Proposer output order doesn't matter
- Cache stability: Same op cached regardless of batch
- Diff stability: Same semantic content = same ID
Ordinal problems:
- Different order = different IDs for same ops
- Proposer output order affects identity
- Cache misses on reordering
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Sequential IDs | Order-dependent, cache-unfriendly |
| UUID | No deduplication |
| Hybrid with ordinal | Ordinal makes ID order-dependent |
Consequences
- Same op in different batches has same fragmentId
- Deduplication is simple set membership
- Proposer can output in any order
FDR-T012: RFC 8785 (JCS) for Canonical JSON
Decision
All JSON serialization for hashing MUST follow RFC 8785 (JSON Canonicalization Scheme).
Context
JSON serialization is not deterministic by default:
- Object key order varies
- Number formatting varies
- Whitespace varies
Rationale
RFC 8785 is the standard solution:
- Well-specified: No ambiguity in implementation
- Library support: Implementations exist
- Interoperable: Cross-language consistency
- Complete: Handles edge cases (numbers, escaping)
Ad-hoc canonicalization problems:
- Edge cases (NaN, Infinity, Unicode)
- Implementation drift
- No external validation
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Custom sort + stringify | Edge cases, no standard |
| Stable stringify libraries | Not standardized |
| Binary format (CBOR) | Overkill, less tooling |
Consequences
- Implementations SHOULD use RFC 8785 library
- NaN/Infinity are reject (not valid JSON)
- Cross-implementation hashes match
FDR-T013: Semantic Collection Ordering Before Hashing
Decision
Semantically unordered collections (union.members, object.fields) MUST be sorted before canonicalization.
Context
Some MEL constructs have collections where order doesn't affect semantics:
union { "a" | "b" }=union { "b" | "a" }object { x: number, y: string }=object { y: string, x: number }
Rationale
Sorting ensures semantic equivalence = hash equivalence:
- Dedup correctness: Semantically equal ops dedupe correctly
- Cache hits: Reordered but equal ops hit cache
- Diff stability: Trivial reorderings don't show as changes
Without sorting:
- Same type, different member order = different hash
- False cache misses
- Noisy diffs
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Preserve order, accept hash variance | Defeats content-addressing |
| Normalize at parse time only | Misses runtime-constructed types |
| Hash sets instead of arrays | Complex, non-standard |
Consequences
- canonicalizeTypeExpr() sorts members/fields
- Hash is truly content-addressed
- Implementation must sort before hash
Memory Integration Decisions
FDR-T014: Memory as Evidence, Not Truth
Decision
Translator treats memory as evidence (candidates + selection + trace), never as truth.
Context
Memory provides context from past Worlds. How to treat it?
| Trust Level | Behavior |
|---|---|
| Truth | Use memory content directly |
| Evidence | Use memory as input, verify separately |
Rationale
Evidence model aligns with Manifesto philosophy:
- Authority verifies: verifyProof() checks evidence
- Trace records: Selection is auditable
- Confidence signals: Low confidence triggers policy ambiguity
- No silent assumptions: Memory doesn't bypass governance
Truth model problems:
- No verification checkpoint
- Memory errors become system errors
- No audit of memory usage
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Trust high-confidence memory | Confidence ≠ correctness |
| Skip memory, always fresh | Loses valuable context |
| Memory in proposer prompt only | Still needs verification |
Consequences
- INV-008: Memory ≠ Truth
- MemoryTrace attached to TranslationTrace
- Authority calls verifyProof(), not Store
FDR-T015: Translator Returns Trace, Actor Attaches to Proposal
Decision
Translator returns MemoryTrace in TranslationTrace. Actor is responsible for attaching to Proposal.
Context
Who creates and attaches memory evidence to Proposal?
| Option | Creator | Attacher |
|---|---|---|
| Translator creates Proposal | Translator | Translator |
| Translator returns trace | Translator | Actor |
Rationale
Actor attachment respects boundaries:
- Translator is frontend: Doesn't create Proposals
- Actor has Proposal context: Knows how to attach
- Separation of concerns: Translator ≠ Proposal factory
- Flexibility: Actor can modify before attach
Translator-creates-Proposal problems:
- Translator becomes too coupled to World Protocol
- Actor loses control over Proposal construction
- Testing requires full Proposal infrastructure
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Translator creates Proposal | Coupling, boundary violation |
| Actor re-selects memory | Duplicates work, inconsistency risk |
| No trace, Actor fetches | Loses selection context |
Consequences
- TranslationTrace.stages.memory.trace exists
- Actor calls MemoryTraceUtils.attachToProposal()
- Clear responsibility chain
FDR-T016: atWorldId is Required Context
Decision
TranslationContext.atWorldId is REQUIRED, not optional.
Context
Memory selection needs a World reference point. Should it be required or optional?
| Option | atWorldId |
|---|---|
| Required | MUST provide |
| Optional | Defaults to "latest" or null |
Rationale
Required atWorldId ensures:
- Explicit anchoring: No ambiguity about reference point
- Memory scoping: Selection is scoped to World lineage
- Verification context: Authority knows what was anchored
- Reproducibility: Same atWorldId = same memory scope
Optional problems:
- "Latest" is non-deterministic
- Null disables memory silently
- Ambiguous in multi-World scenarios
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Default to latest | Non-deterministic |
| Optional with null | Silent memory disable |
| Derive from schema | Schema doesn't have World |
Consequences
- TranslationContext.atWorldId is required field
- Memory selection always has anchor
- INVALID_CONTEXT if atWorldId missing
Ambiguity & Resolution Decisions
FDR-T017: Candidates >= 2 Invariant for All Ambiguity
Decision
All AmbiguityReports MUST have candidates.length >= 2.
Context
Ambiguity with one candidate is not really ambiguity.
| candidates.length | Meaning |
|---|---|
| 0 | Error (no options) |
| 1 | Not ambiguity (one option) |
| >= 2 | True ambiguity (choice needed) |
Rationale
Invariant ensures meaningful choice:
- UX clarity: Actor always has options
- Cancel option: Even low-confidence has apply/cancel
- No degenerate cases: One option = just return it
- Consistent resolve(): Always choosing between options
Single candidate problems:
- Why ask Actor if only one option?
- resolve() is meaningless
- UX confusion
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Allow 1 candidate | Meaningless ambiguity |
| Allow 0 candidates | That's an error |
| Flexible count | Inconsistent resolve() |
Consequences
- policy ambiguity always has apply + cancel
- intent ambiguity includes candidates + cancel
- resolve() always has meaningful choice
FDR-T018: opt-cancel is Universal (All Ambiguity Kinds)
Decision
opt-cancel option is valid for all ambiguity kinds, not just policy.
Context
Should cancel/no-op be available in all ambiguity types?
| Ambiguity Kind | opt-cancel? |
|---|---|
| policy | Always |
| intent | ? |
| target/value/conflict | ? |
Rationale
Universal cancel provides safe exit:
- Actor autonomy: Always can say "no"
- Rephrase opportunity: Cancel and retry with better input
- Consistent UX: Same pattern across ambiguity types
- No forced choice: Actor isn't trapped
Kind-specific cancel problems:
- Inconsistent UX
- Some ambiguities have no exit
- Complex rules for when cancel exists
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| policy-only cancel | Inconsistent, traps Actor |
| No cancel, must choose | No safe exit |
| Cancel with penalty | Overcomplicates |
Consequences
- opt-cancel allowed in any ambiguity
- resolve() with opt-cancel = fragments: []
- No-op result is valid from any ambiguity
FDR-T019: resolve() is Stateless
Decision
resolve() accepts (report, resolution, context). Translator is stateless.
function resolve(
report: AmbiguityReport,
resolution: AmbiguityResolution,
context: TranslationContext
): Promise<TranslationResult>;Context
How to handle ambiguity resolution?
| Design | State |
|---|---|
| Stateful session | Translator stores pending reports |
| Stateless | Caller provides report + resolution |
Rationale
Stateless design enables:
- Horizontal scaling: No shared state between instances
- Restart resilience: No session loss on restart
- Explicit data flow: All inputs visible
- Simpler testing: No session setup
Stateful problems:
- Session management complexity
- Scaling requires session affinity
- Restart loses pending reports
- TTL/eviction policies needed
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Session-based | Scaling, restart, complexity |
| reportId lookup | Still needs storage |
| Combined translate+resolve | Breaks separation |
Consequences
- Caller stores AmbiguityReport
- resolve() is pure function (mostly)
- Context must match original translation
Configuration Decisions
FDR-T020: Two-Threshold Confidence Policy
Decision
Confidence policy uses two thresholds: autoAcceptThreshold and rejectThreshold.
type ConfidencePolicy = {
autoAcceptThreshold: number; // >= this → fragment
rejectThreshold: number; // < this → error
// between → policy ambiguity
};Context
How many thresholds for confidence-based decisions?
| Design | Thresholds |
|---|---|
| Binary | 1 (accept/reject) |
| Ternary | 2 (accept/ambiguity/reject) |
| Multi-zone | 3+ |
Rationale
Two thresholds with ambiguity middle zone:
- High confidence (>= 0.95): Auto-accept, no friction
- Medium confidence: Actor reviews via policy ambiguity
- Low confidence (< 0.30): Reject, don't waste Actor time
Binary problems:
- No middle ground for review
- Either too strict or too lenient
Multi-zone problems:
- Complex rules
- Unclear UX per zone
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Single threshold | No review zone |
| Three+ thresholds | Overcomplicated |
| Per-operation thresholds | Configuration explosion |
Consequences
- Simple mental model: auto/review/reject
- Policy ambiguity handles uncertain cases
- Thresholds are tunable per deployment
FDR-T021: fastPathOnly Uses Same Threshold
Decision
Fast-path match threshold uses confidencePolicy.autoAcceptThreshold, not a separate config.
Context
Should fast-path have its own threshold?
| Design | Threshold |
|---|---|
| Shared | Use confidencePolicy.autoAcceptThreshold |
| Separate | fastPathMatchThreshold |
Rationale
Shared threshold simplifies v1:
- One mental model: High confidence = accept, everywhere
- Less config: Fewer knobs to tune
- Consistency: Same meaning across stages
Separate threshold considerations:
- Fast-path patterns might warrant different bar
- Can add in v1.1 if needed
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Separate threshold | Premature config complexity |
| Always accept fast-path | May accept low-confidence matches |
| Never use thresholds for fast-path | Defeats confidence filtering |
Consequences
- Single confidence threshold for v1
- MAY add fastPathMatchThreshold in future
- Simple configuration to start
Summary Table
| FDR | Decision | Key Principle |
|---|---|---|
| T001 | Frontend Only | No MEL generation, structured output |
| T002 | Incremental-First | Build via successive fragments |
| T003 | Untrusted Proposer | Proposals only, no execution |
| T004 | Ambiguity Return | Never resolve, return to Actor |
| T005 | ExprNode/JsonValue Split | Expressions vs literals |
| T006 | PatchFragment Unit | One op per fragment |
| T007 | Monotonic v1 Ops | No destructive operators |
| T008 | Six-Stage Pipeline | Explicit determinism boundaries |
| T009 | FastPath Candidates | Distinguish failure modes |
| T010 | Applicator Orders | Translator doesn't order |
| T011 | Content-Addressed ID | No ordinal in fragmentId |
| T012 | RFC 8785 (JCS) | Standard canonical JSON |
| T013 | Collection Ordering | Sort before hash |
| T014 | Memory as Evidence | Not truth |
| T015 | Actor Attaches Trace | Translator returns, Actor attaches |
| T016 | atWorldId Required | Explicit anchoring |
| T017 | Candidates >= 2 | Always meaningful choice |
| T018 | Universal opt-cancel | Safe exit from any ambiguity |
| T019 | Stateless resolve() | No session state |
| T020 | Two-Threshold Policy | Auto/review/reject zones |
| T021 | Shared Threshold | Simple v1 config |
| T022 | World as Source of Truth | Context derived from World |
| T023 | Human Escalation | Agents must not auto-resolve |
| T024 | Actor Creates Proposal | Fragment → Proposal by Actor |
| T025 | TypeIndex Derived | Derived from schema, not provided |
| T026 | Memory Provides Content | Fetch World content, not just refs |
| T027 | Memory as Few-Shot | Examples in Proposer prompt |
| T028 | Memory Stage 5/6 Only | Stage 1/2 use pre-built config |
| T029 | Memory is Optional | Graceful degradation without Memory |
| T030 | Store Boundaries | Memory selects, World stores fetch |
| T031 | EscalationTrace Field | Explicit escalation in TranslationTrace |
| T032 | deriveContext() API | Recommended, not required |
Cross-Reference: Related FDRs
From MEL Compiler FDR
| MEL FDR | Relevance |
|---|---|
| FDR-MEL-040 (Call-only IR) | Translator targets this IR |
| FDR-MEL-024 (Canonical form) | Normalization before IR |
From World Protocol FDR
| World FDR | Relevance |
|---|---|
| FDR-W001 (Intent-level governance) | Translator produces proposals |
| FDR-W002 (Proposal = Actor + Intent) | Actor wraps Translator output |
From Memory FDR
| Memory FDR | Relevance |
|---|---|
| M-1 (Memory is not truth) | Translator treats as evidence |
| M-4 (Authority does not re-select) | verifyProof only |
From Host Contract FDR
| Host FDR | Relevance |
|---|---|
| FDR-H003 (No pause/resume) | Ambiguity returns, doesn't pause |
| FDR-H006 (Intent identity) | intentId in fragmentId |
From Builder FDR
| Builder FDR | Relevance |
|---|---|
| FDR-B007 (Zod-first typing) | Schema structure |
| FDR-B001 (No string paths) | SemanticPath type |
Manifesto Ecosystem Integration Decisions
FDR-T022: World as Single Source of Truth for Context
Decision
TranslationContext MUST be derived from World. Independent construction is a spec violation.
Context
How should Translator get schema and type information?
| Option | Source |
|---|---|
| A: Independent | Caller provides schema/typeIndex directly |
| B: World-derived | Caller provides worldId, Translator derives rest |
| C: Hybrid | Caller provides worldId, context is derived |
Rationale
World-derived context ensures:
- Consistency: Schema matches World's current state
- Verifiability: Authority can verify context against World
- Single source of truth: No divergence between World and context
- Audit trail: Context is traceable to specific World
Independent construction problems:
- Schema may be stale
- TypeIndex may not match schema
- Authority cannot verify context origin
- Changes may conflict with current World state
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Trust caller-provided context | No verification possible |
| Validate independently | Expensive, may still miss drift |
| Ignore World, just use schema | Loses governance integration |
Consequences
- Context includes
atWorldIdas primary field - Schema and typeIndex derived from World
INVALID_CONTEXTerror if inconsistent
FDR-T023: Human Escalation for Ambiguity
Decision
AmbiguityReport MUST be escalated to Human. Agents MUST NOT auto-resolve.
Context
When Translator returns ambiguity, who decides?
| Option | Resolver |
|---|---|
| A: Human always | Escalate to Human via UI |
| B: Agent decides | LLM picks best candidate |
| C: Policy-based | Rules decide when to escalate |
Rationale
Human escalation respects Actor autonomy:
- Explicit decision: Human makes the choice
- Audit trail:
resolvedByrecords Human actor - No silent assumptions: Agent doesn't guess intent
- UX clarity: Human sees all options including cancel
Agent auto-resolve problems:
- Compounds uncertainty (LLM choosing between LLM outputs)
- No accountability for choice
- Human may not know choice was made
- Violates INV-004 (Ambiguity Return principle)
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Agent picks highest confidence | Still uncertain, no accountability |
| Only escalate low confidence | Confidence ≠ correctness |
| Auto-resolve if unambiguous | Defeats purpose of ambiguity type |
Consequences
- ESC-001: Agents MUST NOT auto-resolve
- UI MUST show all candidates
- Resolution includes Human actor reference
FDR-T024: Fragment → Proposal Conversion by Actor
Decision
Actor (not Translator) converts fragments to World Protocol Proposal.
Context
Who creates the Proposal for World Protocol?
| Option | Creator |
|---|---|
| A: Translator | Returns Proposal directly |
| B: Actor | Wraps fragments in Proposal |
Rationale
Actor creates Proposal because:
- Separation of concerns: Translator is frontend, not governance layer
- Actor context: Actor has identity, session, preferences
- Flexibility: Actor can modify before submission
- World Protocol alignment: Proposal.actor must be the Actor
Translator-creates-Proposal problems:
- Translator would need Actor context
- Couples Translator to World Protocol internals
- Less flexibility for Actor
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Translator creates Proposal | Coupling, boundary violation |
| Shared creation | Unclear responsibility |
Consequences
- Translator returns fragments + trace
- Actor wraps in IntentInstance → Proposal
- Actor submits to Authority
FDR-T025: TypeIndex Derived, Not Provided
Decision
TypeIndex MUST be derived from Schema via deriveTypeIndex(). Independent provision is error.
Context
Should typeIndex be provided or derived?
| Option | Source |
|---|---|
| A: Provided | Caller passes typeIndex |
| B: Derived | Translator derives from schema |
| C: Hybrid | Either, with validation |
Rationale
Derivation ensures consistency:
- No drift: TypeIndex always matches schema
- Single algorithm:
deriveTypeIndex()is canonical - Less error-prone: Caller can't provide wrong index
- Verifiable: Can always re-derive and compare
Provided typeIndex problems:
- May be stale
- May be incorrectly computed
- Different implementations may differ
- Inconsistency with schema
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Trust caller | Inconsistency risk |
| Accept but validate | Still allows inconsistency |
| Cache independently | Drift risk |
Consequences
deriveTypeIndex(schema)is normative algorithm- TypeIndex is NOT a context input
- Validation rejects inconsistent context
Memory Architecture Decisions
FDR-T026: Memory Provides Translation Examples (Not Just References)
Decision
Memory selection returns SelectedMemory[] (references), but Translator MUST fetch actual content from those Worlds to build MemoryContent for the Proposer.
Context
What should Memory provide to Translator?
| Option | Content |
|---|---|
| A: References only | Just WorldIds, Translator fetches nothing |
| B: References + Content | Translator fetches World content |
| C: Pre-processed context | Memory returns ready-to-use examples |
Rationale
References + Content (Option B) balances:
- Memory boundary: Memory selects, doesn't process
- Translator control: Translator decides what to extract
- Flexibility: Different Translators can extract differently
- Auditability: Clear separation of selection vs. usage
Why not references only?
- Proposer (LLM) needs actual examples, not WorldIds
- Fast-path needs actual patterns
- References alone provide no value to LLM
Why not pre-processed?
- Memory would need to understand Translator's needs
- Coupling between Memory and Translator
- Less flexible for different use cases
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| References only | Useless for LLM prompting |
| Pre-processed by Memory | Coupling, less flexible |
| LLM fetches directly | Security, no trace |
Consequences
- Stage 4 calls both Selector and Store
- MemoryContent type defined in Translator
- Proposer receives actual examples
FDR-T027: Memory Feeds Proposer via Few-Shot Examples
Decision
MemoryContent is injected into Proposer (LLM) prompt as few-shot examples.
Context
How should Memory improve translation?
| Option | Mechanism |
|---|---|
| A: Fine-tuning | Train LLM on past translations |
| B: Few-shot | Include examples in prompt |
| C: RAG | Retrieve and augment |
| D: Embeddings | Semantic similarity lookup |
Rationale
Few-shot (Option B) is practical for v1:
- No infrastructure: No vector DB or fine-tuning needed
- Immediate: Works with any LLM
- Auditable: Examples are in trace
- Adjustable: Easy to tune number of examples
Why not fine-tuning?
- Requires training infrastructure
- Per-domain models
- Slow feedback loop
Why not pure RAG?
- Need structured examples, not raw text
- Translation needs input→output pairs
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Fine-tuning | Infrastructure, slow iteration |
| Pure RAG | Unstructured, not translation pairs |
| Embeddings only | Missing input→output structure |
Consequences
- Proposer prompt includes past translations
- Prompt size increases with examples
- May need truncation strategy
FDR-T028: Memory Enhances Stage 5/6 Only (Not Stage 1/2)
Decision
Memory content is used ONLY in Stage 5 (Proposer) and Stage 6 (Assembly). Stage 1/2 use pre-built, offline-compiled resources.
Context
Original v1.1.0 stated Memory enhances Stages 1, 2, 5, 6. This conflicts with Stage 1/2 determinism requirements.
| Stage | Determinism Requirement | Memory Usage |
|---|---|---|
| Stage 1 | MUST deterministic | ❓ Conflict |
| Stage 2 | MUST deterministic | ❓ Conflict |
| Stage 5 | MAY non-deterministic | ✅ OK |
| Stage 6 | MUST deterministic (semantic only) | ✅ OK |
Rationale
Stage 1/2 MUST NOT use runtime Memory because:
- Determinism violation: Memory selection is non-deterministic (LLM-based)
- Offline capability: Stage 1/2 must work without network
- Pipeline ordering: Memory is fetched at Stage 4, after Stage 1/2
Solution: Pre-built resources
| Stage | Uses | Source |
|---|---|---|
| Stage 1 (Normalization) | config.glossary | Pre-built offline |
| Stage 2 (Fast-Path) | config.fastPathPatterns | Pre-built offline |
| Stage 5 (Proposer) | memoryContent.translationExamples | Runtime Memory |
| Stage 6 (Assembly) | memoryContent.resolutionHistory | Runtime Memory |
Historical data still benefits Stage 1/2, but via offline compilation, not runtime fetch.
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Split stages (1A/1B) | Complexity, spec inflation |
| Make Stage 1/2 non-deterministic | Breaks fundamental guarantees |
| Skip Memory for Stage 1/2 silently | Confusing, implicit behavior |
Consequences
- §8.4 Memory Usage table clarified
- Stage 1/2 use config, not runtime Memory
- No pipeline ordering conflict
FDR-T029: Memory is Optional (Graceful Degradation)
Decision
If Memory (Selector/Store) is not configured, Translator MUST still function with degraded quality.
Context
Should Memory be required or optional?
| Option | Behavior |
|---|---|
| A: Required | Error if no Memory |
| B: Optional | Graceful degradation |
Rationale
Optional with degradation is more practical:
- Bootstrap: New domains have no history
- Simplicity: Simple deployments work
- Testing: Easier to test without Memory
- Incremental: Add Memory later
Required Memory problems:
- Cold start impossible
- Complex test setup
- Barrier to adoption
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Required Memory | Cold start problem |
| Synthetic Memory | Complexity, fake data |
Consequences
- Stage 4 can return empty MemoryContent
- Proposer works without examples (lower quality)
- Fast-path starts empty, learns over time
FDR-T030: Store Boundaries (Memory Selects, World Stores Fetch)
Decision
Memory package (@manifesto-ai/memory) handles selection only. World Protocol stores handle content fetching.
Context
Memory selection returns WorldIds. Who fetches actual World content?
| Option | Fetcher |
|---|---|
| A: Memory store | Memory package provides content |
| B: World stores | Translator uses World Protocol stores |
Rationale
World stores for content (Option B) maintains package boundaries:
- Single responsibility: Memory selects, World stores persist
- No duplication: World content in one place
- Clear dependency: Translator depends on both packages explicitly
- Verification: World Protocol handles integrity verification
Memory-fetches-content problems:
- Memory would need World content access
- Duplication of World data in Memory
- Unclear responsibility for integrity
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Memory stores World content | Duplication, boundary violation |
| Translator fetches directly | No explicit dependency declaration |
Consequences
- TranslatorConfig requires
stores(World Protocol) - Memory Selector returns refs only
- Stage 4 uses both Memory and World stores
FDR-T031: EscalationTrace in TranslationTrace
Decision
TranslationTrace has explicit escalation?: EscalationTrace field. AmbiguityResolution includes escalation metadata when resolved by Human.
Context
ESC-005 requires "All Human escalations MUST be recorded in trace". Where does it go?
| Option | Location |
|---|---|
A: Inside ambiguityResolution | Nested in existing field |
B: Separate escalation field | Top-level in TranslationTrace |
Rationale
Separate field (Option B) is cleaner:
- Explicit: Escalation is first-class trace data
- Queryable: Easy to find escalations in traces
- Complete: Has all metadata (escalatedAt, escalatedTo, duration)
- Audit-friendly: Clear separation of concerns
Nested inside ambiguityResolution problems:
- Type gets complex
- Escalation metadata mixed with resolution choice
- Hard to query traces for escalations
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Nested in ambiguityResolution | Type complexity, hard to query |
| Array of escalations | Overkill for single Human interaction |
Consequences
TranslationTrace.escalationis optional fieldAmbiguityResolution.escalationprovides input metadata- Translator populates trace from resolution
FDR-T032: deriveContext() as Recommended API
Decision
deriveContext(atWorldId, actor, stores) is a recommended (not required) API for constructing TranslationContext.
Context
How should callers construct World-derived context?
| Option | Approach |
|---|---|
| A: Manual construction | Caller builds context manually |
| B: Required API | Caller MUST use deriveContext() |
| C: Recommended API | deriveContext() provided, manual allowed |
Rationale
Recommended API (Option C) balances safety and flexibility:
- Easy correct path: deriveContext() does the right thing
- Flexibility: Testing/edge cases can construct manually
- Verification:
contextVerificationconfig provides safety net - Documentation: API documents the correct pattern
Required API problems:
- Testing becomes harder
- Some edge cases need manual construction
- Over-constrains implementations
Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Required deriveContext() | Too restrictive |
| No helper, manual only | Easy to make mistakes |
| Constructor pattern | Not functional style |
Consequences
- deriveContext() is in Public API (§9.5)
- Manual construction still allowed
- contextVerification provides safety net
End of Foundational Design Rationale v1.1.1