This document is Tessera’s architectural rationale — the reasoning behind the patterns and decisions that shape the codebase. It is organized as a narrative Architecture Decision Record (ADR), a recognized format in software engineering for capturing not just what was decided but why, and what alternatives were considered. It is a companion to the Architecture Overview, which describes what Tessera does and how its layers are organized.
Tessera is a modular monolith. This description is worth being direct about because the alternative framing — “a set of loosely coupled services” — would be misleading and would invite the wrong questions.
Everything runs inside NetSuite’s SuiteScript 2.1 execution environment. The Metric Definition Layer, Cache & Refresh Engine, Query Layer, portlet renderer, Metric Wizard, and Admin console share the same runtime, the same record model, and the same deployment unit. They are modules of one application.
This is the correct architecture for this problem:
The deployment target resolves the question. SuiteScript 2.1 has no concept of external services. There is no network between execution contexts. The platform does not leave room for a services architecture.
Governance semantics require transactional consistency. A metric approval that transitions the metric’s status and writes an audit event must be atomic. In a monolith, this is straightforward. Across distributed services, it requires coordination infrastructure that adds complexity without adding capability.
GU budget is per-script-execution. NetSuite charges governance units per execution context. Decomposing into network-calling services would multiply GU consumption with no benefit.
The modular part is a deliberate choice. Each layer has a defined interface, a single owner, and a deliberate dependency direction. The Query Layer does not depend on the Cache & Refresh Engine. The portlet renderer depends on the Metric Result record (R6), not on the engine that produced it. These boundaries are enforced by TypeScript interfaces in tessera_ports.ts — module separation is a compile-time guarantee, not a convention.
Sam Newman, who wrote the canonical text on microservices, has spent considerable effort writing about when not to use them. A well-structured monolith with genuine module boundaries is often the correct starting point, and sometimes — as here — the correct permanent answer.
Tessera’s most important invariants are enforced twice: once at the type level (compile time), and again at the runtime level (defense in depth). The type-level enforcement comes first.
The principle is articulated by Scott Wlaschin in the context of functional domain modeling: use the type system to make invalid states impossible to represent, rather than relying on runtime guards to catch them. A runtime guard that blocks a closed-period result from being rewritten is useful. A type system that makes it impossible to pass a closed-period result to the write function at all is better — the mistake becomes a compile error rather than a runtime catch.
TypeScript’s structural type system treats any two objects with the same shape as interchangeable. Branded types break structural equivalence by attaching a phantom type tag that exists only at the type level and is stripped at compile time. The runtime impact is zero.
type Brand<T, B> = T & { readonly __brand: B };
type ClosedPeriodResult = Brand<MetricResultDTO, 'ClosedPeriodResult'>;
type OpenPeriodResult = Brand<MetricResultDTO, 'OpenPeriodResult'>;
type ApprovedMetric = Brand<MetricDefinitionDTO, 'ApprovedMetric'>;
type BaselineComponent = Brand<QueryComponentDTO, 'BaselineComponent'>;
The write function accepts only OpenPeriodResult. Passing a ClosedPeriodResult is a compile error. The CRL’s execution path accepts only ApprovedMetric. Passing a draft or pending metric is a compile error. The component mutation function accepts only CustomComponent. Passing a BaselineComponent is a compile error.
Branded types are applied at trust boundaries — the points where data crosses from NetSuite records into the typed Tessera domain. Outside of these constructor functions, casts do not appear. The compiler enforces everything downstream.
| Boundary | What gets branded |
|---|---|
loadApprovedMetrics() — CRL input stage |
Raw R1 records → ApprovedMetric |
asClosedPeriodResult() — CRL write check |
Raw R6 records with closed period → ClosedPeriodResult |
signOff() — Wizard approval gate |
Validated pending metric → ApprovedMetric |
fork() — Registry fork action |
Baseline component → DraftCustomComponent |
The Metric Definition moves through four states. Each state is a distinct branded type. Transitions are functions — the only way to move between states.
DraftMetric → PendingApprovalMetric → ApprovedMetric → SupersededMetric
Attempting to execute a DraftMetric or approve it directly without a submission step is a compile error. The state machine is enforced by the type system, not by runtime status checks scattered across the codebase.
The Architecture Overview describes CQRS at the product level — what it means for correctness and the “Controller would sign this” guarantee. This section describes the implementation boundary.
The write side and read side are separated by the Metric Result record (R6) and the Event Log (R18).
Write side — commands and their events:
Every state-mutating operation in Tessera is a command issued by a named actor. Each command produces one or more domain events written to R18 (the append-only Event Log). The significant commands:
| Actor | Command | Domain Events |
|---|---|---|
| Controller / Domain Owner | ApproveMetric |
MetricApproved, CRLBackfillQueued |
| Controller | SupersedeMetric |
MetricSuperseded, MetricActivated |
| CRL (system) | ComputeOpenPeriodResult |
MetricResultWritten |
| CRL (system) | InvalidateOpenPeriodResult |
MetricResultInvalidated |
| CRL (system) | ExecuteFleetRun |
FleetRunStarted, FleetRunCompleted |
| NetSuite (trigger) | Transaction posted | MetricResultInvalidated (via User Event) |
Read side — queries with no state mutation:
Portlet rendering, drill-down, Audit Report, and Registry Catalog are pure reads. No state changes on the read path. The Audit Report reads from R18 to reconstruct governance history. The portlet reads from R6. Neither path touches the write-side computation logic.
Why the event log matters: R18 is not an event source — NetSuite custom records remain the system of record. R18 is a derived log of state transitions that makes the governance history reconstructable without replaying record queries. It is also the boundary across which the write side communicates with read-side projections (Admin console fleet status, Audit Report) without coupling the sides directly.
Core domain logic is isolated from NetSuite infrastructure through a ports-and-adapters layer in tessera_ports.ts.
Ports (interfaces the domain depends on): IQueryExecutor, IRecordLoader, IResultStore, ICacheStore, IFileStore.
Adapters (NetSuite implementations of each port): thin wrappers around N/record, N/query, N/cache, and N/file.
Domain logic — the metric definition model, formula evaluator, governance rules, versioning model — depends on the interfaces, not on NetSuite APIs directly. The adapters are the bounded surface that changes when the platform changes. The domain logic does not.
The practical consequence is the test suite. The query resolver and executor tests run in Node without a NetSuite instance. The formula evaluator is tested independently of the query layer. Module boundaries enforced by interface tend to be testable by isolation — this is not a coincidence, it is the mechanism.
NetSuite charges governance units (GUs) for script execution. The architecture is GU-conscious throughout:
Closed-period skip. The CRL Tier 1 policy computes a closed-period result exactly once. Every subsequent fleet run skips it at zero GU cost. A ClosedPeriodResult branded type makes the skip structurally enforced — the write function cannot accept it.
Sensitivity profiles. The invalidation User Event does not mark all metric results stale on every transaction post. It maintains a materialized Metric-Account Invalidation map (a junction record linking approved metric versions to the GL accounts they query). On transaction post, only metrics sensitive to the posted transaction’s accounts are marked stale.
Paginated queries. All unbounded SuiteQL queries use 1,000-row pages. Multi-period computations partition by accounting period. The pagination function (runSuiteQLPaged) is the only entry point for unbounded queries.
Map/Reduce for fleet. Bulk metric computation runs as Map/Reduce, not inline scripts. This is the correct SuiteScript pattern for any operation that scales with the number of approved metrics and must survive governance unit limits.
All 18 custom record types, all Query Templates, the Wizard UX flow, the portlet rendering model, and the security role model were fully designed and documented before production coding began. The design documentation — approximately 25 architecture documents — is maintained as a versioned artifact alongside the source code.
The rationale: a governance product whose design is discovered during implementation will have inconsistencies in its governance model. A governance product that is fully designed first can make and verify the consistency guarantees before any code commits them.
This is also why the test suite existed before the NetSuite sandbox was available. The Ports and Adapters boundary makes domain logic testable in Node; the design-first discipline means the test cases could be written from the design documents.