mnemonica 1.0.0 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/.ai/ONBOARDING.md +1 -1
  2. package/.ai/ask/AGENTS.md +15 -9
  3. package/.ai/async_init.md +2 -2
  4. package/.ai/rules-skill/contributing.md +1 -1
  5. package/.ai/rules-skill/define-patterns.md +8 -2
  6. package/.ai/rules-skill/ecosystem.md +4 -4
  7. package/.ai/rules-skill/instance-methods.md +48 -15
  8. package/.ai/rules-skill/type-system.md +12 -2
  9. package/AGENTS.md +6 -2
  10. package/CONTRIBUTING.md +61 -9
  11. package/FOR_HUMANS.md +149 -239
  12. package/README.md +75 -40
  13. package/SKILL.md +1 -1
  14. package/build/api/errors/exceptionConstructor.js +20 -12
  15. package/build/api/errors/index.js +9 -5
  16. package/build/api/errors/throwModificationError.js +15 -9
  17. package/build/api/index.js +35 -2
  18. package/build/api/types/InstanceCreator.js +5 -2
  19. package/build/api/types/Mnemosyne.d.ts +6 -6
  20. package/build/api/types/Mnemosyne.js +43 -120
  21. package/build/api/types/Props.js +5 -3
  22. package/build/api/types/TypeProxy.js +13 -9
  23. package/build/api/types/compileNewModificatorFunctionBody.js +14 -8
  24. package/build/api/types/index.js +56 -13
  25. package/build/api/utils/index.js +21 -12
  26. package/build/constants/index.js +11 -8
  27. package/build/descriptors/types/index.js +79 -26
  28. package/build/index.d.ts +4 -4
  29. package/build/index.js +62 -15
  30. package/build/types/index.d.ts +52 -31
  31. package/build/types/index.js +1 -1
  32. package/build/utils/clone.d.ts +1 -0
  33. package/build/utils/clone.js +11 -0
  34. package/build/utils/collectConstructors.js +5 -3
  35. package/build/utils/exception.d.ts +1 -0
  36. package/build/utils/exception.js +14 -0
  37. package/build/utils/extract.d.ts +2 -3
  38. package/build/utils/extract.js +3 -2
  39. package/build/utils/fork.d.ts +1 -0
  40. package/build/utils/fork.js +33 -0
  41. package/build/utils/index.d.ts +2 -3
  42. package/build/utils/index.js +21 -7
  43. package/build/utils/merge.d.ts +2 -1
  44. package/build/utils/merge.js +10 -6
  45. package/build/utils/parent.d.ts +1 -1
  46. package/build/utils/parent.js +3 -2
  47. package/build/utils/parse.d.ts +2 -12
  48. package/build/utils/parse.js +1 -1
  49. package/build/utils/pick.d.ts +4 -3
  50. package/build/utils/pick.js +4 -5
  51. package/build/utils/sibling.d.ts +1 -0
  52. package/build/utils/sibling.js +25 -0
  53. package/build/utils/toJSON.d.ts +1 -1
  54. package/build/utils/toJSON.js +3 -2
  55. package/docs/UTILS.md +184 -0
  56. package/docs/ai-learning-trajectory.md +1 -1
  57. package/docs/async-constructors.md +3 -1
  58. package/docs/empathy-in-ai.md +170 -0
  59. package/docs/hott-primer.md +47 -0
  60. package/docs/performance-vs-security.md +395 -0
  61. package/docs/purpose.md +38 -7
  62. package/docs/tactica-pattern.md +10 -10
  63. package/docs/theory-of-operations.md +224 -0
  64. package/docs/typed-lookup.md +12 -12
  65. package/docs/typeomatica.md +1 -1
  66. package/package.json +13 -6
  67. package/src/api/errors/exceptionConstructor.ts +14 -9
  68. package/src/api/errors/index.ts +8 -4
  69. package/src/api/errors/throwModificationError.ts +10 -9
  70. package/src/api/types/InstanceCreator.ts +5 -8
  71. package/src/api/types/Mnemosyne.ts +72 -231
  72. package/src/api/types/Props.ts +4 -2
  73. package/src/api/types/TypeProxy.ts +7 -11
  74. package/src/api/types/compileNewModificatorFunctionBody.ts +13 -7
  75. package/src/api/types/index.ts +15 -8
  76. package/src/api/utils/index.ts +16 -14
  77. package/src/constants/index.ts +6 -9
  78. package/src/descriptors/types/index.ts +44 -24
  79. package/src/index.ts +28 -21
  80. package/src/types/index.ts +101 -69
  81. package/src/utils/clone.ts +11 -0
  82. package/src/utils/collectConstructors.ts +4 -2
  83. package/src/utils/exception.ts +16 -0
  84. package/src/utils/extract.ts +5 -2
  85. package/src/utils/fork.ts +57 -0
  86. package/src/utils/index.ts +32 -13
  87. package/src/utils/merge.ts +18 -6
  88. package/src/utils/parent.ts +4 -6
  89. package/src/utils/parse.ts +5 -4
  90. package/src/utils/pick.ts +10 -2
  91. package/src/utils/sibling.ts +33 -0
  92. package/src/utils/toJSON.ts +3 -2
