** This file was created with an agent. **
Third assessment in the series (see Related documents). Where the post-typing assessment evaluated the strict-TypeScript conversion, this one is a verified current-state snapshot: every metric was measured from the tree on 2026-07-19, not carried over. It adds a definition of the descriptor state machine pattern, a re-verification of earlier claims, a delta analysis, and a ranked risk list.
A descriptor state machine is a state machine whose states are encoded not
in a field’s value but in a property’s descriptor — its kind (accessor vs.
data) and its flags (writable, configurable) — with transitions performed
by Object.defineProperty redefinition.
In _AbstractStructModel, dependencies (and _value) progress through three
descriptor states:
| Phase | Descriptor | Meaning |
|---|---|---|
| Primal | own accessor whose getter throws LIFECYCLE ERROR when read before draft mode; in draft mode it delegates to this[OLD_STATE].dependencies; configurable: true |
reading is illegal in primal state |
| Draft / metamorphose | redefined as data property: value: Object.freeze(dependenciesData), writable: true, configurable: true |
dependencies materialized for comparison against OLD_STATE |
Locked (#_lockAndFreeze) |
redefined: writable: false, configurable: false |
irreversibly immutable; OLD_STATE is deleted for GC |
Properties of the pattern:
configurable is the ratchet — true → true → false makes the final
freeze irreversible; earlier phases must stay redefinable.declare plus a scoped @ts-expect-error (TS2610: instance property
overrides base accessor). The suppression monitors the one formal symptom;
if the base contract is ever refactored, tsc reports the unused directive
and forces a human to re-read this machine.The pattern is deliberate machinery, not accidental complexity — but it is one
of three exotic idioms (with symbol-keyed protocols and generator-based
resource resolution) a newcomer must learn before their first get().
| Metric | Value |
|---|---|
| Files / LOC / functions / classes | 22 / 8,907 / 268 / 37 |
| Language | 100% TypeScript, strict + noUncheckedIndexedAccess, typecheck clean |
| External runtime dependencies | 0 — every import is ./-relative; pure leaf module |
| Consumption | one-line facade lib/js/metamodel.mjs + one direct importer |
| Type-escape hatches | 1 @ts-expect-error, 0 @ts-ignore, 0 as any, 2 intentional : any, 28 non-null assertions; 72 as unknown as (documented post-typing) |
| Tests covering the metamodel | 0 — the repo’s first vitest tests (added 2026-07) cover type-specnion only |
| Error culture | 152 throw new across 15 distinct error classes |
| Comment density | ~23% |
| Internal protocols | 20+ Symbol(...) keys (dunder-style inter-object protocols) |
Closer to a small relational state engine than a frontend state manager:
immutable persistent states, copy-on-write drafts, a primal→draft→frozen
lifecycle, topological dependency resolution, foreign keys with constraints,
coherence functions (cascading invariant triggers), structural diffing, and
generator-based async resource resolution. See path-to-standalone.md for the
comparison against Redux/MobX/Zustand — most of these features have no
counterpart there.
util / type-utils / topological-sort foundations
base-model.ts (780L) _BaseModel, _BaseSimpleModel/_BaseContainerModel,
FreezableMap/Set, serialization drivers
scalar models number, enum, simple, simple-or-empty, generic
container models struct (1580L) · ordered-map (954L) · list (596L) · dynamic-struct (686L)
machinery potential-write-proxy · compare · coherence-function ·
foreign-key · links · accessors · path · serialization
metamodel.ts (211L) curated export hub with an explicit don't-pollute policy
Shallow hierarchy; concrete classes are produced by createClass factories.
Hub-and-spoke: _AbstractStructModel is the gravitational center — its get
is called from 50 sites, has from 44, set from 30.
_IS_DRAFT_MARKER
/ _GET_DRAFT_FOR_PROXY symbol protocols, and copy-on-write escalation are
designed, documented in comments, and internally consistent.ENSURE_DRAFT for
explicit draft creation, compare/descendant enumeration fixes, better
accessor error messages.resolveSyntheticProperties drop-check bug (fixed 2026-07) survived
because no test ran the code. The three bugs the typing pass found were
static-analysis luck, not coverage.get().dependencies means a set of names on the
base class but a name→value record on struct instances — a Liskov-flavored
overload, currently latent (no polymorphic consumer mixes the two). Likely
not unique in a module this size; worth one audit pass for siblings.as unknown as double-casts (documented post-typing). Each tells
the compiler rather than asks it; concentrated risk if invariants shift.potential-write-proxy (540L) added and hardened, compare fixed.any hold;
the single @ts-expect-error is now legal TypeScript (the illegal
declare override combo, TS1243, was removed 2026-07 after it broke node’s
type-stripping parser and vitest’s transform path).dependencies descriptor states,
#_lockAndFreeze, topological init order. ~20 tests would cover the paths
every feature depends on.dependencies-style overloads once — cheap, now that the
pattern’s signature is known.assessment-pre-typing.md — after the monolith→multi-file refactorassessment-post-typing.md — after the strict-TypeScript passpath-to-standalone.md — could this be a library?../agentic-artefacts/thoughts/plan/2026-02-23-metamodel-typing-plan.md — the typing programmodel: moonshotai/kimi-k3 provider: openrouter agent: goose v1.41.0