Loop MMT — Coding Standards v2 · disclosure funnel
Code is read far more often than it is written. Every line of code in a Loop MMT constellation will be read by future AI sessions that have never seen it before, by the Integrator verifying it against the spec, by the Operator debugging a production issue at 2am, and potentially by developers (human or AI) maintaining the system years after the original conversations are gone. The code must serve all of these readers. Performance matters. Correctness matters more. But readability matters most — because unreadable code cannot be verified for correctness and cannot be safely optimized for performance.
This is not an abstract preference. It is a structural requirement of the Loop MMT methodology. Each module conversation sees only its own loading pack. The Integrator sees the full spec but reads every module's code to verify it. A future bug-fix session will load this code cold and need to understand it immediately. If the code cannot be understood by a reader with no context beyond the section header and the contracts, the code is not done — regardless of whether it runs correctly.
Robert C. Martin's Clean Code established the principle that code should be written for human readers first and for the machine second. This principle is absolute in Loop MMT. The machine will execute whatever you give it. The human (or the next AI session) must be able to understand what you gave it and why.
This means: choose clarity over brevity every time. A variable named v_remainingRetryAttempts is better than v_retries. A function named f_calculateTieredDiscountForLoyaltyCustomer is better than f_calcDisc. Yes, the longer names take more characters. Characters are free. Comprehension is expensive.
This also means: do not optimize for performance until you have measured a problem. Readable code that runs in 5ms is better than clever code that runs in 2ms. The 3ms you saved will cost hours when someone needs to understand the clever version. Optimize only when profiling shows a specific bottleneck, and when you do optimize, leave a comment explaining why the readable version was replaced and what the performance constraint was.
Names are the primary mechanism by which code communicates intent. A well-chosen name eliminates the need for a comment. A poorly chosen name creates a need for a comment and then fails to satisfy it.
Use intention-revealing names. The name should answer: why does this exist, what does it do, and how is it used. v_elapsedTimeInDays communicates. v_d does not. f_validateOrderHasRequiredFields communicates. f_check does not. The reader should never have to look at the implementation to understand what the function does — the name should tell them.
Avoid disinformation. Do not call a collection v_orderList if it is not a list (it might be a Set or a Map). Do not call a variable v_accountGroup if it is a single account. Names that imply the wrong data structure or cardinality mislead the reader into wrong assumptions.
Make meaningful distinctions. If you have two variables in the same scope, their names must make it clear why both exist. v_order and v_orderData — what is the difference? v_orderFromDatabase and v_orderForPacket — now I know. If you cannot name two things distinctly, they may not need to be two things.
Use pronounceable names. Code is discussed verbally — in the talk phase, in handoffs, in bug reports. v_genymdhms cannot be spoken in conversation. v_generationTimestamp can. If you cannot say the name out loud in a sentence, the name is wrong.
Use searchable names. Single-letter variables and short generic names are impossible to search for in a codebase. MAX_RETRY_ATTEMPTS can be found instantly. 3 buried in a conditional cannot. This matters because the Integrator searches across modules to verify consistency.
Pick one word per concept and stick with it. If Vault Loops use fetch for read operations, all Vault Loops use fetch. Not get in one and retrieve in another and load in a third. If packet types use _CREATED for post-write echoes, they all do. Not _MADE or _ADDED. Consistency across the codebase is more important than the specific word chosen.
Functions should be small. How small? Small enough that they do exactly one thing and you can describe that thing in a single sentence without using the word "and." If the description requires "and," the function is doing two things and should be split.
The stepdown rule. Code should read like a narrative, top to bottom, where each function is followed by the functions at the next level of abstraction. The top-level handler reads like prose: "validate the order, hydrate the customer data, calculate the price, persist the record, emit the echo." Each of those steps is a function. Each of those functions contains its own lower-level steps. The reader can stop at whatever level of detail they need.
// ── Top level reads like prose ──
async function h_processOrder(packet) {
const v_validationResult = f_validateOrderFields(packet.payload);
if (!v_validationResult.valid) {
f_emitValidationRejection(v_validationResult.errors);
return;
}
const v_customer = await f_hydrateCustomer(packet.payload.customerId);
if (!v_customer) {
f_emitCustomerNotFound(packet.payload.customerId);
return;
}
const v_pricedOrder = f_calculateOrderPrice(packet.payload, v_customer);
await f_persistOrder(v_pricedOrder);
f_emitOrderCreated(v_pricedOrder);
}
// Each called function handles one concern at the next level down.
// The reader understands the flow without reading any of them.
One level of abstraction per function. Do not mix high-level business logic with low-level implementation details in the same function. If a function calls f_calculateDiscount (high-level) and also does Math.round(price * 100) / 100 (low-level rounding detail), the rounding belongs in a helper: f_roundToTwoDecimals. The reader should not have to mentally switch between abstraction levels while reading a single function.
Functions should have no side effects. A function named f_validateOrderFields should validate and return a result. It should not also log to the audit trail, modify the packet, or emit a bus event. If it does those things, the name is a lie — the reader expects validation only and gets surprised by hidden behavior. Side effects belong in the calling function where they are visible and explicit.
The best comment is code that doesn't need one. A function named f_rejectOrderIfCustomerIsArchived does not need a comment explaining what it does. A variable named v_maximumAllowedWeightInPounds does not need a comment explaining what it holds.
When you feel the need to write a comment, first ask: can I rename the function or variable to make the comment unnecessary? Can I extract a block of code into a well-named function? Can I restructure the logic to be more obvious?
When comments are necessary, they explain why, not what. "Why" comments survive code changes; "what" comments become lies the moment the code is modified and the comment is not updated. Good comments explain: business rules that are not obvious from the code, why a particular approach was chosen over a simpler alternative, what would break if this code were changed, and edge cases that are not apparent from reading the happy path.
// GOOD: explains WHY — the business rule behind the code
// Orders under $10 get free shipping because the processing cost
// exceeds the shipping fee at that price point
if (v_orderTotal < 1000) {
v_shippingCost = 0;
}
// BAD: explains WHAT — restates the code, adds no information
// Check if order total is less than 1000
if (v_orderTotal < 1000) {
v_shippingCost = 0;
}
Loop MMT requires a comment above every function (see Section 8). This is the one place where the "last resort" rule is overridden — the function comment serves as a quick-reference entry point for readers scanning the file. But even this comment should explain why the function exists, not what it does line by line.
Code formatting is not cosmetic. It communicates structure. Related code should be close together. Unrelated code should be separated by whitespace. The vertical distance between two pieces of code should reflect their conceptual distance.
Vertical ordering. High-level functions at the top, lower-level helpers below. The reader enters at the top and drills down as needed. This is the newspaper metaphor from Clean Code: the headline is at the top, the details are below, and you stop reading when you have enough information.
Horizontal density. Keep lines short enough to read without scrolling. Long lines force the reader to track two dimensions (down and across). If a function call has six arguments and doesn't fit on one line, it may need restructuring — not line-wrapping.
Consistent formatting across the entire codebase. Every loop factory follows the same internal structure (Section 9). Every handler has the same error-handling shape. Every packet emission has the same field order (type, marker, payload, source). Consistency lets the reader's pattern recognition do the work — they learn the shape once and recognize it everywhere.
Leave the code cleaner than you found it. If a bug-fix session touches a function with a misleading name, rename it. If a module conversation notices a magic number while working nearby, extract it to a named constant. Small, continuous improvements prevent the gradual accumulation of unreadable code.
This rule has a boundary in Loop MMT: do not refactor outside your scope without flagging it to the Operator. The improvement must be within the module you are working on. Cross-module cleanups go through the Coordinator and the Integrator.
Before delivering code, apply this test: could a new AI session, loaded only with this module's loading pack and these coding standards, read this code and understand what it does, why it does it, and how it connects to the rest of the constellation? If the answer is no, the code is not done. The tests may pass. The contracts may match. But if the next reader cannot understand it, it will eventually be changed incorrectly, and the tests and contracts will stop protecting it.
A second test, more demanding: could a person who does not write code — but who understands the business domain — read the top-level handler functions and recognize the business process? "It validates the order, looks up the customer, calculates the price, saves it, and sends a confirmation." If the code is clean enough, a business-literate non-programmer can follow that narrative in the code itself, not just in a comment above it.
f_ functions are pure; side effects are explicit bus emissions in handlers. Don't Repeat Yourself → patterns are named and reusable; config tables drive behavior generically. Comments explain why → every function gets a why-comment, not a what-comment. Consistent formatting → every factory follows the same six-part internal structure. These aren't aspirations. They are requirements. Code that violates them is not ready for delivery.
Full words, no abbreviations. camelCase for variables and functions. UPPER_SNAKE_CASE for constants. Every function has a comment above it explaining why it exists. Error case before happy path. Magic numbers become named constants. No unrequested features. Build exactly what was discussed in the talk phase.
Every variable, function, constant, and factory uses a prefix that identifies its role at a glance. This is not optional.
| Prefix | Meaning | Examples | Notes |
|---|---|---|---|
bus_ | Bus instance | bus_data, bus_vault, bus_signal | The bus objects passed to loop factories. |
loop_ | Loop factory function | loop_vaultOrders, loop_computePricing | Never used for instances — only the factory. |
pkt_ | Packet type constant | pkt_ORDER_CREATED, pkt_FETCH_CUSTOMER | UPPER_SNAKE after prefix. Matches contract registry. |
wf_ | Workflow pipeline spec | wf_cancelOrder, wf_batchNotify | The pipeline spec object, not a function. |
gate_ | Dependency gate instance | gate_supabase, gate_stripe | One per adopted dependency. |
v_ | State variable | v_retryCount, v_connected, v_buffer | Any mutable state within a loop. Scratch state uses this prefix. |
f_ | Pure function / helper | f_calculatePrice, f_validateSchema | Takes inputs, returns output. No side effects. No bus access. |
e_ | DOM element reference | e_intakeForm, e_statusDisplay | Presentation Loop only. Cached at init, not queried per render. |
h_ | Event handler | h_onSubmitOrder, h_onStatusClick | Presentation Loop only. Bound to UI events. |
c_ | Contract definition | c_orderCreated, c_fetchCustomer | The contract object in the registry. |
v_. Is it a function with no side effects? f_. Does it respond to a UI event? h_. Does it talk to the bus? It's a handler inside a subscription, not a standalone function — it doesn't get a prefix, it lives inside the bus.on() callback or is named as a handler within the factory.
Every operation that can fail gets a try/catch. The catch follows the Loop Recovery Protocol: do not crash, alert the bus, preserve state, continue operating.
// ── The standard error handling pattern ──
async function h_processOrder(packet) {
try {
// Error case checks first (NASA rule)
if (!packet.payload.orderId) {
bus.data.emit({
type: 'VALIDATION_REJECTED',
payload: { reason: 'missing_orderId', source: packet },
source: 'vault:orders',
});
return; // Early return — do not process
}
// Happy path
const result = await db.from('orders').insert(packet.payload);
// ... emit echo ...
} catch (err) {
// Alert the bus — specific error context
bus.data.emit({
type: 'LOOP_ERROR',
payload: {
loop: 'vault:orders',
operation: 'CREATE_ORDER',
error: err.message,
// Include enough context to diagnose without replay
packetType: packet.type,
payloadSummary: { orderId: packet.payload?.orderId },
},
source: 'vault:orders',
});
// Do not re-throw. The loop continues to operate.
// State was not modified (the insert failed).
}
}
Error case before happy path. Check for invalid inputs, missing required fields, and precondition failures at the top of every handler. Return early on failure. The happy path should be the unindented code, not nested inside validation checks.
LOOP_ERROR payload. Always include: which loop (loop), which operation (operation), the error message (error), and enough context to diagnose without needing to replay the exact packet. Do not include the full packet payload in the error — it may contain PII. Include identifiers only.
camelCase, full English words, reads like prose. Factory functions use create prefix: createVaultOrders, createFilterOrderValidation, createComputePricing. The factory name encodes the loop type and the business domain.
Handler functions inside factories use descriptive names: h_createOrder, h_fetchCustomer, h_onSubmitIntake. Helper functions use f_ prefix: f_calculateDiscount, f_validateOrderSchema, f_generateId.
UPPER_SNAKE_CASE. Verb-noun structure. The verb describes what happened or what is requested:
| Pattern | Examples | Used For |
|---|---|---|
CREATE_* | CREATE_ORDER | Commands — requesting a write |
*_CREATED | ORDER_CREATED | Echoes — confirming a write happened |
FETCH_* | FETCH_CUSTOMER | Pull requests — requesting data |
*_FETCHED | CUSTOMER_FETCHED | Pull responses |
*_UPDATED | ORDER_UPDATED | Echoes for updates |
*_ARCHIVED | ORDER_ARCHIVED | Echoes for soft deletes |
*_VALIDATED | ORDER_INTAKE_VALIDATED | Filter pass results |
*_REJECTED | VALIDATION_REJECTED | Filter rejection results |
COMPUTE_* | COMPUTE_PRICE | Requesting a computation |
*_CALCULATED | PRICE_CALCULATED | Computation results |
Format: type:businessDomain. The type is one of: working, compute, vault, filter, signal, presentation, workflow. The business domain is camelCase. Examples: vault:orders, compute:pricing, filter:orderValidation, signal:customerNotify, workflow:cancel.
Pipeline spec variable names use wf_ prefix with descriptive name: wf_createOrder, wf_cancelOrder, wf_batchSendNotifications. The name field inside the spec uses snake_case: name: 'create_order'. Step names within the pipeline also use snake_case: name: 'validate_payment'.
snake_case for database tables and columns: orders, customer_id, total_price, created_at. camelCase for JavaScript/packet fields: customerId, totalPrice, createdAt. The Vault Loop translates between the two conventions at the database boundary. This translation is part of the Vault Loop's responsibility and is never done by other loop types.
Every function gets a // comment on the line above it. The comment explains why the function exists, not what it does line-by-line. If the function name and signature don't make the "what" obvious, the function is named poorly — fix the name, don't compensate with comments.
// Translate database snake_case to packet camelCase for the order contract
function f_mapOrderRow(row) { ... }
// Reject orders with missing required fields before they enter the pipeline
function f_validateOrderSchema(payload) { ... }
// The pricing engine uses tiered rates — this prevents a single-item
// order from paying the same base rate as a 50-item bulk order
function f_calculateTieredDiscount(items, customerTier) { ... }
Section dividers within a factory use the single-bar format:
// ── Subscriptions ── // ── Handlers ── // ── Helpers ──
Do not write comments that restate the code. // increment counter above count++ is noise. Write comments that explain why: decisions, business rules, edge cases, things a future reader would wonder about.
Every factory follows the same internal structure:
- Section header — the quick-reference card (type, capabilities, emits, listens, patterns).
- State declarations — any
v_variables the loop maintains (scratch state, bridges, counters). - Subscriptions — all
bus.on()calls grouped together at the top of the factory body. - Handlers — the functions that process each subscribed packet type, in the same order as the subscriptions.
- Helpers — pure
f_functions used by the handlers. - Initialization — any setup that runs when the factory is called (startup sweeps, bridge creation, timer setup).
This order makes every loop readable in the same way. A reader looking for "what does this loop do when it receives ORDER_CREATED?" goes to the subscriptions section, finds the handler name, and jumps to it.
Run through this for every deliverable. Every time.
- Factory signature matches capability table. A Working Loop receives
{ bus }and nothing else. A Vault Loop receives{ bus, db }and nothing else. No extra capabilities. - Section header is accurate. Type, capabilities, emits, listens, patterns — all match the actual code.
- Emitted packets match contracts. Every emitted packet type exists in the contract registry. Every required field is present. Field names are character-for-character matches.
- Subscriptions match routing table. Every
bus.on()call subscribes to a type that has a routing entry with this loop as a destination. - Markers are explicit. Every emitted packet includes
marker: 'live','empty', or'tombstone'. No omissions. - Error handling follows recovery protocol. Every async operation has try/catch. Every catch emits
LOOP_ERRORwith loop name, operation, and error message. No re-throws. No silent catches. - Naming follows taxonomy. Prefix on every variable, function, and constant. Full words. camelCase.
- Comments explain why. Every function has a comment. No "what" comments that restate the code.
- No unrequested features. The deliverable contains only what was agreed on in the talk phase.
- Tests pass. If this module has tests, they are green. If not, the code has been manually verified against the contracts.
| Anti-Pattern | Why | Instead |
|---|---|---|
| Ghost state in closure variables | Invisible to other loops, not on bus, not in Vault | State goes on the bus (emit it) or in a Vault (persist it). Only scratch state is allowed in closures, and it must be cleared per-transaction. |
| Capability leakage | A loop accesses something not in its factory signature | If you need it, it belongs in the capability injection. If it's not in the table for your loop type, your design is wrong. |
| Guessing at field names | Creates the #1 integration error | Check the contract registry. Character-for-character match. If the contract says customerId, do not write customer_id. |
| Silent error swallowing | Failures become invisible | Every catch emits LOOP_ERROR. No empty catch blocks. No catch (e) { /* ignore */ }. |
| Omitting the marker | Ambiguous responses — "no data" vs "lookup failed" becomes indistinguishable | Every emitted packet includes an explicit marker. Every pull response includes a marker. |
| Cross-loop communication outside the bus | Bypasses contracts, routing table, and observability | All inter-loop communication goes through the bus. No shared variables, no direct function calls, no events outside the bus system. |
| Database access outside Vault Loops | Violates the closure wall — only Vault Loops receive db | Need data? Pull it from a Vault via the bus. Need to write? Emit a command to a Vault. |
| Business logic in Presentation Loops | UI should render state, not compute it | Computation goes in a Compute Loop. The Presentation Loop subscribes to the result. |
| Unrequested features | Scope creep, untested code, undiscussed design decisions | Build exactly what was discussed. If you see something useful to add, suggest it — don't implement it. |
| Verbal contracts | "I think the field was called X" introduces mismatches | Check the document. If it's not in the loading pack, ask for it. |
| Returning from factory functions | The factory sets up subscriptions; it doesn't return an API | Loops communicate through the bus, not through return values. (Exception: the bridge helper, which is internal to the loop.) |
| Happy path first | Error cases get buried, forgotten, or half-implemented | Error case before happy path. Validate inputs at the top. Return early on failure. |