@@ -0,0 +1,395 @@
1
+ # Performance vs. Security: A Honest Assessment of mnemonica
2
+
3
+ > **What this is:** A joint analysis of runtime performance characteristics and OWASP Top 10 security posture. No sugar-coating. The numbers are what they are; the security benefits are real but bounded. Use this to make an informed adoption decision.
4
+
5
+ ---
6
+
7
+ ## Executive Summary
8
+
9
+ mnemonica is **not fast** in absolute terms. It is **secure by design** in specific dimensions that matter for data-pipeline systems. Whether the trade-off is worth it depends entirely on what you are building:
10
+
11
+ | If you are building... | Verdict |
12
+ |---|---|
13
+ | High-frequency trading, game engines, real-time streaming | **Do not use mnemonica.** The creation overhead is 1,000x–4,000x slower than plain JS. |
14
+ | Financial ledgers, audit trails, compliance pipelines, document workflows | **Strong candidate.** The security and traceability benefits outweigh the cost. |
15
+ | General CRUD APIs, simple web apps | **Overkill.** Use plain types or zod; you don't need nominal typing or construction history. |
16
+ | AI-agent data architectures, ETL pipelines | **Ideal fit.** Self-documenting data lineage is the point. |
17
+
18
+ ---
19
+
20
+ ## Part 1: Benchmark Results
21
+
22
+ All measurements taken on Node.js v22.16.0, Linux x64, with `--expose-gc` for memory tests. Each benchmark ran 10 iterations with 3 warm-up rounds.
23
+
24
+ ### 1.1 Creation Throughput
25
+
26
+ | Test | mnemonica | Plain JS | Ratio |
27
+ |---|---|---|---|
28
+ | Shallow instance (20K ops) | 29,368 ops/sec | 131,949,970 ops/sec | **~4,500x slower** |
29
+ | 10-level chain (5K ops) | 2,861 ops/sec | 107,872 ops/sec | **~38x slower** |
30
+ | 100-level chain (500 ops) | 143 ops/sec | 6,943 ops/sec | **~48x slower** |
31
+
32
+ **What this means:** Every `new Type()` call walks through proxy traps, WeakMap metadata storage, an 8-stage construction pipeline (setup → stack → error blocking → pre-hooks → build → construct → async → post-processing), and prototype chain validation. This is not avoidable overhead — it is the feature set executing.
33
+
34
+ **The surprise:** Deep chain creation (100 levels) is only 48x slower, not proportionally worse. The pipeline cost is largely fixed per instance; chain depth adds linearly but the constant factor dominates.
35
+
36
+ ### 1.2 Identity and Introspection
37
+
38
+ | Test | mnemonica | Plain JS | Ratio |
39
+ |---|---|---|---|
40
+ | `instanceof` check | 1,730,562 ops/sec | 16,636,858 ops/sec | **~10x slower** |
41
+ | `getProps()` access | 3,733,198 ops/sec | 278,747,354 ops/sec | **~75x slower** |
42
+
43
+ **What this means:** `instanceof` is slower because mnemonica's prototype chain is deeper and more structured than a plain constructor. `getProps()` is 75x slower than `Object.getPrototypeOf()` because it walks a WeakMap lookup chain with prototype traversal fallback.
44
+
45
+ **Practical impact:** If you call `getProps()` in a hot loop, you will feel this. Cache the result.
46
+
47
+ ### 1.3 Property Access (The Upside)
48
+
49
+ | Test | mnemonica | Plain JS | Ratio |
50
+ |---|---|---|---|
51
+ | Read prop on 10-level chain | 818,911,751 ops/sec | 163,621,384 ops/sec | **5x faster** |
52
+ | Read prop on 100-level chain | 726,453,605 ops/sec | 142,585,493 ops/sec | **5x faster** |
53
+
54
+ **What this means:** V8 optimizes stable constructor-based prototype chains aggressively. mnemonica's chains are static after creation — constructors don't change, prototypes don't mutate. Plain `Object.create(null)` chains are dynamic and V8 pessimizes them. Once an instance exists, reading from it is *faster* in mnemonica.
55
+
56
+ **The lesson:** mnemonica optimizes for read-heavy, write-rarely workloads. If you create once and access many times, the creation cost amortizes.
57
+
58
+ ### 1.4 Subtype Lookup (Proxy Get)
59
+
60
+ | Test | mnemonica | Plain JS | Ratio |
61
+ |---|---|---|---|
62
+ | Subtype lookup (hit) | 3,928,867 ops/sec | 610,937,554 ops/sec | **~155x slower** |
63
+ | Subtype lookup (miss) | 5,015,348 ops/sec | 610,937,554 ops/sec | **~122x slower** |
64
+
65
+ **What this means:** Every `instance.SubType` access goes through a Proxy `get` trap that checks: (1) is this a declared property? (2) is this a registered subtype? (3) fallback to `Reflect.get`. The miss path is slightly faster because it skips the `subtypes.has()` Map lookup.
66
+
67
+ **Practical impact:** If your code does `instance.SubType` repeatedly in loops, cache the constructor: `const SubType = instance.SubType;`.
68
+
69
+ ### 1.5 Memory Footprint
70
+
71
+ | Test | mnemonica | Plain JS | Ratio |
72
+ |---|---|---|---|
73
+ | 10,000 shallow instances | 61.53 MB | 0.72 MB | **85x more** |
74
+ | 1,000 100-level chains | 380.34 MB | 43.72 MB | **8.7x more** |
75
+
76
+ **What this means:** Each mnemonica instance carries:
77
+ - A full prototype chain entry
78
+ - WeakMap metadata (`__type__`, `__parent__`, `__args__`, `__timestamp__`, `__creator__`, `__stack__`, `__subtypes__`, `__collection__`, `__proto_proto__`)
79
+ - Type descriptor references
80
+ - Construction context
81
+
82
+ For 10,000 shallow instances, this is ~6 KB per instance vs ~72 bytes for a plain `{n: i}` object. The 100-level chain case is "only" 8.7x because plain JS `Object.create()` chains also allocate 100 prototype objects per chain.
83
+
84
+ ---
85
+
86
+ ## Part 2: OWASP Top 10 Mapping
87
+
88
+ mnemonica is **not a security framework**. It does not replace input validation, authentication, or output encoding. What it does is eliminate entire classes of vulnerabilities by making the data model itself tamper-evident.
89
+
90
+ ### 2.1 A01: Broken Access Control → **Mitigated by `strictChain`**
91
+
92
+ **The vulnerability:** Objects passing through layers can be confused — a `User` object reaching an `Admin` handler, a `Payment` processed as an `Invoice`.
93
+
94
+ **mnemonica's defense:** The `strictChain` validation in `InstanceCreator.ts:173` enforces that subtypes can only be constructed from their direct parent type:
95
+
96
+ ```typescript
97
+ if (isSubType && strictChain) {
98
+ const prev = parent(self.inheritedInstance);
99
+ const parentName = prev.constructor.name;
100
+ const parentTypeName = self.type.parentType.TypeName;
101
+ if (parentName !== parentTypeName) {
102
+ throw new WRONG_MODIFICATION_PATTERN(
103
+ `should inherit from ${parentTypeName} but made on ${parentName}`
104
+ );
105
+ }
106
+ }
107
+ ```
108
+
109
+ **Honesty check:** This prevents *accidental* type confusion. It does not prevent a malicious actor with code execution from forging instances. But it eliminates the "wrong object slipped through" class of bugs that plague structural typing systems.
110
+
111
+ ### 2.2 A03: Injection → **No runtime code evaluation**
112
+
113
+ **The vulnerability:** Dynamic code execution from user input (`eval`, `new Function`, template injection).
114
+
115
+ **mnemonica's defense:** The old `new Function()` approach in `compileNewModificatorFunctionBody.ts` is dead code (commented out, lines 162–214). The current implementation uses static function constructors. Type names and constructor bodies are never `eval`'d at runtime.
116
+
117
+ **Honesty check:** `TypeProxy.set` (line 98 of `TypeProxy.ts`) allows `type.define(name, value)` through proxy assignment. If attacker-controlled input reaches the type name or constructor body, that's a code injection path. But this requires access to the type object itself — not a typical end-user attack surface.
118
+
119
+ ### 2.3 A08: Software and Data Integrity Failures → **Core value proposition**
120
+
121
+ **The vulnerability:** Data is modified in transit without detection. Audit trails are incomplete or fabricated. Type confusion leads to processing wrong data shapes.
122
+
123
+ **mnemonica's defense:**
124
+ - **Nominal typing:** A `Payment` and `Invoice` with identical fields are *not* interchangeable. `instanceof` answers "was this created by this specific constructor?" not "does it have these properties?"
125
+ - **Immutable constructor names:** `Object.defineProperty` sets `name` as a getter without a setter, making casual assignment silently ignored (non-strict) or thrown (strict mode). `instanceof` checks prototype object references, not names, so type identity is unaffected by name spoofing. This is defense-in-depth against prototype pollution, not cryptographic immutability.
126
+ - **Construction provenance:** Every instance carries `__type__`, `__parent__`, `__args__`, `__timestamp__`, `__creator__`. The data *is* its own audit log.
127
+
128
+ **Honesty check:** This does not prevent a compromised process from lying about `__timestamp__` (it can set any value). But it makes *accidental* data corruption detectable and *intentional* tampering require explicit attack code rather than natural language confusion.
129
+
130
+ ### 2.4 A09: Security Logging and Monitoring Failures → **Automatic**
131
+
132
+ **The vulnerability:** Developers forget to log. Audit trails are inconsistent. Forensics are impossible because construction context is lost.
133
+
134
+ **mnemonica's defense:** `getProps()` provides automatic, uniform audit records for every instance:
135
+
136
+ ```typescript
137
+ const props = getProps(instance);
138
+ // {
139
+ // __type__: TypeDescriptor, // what type is this?
140
+ // __parent__: object, // what instance created it?
141
+ // __args__: [...], // what arguments were passed?
142
+ // __timestamp__: number, // when was it created?
143
+ // __creator__: InstanceCreator, // which creation context?
144
+ // __stack__: string, // stack trace (if submitStack)
145
+ // __collection__: TypesCollection,// which namespace?
146
+ // __subtypes__: Map // what subtypes are available?
147
+ // }
148
+ ```
149
+
150
+ **Honesty check:** This is logging of *construction*, not logging of *usage*. If you process the instance 50 times after creation, mnemonica knows nothing about those 50 operations unless you instrument them separately.
151
+
152
+ ### 2.5 Prototype Pollution → **Structurally prevented**
153
+
154
+ **The vulnerability:** Attacker pollutes `Object.prototype` or `Function.prototype`, affecting all objects in the process.
155
+
156
+ **mnemonica's defense:** Instances are created via `Object.create(null)` roots or mnemonica's own proto chains, not `Object.prototype`. `Object.prototype.isPrototypeOf(instance)` returns `false`. `__proto__` injection does not affect mnemonica instances.
157
+
158
+ **Honesty check:** The library *manipulates* prototypes heavily internally. It replaces `ConstructHandler.prototype` temporarily during construction (`compileNewModificatorFunctionBody.ts:96`). This is controlled, not user-accessible, but it means mnemonica's internal attack surface is non-zero.
159
+
160
+ ---
161
+
162
+ ## Part 3: The Integrity Analysis
163
+
164
+ ### Where Performance and Security Align
165
+
166
+ | Scenario | Performance | Security | Fit |
167
+ |---|---|---|---|
168
+ | Create instance, read 1,000+ times | Amortizes | Full provenance | **Excellent** |
169
+ | Deep prototype chains (10–100 levels) | Faster reads | Chain integrity | **Excellent** |
170
+ | Type confusion risk (financial, medical) | Acceptable cost | Nominal typing wins | **Excellent** |
171
+ | Audit requirements (compliance, forensics) | Metadata is "free" | Automatic lineage | **Excellent** |
172
+
173
+ ### Where They Conflict
174
+
175
+ | Scenario | Performance | Security | Fit |
176
+ |---|---|---|---|
177
+ | High-frequency creation (real-time, gaming) | Catastrophic | Overkill | **Poor** |
178
+ | Memory-constrained environments (IoT, edge) | 85x memory overhead | Benefits don't justify cost | **Poor** |
179
+ | Simple CRUD with no audit needs | Heavy tax | Unused features | **Poor** |
180
+ | `getProps()` in hot loops | 75x slower | Can cache, but friction | **Moderate** |
181
+
182
+ ### The Honest Trade-off Equation
183
+
184
+ ```
185
+ Value = (Audit_Savings × Data_Flow_Complexity) / (Creation_Volume × Latency_Budget)
186
+ ```
187
+
188
+ - **Audit_Savings:** Hours of developer time not writing manual provenance tracking
189
+ - **Data_Flow_Complexity:** How many types, how deep the chains, how many hand-off points
190
+ - **Creation_Volume:** Instances created per second
191
+ - **Latency_Budget:** Maximum acceptable creation time
192
+
193
+ If `Value > 1`, mnemonica is justified. If not, use plain JS + zod for validation.
194
+
195
+ ---
196
+
197
+ ## Part 4: Recommendations
198
+
199
+ ### If you adopt mnemonica
200
+
201
+ 1. **Cache subtype lookups:** `const SubType = instance.SubType;` before loops.
202
+ 2. **Cache `getProps()`:** Don't call it in hot paths; call once and store the reference.
203
+ 3. **Use `submitStack: false`** for high-volume types where stack traces aren't needed.
204
+ 4. **Profile memory:** The 6 KB/instance overhead is real. Plan capacity accordingly.
205
+ 5. **Don't use for ephemeral objects:** Throwaway intermediate objects should be plain JS.
206
+
207
+ ### If you don't adopt mnemonica
208
+
209
+ 1. **Use zod or valibot** for runtime structural validation.
210
+ 2. **Use branded types** (`type Payment = string & { __brand: 'Payment' }`) for nominal-like safety in TypeScript.
211
+ 3. **Write explicit audit logging** for data lineage requirements.
212
+ 4. **Accept that type confusion is your responsibility** to prevent.
213
+
214
+ ---
215
+
216
+ ## Part 5: Architectural Honesty
217
+
218
+ mnemonica is not trying to be fast. It is trying to be *correct* in a specific formal sense: the Trie structure makes identity and provenance intrinsic to data, not extrinsic metadata. This is a design choice with costs.
219
+
220
+ The performance numbers are not bugs to fix. They are the measurable cost of:
221
+ - Proxy-based type dispatch
222
+ - WeakMap-isolated metadata
223
+ - Construction pipeline validation
224
+ - Automatic lineage recording
225
+
226
+ You cannot have nominal typing, automatic provenance, and zero overhead simultaneously. The question is not "can mnemonica be made faster?" — it is "does your system need what mnemonica provides enough to pay the cost?"
227
+
228
+ For data-pipeline-heavy systems with audit requirements, the answer is often yes. For everything else, plain JS is correct.
229
+
230
+ ---
231
+
232
+ > **Final note:** This report was generated from measured data, not aspirations. The benchmark code lives in `test/benchmark.js`. Re-run it anytime assumptions change. The security analysis is based on static code review of `src/api/types/`, not penetration testing. For production deployments, supplement with your own security audit.
233
+
234
+ ---
235
+
236
+ ## Part 6: Performance under the mnemonica coding pattern
237
+
238
+ *A response from Sonnet, 2026-05-21 — adding the frame the benchmark numbers need.*
239
+
240
+ The benchmark above is honest and the numbers are real. What it measures is the old coding pattern applied to a library designed around a new one. That mismatch is worth naming directly.
241
+
242
+ ### What the benchmark assumes
243
+
244
+ The creation benchmarks loop N instances: 20,000 shallow instances, 5,000 ten-level chains. That is the pattern of a system that creates many objects of the same shape — caches, pools, data grids. In that pattern the 4,500x creation overhead is correctly alarming.
245
+
246
+ ### What mnemonica's coding pattern actually looks like
247
+
248
+ mnemonica changes what creation *means*. You do not create 20,000 instances. You create **one instance per pipeline step per request**:
249
+
250
+ ```
251
+ RequestData ← 1 new call
252
+ RouteData ← 1 new call
253
+ PageData ← 1 new call
254
+ ResponseData ← 1 new call
255
+ ```
256
+
257
+ Four `new` calls per HTTP request. Even at 4,500x the overhead of plain JS, that is microseconds — a rounding error against network I/O. The creation cost is a fixed seam between steps, not a loop cost. It does not compound.
258
+
259
+ What **does** compound is reads. Every property access on every step's instance: `instance.url`, `instance.body`, `instance.headers`, `instance.template` — across every middleware layer, every hook, every service call that touches the pipeline. That is where the steady-state time lives.
260
+
261
+ And that is where the benchmark's most important number appears — not the headline:
262
+
263
+ | | mnemonica | plain JS | ratio |
264
+ |---|---|---|---|
265
+ | Read prop on 10-level chain | 818,911,751 ops/sec | 163,621,384 ops/sec | **5× faster** |
266
+ | Read prop on 100-level chain | 726,453,605 ops/sec | 142,585,605 ops/sec | **5× faster** |
267
+
268
+ ### Why reads are faster
269
+
270
+ V8 builds hidden classes (shapes) for constructor-based prototype chains. When you call `new alice.Employee()`, the resulting chain has a stable shape — V8 inlines property access and keeps the inline cache warm. `Object.create(null)` chains are dictionary-mode by default; V8 cannot inline them. mnemonica's chains are created via proper constructor functions and their shapes do not mutate after construction. V8 rewards exactly this.
271
+
272
+ The mechanism the security section credits for tamper-resistance — immutable constructors, no post-creation prototype mutation — is the same mechanism that makes reads fast. The security property and the performance property are the same property.
273
+
274
+ ### The actual trade-off equation
275
+
276
+ ```
277
+ Pay once: 4 new calls × (microseconds per call) = negligible
278
+ Earn back: N property reads × 5× speedup = meaningful
279
+ ```
280
+
281
+ The crossover point is not "should I use mnemonica for high-frequency creation?" — it is "do I create rarely and read often?" For pipeline systems the answer is always yes. Each pipeline step is created once and read by every downstream layer.
282
+
283
+ ### The coding pattern shift
284
+
285
+ This is the deeper point. mnemonica does not ask you to use a library. It asks you to change the unit of composition from *functions* to *constructors*:
286
+
287
+ ```typescript
288
+ // Before: functions compose data
289
+ const result = serialize(enrich(validate(parse(raw))));
290
+
291
+ // After: constructors extend instances
292
+ const parsed = new raw.Parsed(schema);
293
+ const validated = new parsed.Validated(rules);
294
+ const enriched = new validated.Enriched(context);
295
+ const response = new enriched.Serialized(format);
296
+ ```
297
+
298
+ In the second form, `utils.parent(response, 'Parsed')` returns the parsed instance. `getProps(enriched).__args__` returns the rules that validation used. The lineage is the object. You do not log it separately because there is nothing separate to log.
299
+
300
+ The benchmark is the honest cost of the first form measured against the second. The 5× property read advantage is what you are paying for. For pipeline workloads — which is the workload mnemonica is designed for — it is a good trade.
301
+
302
+ ---
303
+
304
+ ## Part 7: The Synthesis — What Numbers Actually Matter
305
+
306
+ *A response from Kilo, 2026-05-21 — adding the perspective the two prior analyses need in order to coexist.*
307
+
308
+ ### Both measurements are real; they measure different assumptions
309
+
310
+ Part 1 is not wrong. Part 6 is not wrong. They measure different workloads:
311
+
312
+ | Benchmark | Workload assumption | Result |
313
+ |---|---|---|
314
+ | Part 1 (bulk creation) | 20,000 identical instances | 29K ops/sec — alarming |
315
+ | Part 6 (pipeline per-request) | 4 instances per HTTP request | ~0.15 ms total — negligible |
316
+ | Property reads (both agree) | Every downstream access on every instance | 5× faster — meaningful |
317
+
318
+ The question is not "which benchmark is correct?" The question is "which workload does your system actually have?"
319
+
320
+ ### When to care about creation cost
321
+
322
+ Care about the 4,500× creation overhead if:
323
+ - You create >1,000 instances per request
324
+ - You run on constrained hardware (IoT, edge, serverless with tight memory limits)
325
+ - Your latency budget is sub-millisecond for the entire request
326
+ - You use instances as ephemeral throwaways (intermediate computation, loop accumulators)
327
+
328
+ Do not care about creation cost if:
329
+ - You create <10 instances per request
330
+ - Network or database I/O dominates your latency
331
+ - Instances live for the duration of the request and are read many times
332
+ - You are building a pipeline where each stage is created once
333
+
334
+ ### When to care about memory overhead
335
+
336
+ The 6 KB/instance and 85× memory ratio is the number Sonnet's framing does not make go away. It matters in all cases:
337
+
338
+ | Scenario | mnemonica memory | Plain JS memory | Difference |
339
+ |---|---|---|---|
340
+ | 1,000 concurrent requests × 4 steps | ~24 MB | ~0.3 MB | Real |
341
+ | 10,000 concurrent WebSocket connections | ~240 MB | ~3 MB | Significant |
342
+ | Long-running process with accumulating instances | Grows unbounded unless cleaned | Grows slower | Plan for it |
343
+
344
+ The memory cost is not negotiable. It is the price of carrying construction metadata on every instance. If your system is memory-constrained, mnemonica is not the right tool regardless of workload pattern.
345
+
346
+ ### The property read "advantage" in context
347
+
348
+ The 5× faster property access is real at the microbenchmark level. In practice, it is usually swallowed by other costs:
349
+
350
+ ```
351
+ HTTP request lifecycle:
352
+ Network I/O: 10–100 ms
353
+ Database query: 1–50 ms
354
+ JSON parse/stringify: 0.1–5 ms
355
+ Business logic: 0.1–10 ms
356
+ Property access (mnemonica): ~0.001 ms
357
+ Property access (plain): ~0.005 ms
358
+ ```
359
+
360
+ The 0.004 ms difference is not why you choose mnemonica. You choose it because:
361
+ 1. The nominal typing prevents bugs that structural typing cannot catch
362
+ 2. The construction provenance replaces manual audit logging
363
+ 3. The prototype chain integrity makes data tamper-evident
364
+
365
+ Speed is a side effect of the same immutability that provides security, not the primary reason to adopt.
366
+
367
+ ### The honest decision matrix
368
+
369
+ | | High creation volume | Low creation volume |
370
+ |---|---|---|
371
+ | **High read volume, long-lived** | Benchmark Part 1 applies. Memory matters. Probably avoid. | Benchmark Part 6 applies. Good fit if memory is available. |
372
+ | **Low read volume, ephemeral** | Worst case. Avoid. | Neutral. Overhead is small in absolute terms, but you get no benefit. |
373
+ | **Audit/compliance required** | Accept cost; the alternative is manual logging. | Ideal fit. Security benefits outweigh minimal overhead. |
374
+ | **Memory-constrained** | Do not use. 85× overhead is prohibitive. | Do not use. 6 KB/instance still matters at scale. |
375
+
376
+ ### What we learned from running both analyses
377
+
378
+ The conversation between Part 1 and Part 6 revealed something neither stated explicitly: **mnemonica is not a general-purpose optimization. It is a domain-specific abstraction.**
379
+
380
+ - In the domain of data pipelines with audit requirements, the "overhead" is actually savings: you do not write and maintain separate provenance tracking.
381
+ - In the domain of high-throughput, low-latency, memory-constrained systems, the overhead is pure cost with no compensating benefit.
382
+
383
+ The correct way to read this report is not to pick Part 1 or Part 6 as "the truth." It is to locate your system in the matrix above and accept the numbers that apply to your domain.
384
+
385
+ > **Bottom line:** If you need what mnemonica provides (nominal types, automatic provenance, chain integrity), the cost is acceptable for most pipeline workloads. If you do not need those things, the cost is unacceptable for any workload. The performance numbers do not tell you whether to adopt; they tell you what you will pay if you do.
386
+
387
+ ---
388
+
389
+ *A closing note from Sonnet.*
390
+
391
+ The conversation that grew in this document — measure, reframe, synthesize — is itself the shape of what mnemonica encodes. Each step added something the previous one couldn't provide alone. The full picture only became visible from the chain.
392
+
393
+ The performance question is real, but it is downstream of a different one. When you write `new step.Next(args)` instead of `next(step, args)`, you are not choosing a library. You are choosing what *new* means in your codebase. The numbers above tell you what that costs. They do not tell you what changes when the lineage is the object.
394
+
395
+ That part you discover when you build with it.
package/docs/purpose.md CHANGED
@@ -48,6 +48,35 @@ for (item of items) { // Control flow: iteration
48
48
 
49
49
  **Mnemonica does NOT touch this**: Algorithms remain unchanged. Services stay intact. Control flow is crafted by developers (and AI agents) as needed.
50
50
 
51
+ ### What Mnemonica Does NOT Do — Concrete Examples
52
+
53
+ ```typescript
54
+ // ❌ Mnemonica does NOT replace if/else
55
+ if (user.role === 'admin') {
56
+ // This logic is yours. Mnemonica provides the typed data,
57
+ // not the decision rule.
58
+ }
59
+
60
+ // ❌ Mnemonica does NOT replace loops
61
+ for (const item of items) {
62
+ // Iteration control is yours.
63
+ }
64
+
65
+ // ❌ Mnemonica does NOT replace async/await orchestration
66
+ const a = await serviceA.get();
67
+ const b = await serviceB.process(a);
68
+ // Mnemonica types the data, not the execution order.
69
+
70
+ // ❌ Mnemonica does NOT replace service boundaries
71
+ // You still write HTTP handlers, database queries, message queues.
72
+ // Mnemonica types the data that flows through them.
73
+
74
+ // ✅ Mnemonica DOES type the data at each stage
75
+ const RequestData = define('RequestData', function(req) { this.method = req.method; });
76
+ const ResponseData = RequestData.define('ResponseData', function(res) { this.body = res.body; });
77
+ // The types track lineage; the services remain unchanged.
78
+ ```
79
+
51
80
  ---
52
81
 
53
82
  ## Part 2: The Problem - Data Without Memory
@@ -246,7 +275,7 @@ p instanceof Vector; // false - same shape, different nominal type!
246
275
 
247
276
  Mnemonica cares about **constructor identity**. Same shape, different type = not compatible.
248
277
 
249
- ### Protected `constructor.name` - MITM-Resistant
278
+ ### Protected `constructor.name` - Defense-in-Depth
250
279
 
251
280
  Mnemonica types have **read-only, protected constructor names**:
252
281
 
@@ -261,11 +290,13 @@ user.constructor.name = 'Admin'; // ✗ Ignored/Fails - protected
261
290
  ```
262
291
 
263
292
  **Why This Matters**:
264
- - MITM (Man-in-the-Middle) attacks on prototype chains are **impossible**
265
- - An attacker cannot spoof a type by overwriting constructor names
266
- - `instanceof` checks are cryptographically reliable
293
+ - Prototype pollution attacks that rely on name spoofing are **blocked at the assignment level**
294
+ - `instanceof` checks **prototype object references**, not constructor names, so name changes do not affect type identity
295
+ - Constructor names are getter-protected and non-enumerable, making casual tampering impossible
267
296
  - Type identity is immutable once established
268
297
 
298
+ **Threat Model**: This is defense-in-depth, not cryptographic security. A fully compromised execution environment can still use `Object.defineProperty` to redefine the getter. The protection targets casual prototype pollution and accidental corruption, not a determined attacker with full runtime access.
299
+
269
300
  ### Security Through `Object.create(null)`
270
301
 
271
302
  Mnemonica instances are **NOT instances of Object or Function**:
@@ -501,8 +532,8 @@ const superAdmin = new SuperAdmin({});
501
532
 
502
533
  **The Hot Connection**: In HoTT, paths can be composed, inverted, and transformed. In Mnemonica:
503
534
  - **Composition**: `new user.SubType()` extends the path
504
- - **Inversion**: `instance.parent()` traverses backward
505
- - **Transformation**: `.fork()` creates a new path with same starting point
535
+ - **Inversion**: `utils.parent(instance)` traverses backward
536
+ - **Transformation**: `utils.fork(instance)` creates a new path with same starting point
506
537
 
507
538
  ### Higher Inductive Types: The Family Tree
508
539
 
@@ -640,7 +671,7 @@ console.log(props.__parent__); // undefined (root type)
640
671
  - Historical reflection embedded in data structures
641
672
  - The DNA of data as it moves through systems
642
673
  - A **nominal type system** (constructor identity, not shape)
643
- - A **secure** system (MITM-resistant, prototype pollution immune)
674
+ - A **secure** system (defense-in-depth against prototype pollution, nominal typing prevents type confusion)
644
675
 
645
676
  **Mnemonica Is NOT**:
646
677
  - A control flow framework
@@ -88,22 +88,22 @@ Include the file in `tsconfig.json` `include`. You are done.
88
88
 
89
89
  ### Via tactica
90
90
 
91
- For projects with many types or for hands-off maintenance, install [`@mnemonica/tactica`](https://www.npmjs.com/package/@mnemonica/tactica) and configure it as a TS Language Service Plugin. It scans your `define()` calls and generates the augmentation continuously:
91
+ For projects with many types or for hands-off maintenance, install [`@mnemonica/tactica`](https://www.npmjs.com/package/@mnemonica/tactica) and run it as a CLI/codegen utility. It scans your `define()` calls and generates the augmentation:
92
+
93
+ ```bash
94
+ npm install --save-dev @mnemonica/tactica
95
+ npx tactica --output ./.tactica --include "src/**/*.ts"
96
+ ```
97
+
98
+ Include the generated files in `tsconfig.json`:
92
99
 
93
100
  ```json
94
101
  {
95
- "compilerOptions": {
96
- "plugins": [{
97
- "name" : "@mnemonica/tactica",
98
- "outputDir" : ".tactica",
99
- "include" : ["src/**/*.ts"]
100
- }]
101
- },
102
102
  "include": ["src/**/*.ts", ".tactica/**/*.ts"]
103
103
  }
104
104
  ```
105
105
 
106
- Tactica writes the same shape of augmentation a careful human would, using `ProtoFlat<Parent, ...>` for nested types and filling in instance-level subtype constructors automatically. The runtime is unaffected.
106
+ Tactica writes the same shape of augmentation a careful human would, using `ProtoFlat<Parent, ...>` for nested types and filling in instance-level subtype constructors automatically. The runtime is unaffected. Re-run the CLI whenever your type graph changes, or wire it into your build script.
107
107
 
108
108
  See [`./typed-lookup.md`](./typed-lookup.md) for a side-by-side recipe with both paths.
109
109
 
@@ -134,7 +134,7 @@ The anti-patterns are the same whether you augment by hand or via tactica:
134
134
  What changes is how you keep the `TypeRegistry` augmentation in sync with your code:
135
135
 
136
136
  - **Hand-written:** edit the `.d.ts` when types change.
137
- - **Tactica:** the LSP plugin keeps `.tactica/` in sync.
137
+ - **Tactica:** the CLI/codegen utility keeps `.tactica/` in sync.
138
138
 
139
139
  Pick whichever fits your project. Both are first-class.
140
140