Skip to content

Latest commit

 

History

History
449 lines (328 loc) · 56.9 KB

File metadata and controls

449 lines (328 loc) · 56.9 KB

AGENTS.md — Codex Governance Contract (Elohim Protocol)

This is Codex's root gospel. The gospel body below the horizontal rule is the SAME text as CLAUDE.md, projected verbatim from the elohim-root-gospel package — the two cannot drift. THIS preamble is the codex-localized governance contract: what Claude Code already receives from edit-time hooks but this repo has not yet projected into Codex's current project-hook ABI. Read it first. Until epr doctor reports the Codex hook projection present and the runtime trusts it, this binds you morally where it cannot bind you mechanically, and it names the gates that bind you mechanically regardless of harness.

1. First contact: inspect before changing

From a fresh clone, activate the checked-in reach gate and inspect the native governance floor before substantive work:

env RUSTFLAGS= CARGO_TARGET_DIR=/tmp/eprfs-onboarding-target cargo run --manifest-path elohim/eprfs/Cargo.toml -q -p elohim-epr-cli -- setup

After that first build, use epr doctor (or the same cargo run … -- doctor) to inspect harness projections and hook activation, epr explain <path> before an unfamiliar edit, and epr check for local advice. Local checks never take authority over the working tree. epr ready [--target <ref>] is the explicit reach rehearsal: it evaluates committed changes against their merge base and fails only findings that would prevent the requested push/merge boundary.

2. Editing agentic surfaces is package-first

The skills, subagents, hooks, and agent-docs (.claude/*, .codex/*, CLAUDE.md, AGENTS.md) are PROJECTIONS of Elohim-native packages under .epr-meta/elohim/packages/. Two disciplines coexist; the marker decides which:

  • A package marked metadata.master: "package" (planted) is authoritative. Edit its package JSON, then regenerate ONLY that surface and re-verify: node elohim/sdk/domains/elohim-agent/scripts/package-projections.mjs project --write-fixtures --write-runtime --only <Kind:id> then … verify. NEVER hand-edit the projected .claude/.codex file — the next projection clobbers it and verify reds on the drift.
  • A Claude-sourced package that is NOT planted (no master) is source-first. Edit its .claude/* source directly, then re-import: … project --write-fixtures (or elohim-agent:packages:write). The package is a certified mirror; the fidelity gate proves project(import(source)) === source.

If unsure which a surface is, read its package's metadata.master. When in doubt, do not edit the projection — edit the package or the source.

3. The .epr-meta compose-gate binds you morally

Claude enforces directory-local governance at edit time via a PreToolUse hook. Current Codex supports project-local lifecycle hooks, but this repo has not yet projected the shared definitions to .codex/hooks.json; until that projection is present and trusted, the SAME rules bind you by contract instead of by mechanism. Before writing a file, honor the nearest .epr-meta ancestor (the cascade: closest manifest wins, root /.epr-meta/manifest.md is the base): a specs/ doc requires lifecycle frontmatter; elohim/ carries an interface-first-reuse advisory (reuse the existing trait/type before adding a parallel one); code directories may carry YAGNI/over-engineering guards. A manifest that would DENY a Claude edit should stop you too — you just won't get a prompt. Treat a missing prompt as your own responsibility, not permission.

4. Gates that bind Codex mechanically (harness-independent)

These run outside any harness and WILL fail your push or CI:

  • .husky/pre-push — project-detected quality gates (lint / format:check / typecheck / tests), plus sweettest-check when the push targets dev/main. Do not bypass with --no-verify unless the gates already ran green.
  • pnpm run elohim-agent:packages:verify — projection/governance drift. If you touched any agentic surface or its package, this must be GREEN before commit.
  • epr flow cites owns cites: frontmatter. NEVER hand-write a slug, path, or sha256: fingerprint — run epr flow cites seal <doc>. A hand-edited fingerprint fails the cite gate.

5. Rails (non-negotiable)

  • Never git add -A / git commit -a — the worktree is shared across concurrent sessions. Stage path-scoped: git add <explicit paths>.
  • Never push without green gates, and never amend or force-push shared history.
  • Native (non-WASM) cargo needs CARGO_TARGET_DIR at the pool slot per the disk policy; WASM/DNA workspaces stay plain cargo.
  • RUSTFLAGS gotcha: the environment sets RUSTFLAGS=--cfg getrandom_backend="custom" for Holochain WASM. Native builds (doorway, steward/node) need RUSTFLAGS=""; elohim/elohim-storage keeps the custom backend flag.

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Repository Overview

Polyglot monorepo for the Elohim Protocol - a distributed learning platform built on Holochain, Angular, and Rust. dev is the integration target (not a protected default): feature branches land on dev via local fast-forward merge — no PR. Release-grade PR review happens at dev → main.

What to work on, and how you know it's done

Four artifacts. Everything else in the development machinery is commentary hanging off them.

  1. The habit register — what this system reliably does, each bound to a runnable check. A habit is DECLARED where its concern lives: <dir>/.epr-meta/<id>.habit.md, in the governance package of the directory whose behaviour it describes (frontmatter = declaration, body = evidence ledger). One scope authority, .epr-meta, so a habit inherits the resolver, the cascade, covers: and retire-when: for free. genesis/manifests/habits.yaml is the generated projection of that walk — never edit it; run .claude/scripts/habits-project.py (pre-push runs --check). No headcount cap; every habit is born with a retire-when: exit condition. Max 2 active (the WIP fence — attention doesn't compose, so it stays a global roll-up), status: green | red | unwired, and flips require evidence (a build number, a live probe, a test run) — never intention. unwired means we committed to a habit with no way to observe whether we keep it; that state is declared and counted on purpose. Render it with .claude/scripts/habits-status.py [--full]. Work = move the top red toward green with proof.
  2. The @concern: tag on the a2o scenario a habit's checks: names — the one identifier that joins a claim to its proof across CI, Gherkin, and this register without prose.
  3. The gate command for the tree you touched — gate.projects in the nearest build-manifest.json names the dir and steps. A lane of work and its verification are the same boundary.
  4. A one-line delta in the habit's own atom (<dir>/.epr-meta/<id>.habit.md) when you finish, then re-project. That is the deliverable, not a summary.

A plan, spec, or sprint that serves no habit belongs in held/, not in flight. If you are about to write a new register, ledger, or ranking script, the answer is almost certainly one of the four above — this layer has repeatedly grown instruments with no reader (see genesis/docs/superpowers/ for the 2026-08-06 nomenclature review).

Bounded recall

When prior work matters, open a governed recall session before loading more history: epr flow memory recall open --session <ceremony-id> --need "Locate current authority". .epr-meta/elohim/algorithms/recall-contract.json is the governed algorithm artifact — routes, composition, budgets and fitness checks — and every receipt pins its CID. Reuse the session for continued questions; the executor pins method bytes and accumulates costs. Use search --search-scope <dir> --name <glob> --query <term> (add --tag <category> for exact frontmatter membership) for metadata candidates, then source --path <p> and read --path <p> --lines <start>:<end> for bounded evidence. Receipts and continuations are PRIVATE session records under .eprfs/status/recall/<session>/: never imported, projected, witnessed or targeted by feedback. Direct shell/MCP reads remain outside this executor and must be accounted separately. Read-only MemPalace recall is discoverable for repository agents; use it only when source routes are insufficient, within the session’s remaining budget. Verify returned source bytes and current native evidence; similarity and index freshness do not establish acceptance. Do not load the palace protocol or write diaries automatically. Curation needs explicit maintenance authority. A deliberately isolated blind-reader retains its one-document boundary. Runtime tool access must be checked, not inferred from package declarations.

Build & Test Commands

Install the workspace once with pnpm install. Sophia is a git submodule with its own pnpm workspace. The root developer interface is intentionally small:

just --list
just gate                     # changed projects, including worktree edits
just gate elohim-storage      # explicit manifest project or owning path
just test app                 # focused test family
just test mesh [scope]        # Act I a2o lane against the local mesh (scope = feature path or tag expr; scoped runs stay scoped); REFUSES before launch when the prologue's hosted-human roster is missing or older than the doorway archive (a cold start drops the archive and the roster with it — without this the lane fails as sixteen opaque `Invalid credentials` 401s); MESH_ALLOW_NO_PROLOGUE=1 for a lane that casts nobody
just dev start                # isolated conductor + storage + doorway
just dev package app/elohim-app # build, check and package an Angular EPR app locally; no upload
# EPR_APP_ADAPTER=./my-adapter.mjs just dev package ./my-client selects a local adapter
just dev conductor alpha      # T3 hybrid rung: a workspace conductor joined to alpha (fork iroh pair, CONDUCTOR_ARC_FACTOR / CONDUCTOR_APP_PORT=4485; stock 0.7 lacks the fork cross-relay preflight fix); auto-offsets to STORAGE_PORT=8095/DOORWAY_PORT=8898 and a sandbox named t3-<profile> beside an already-running household mesh
CONDUCTOR_ADMIN_PORT=39097 just dev conductor alpha  # join-alpha enrollment/resume pins an explicit admin port (1..65535); ordinary resumes retain the recorded .hc_ports port so existing storage/doorway pairing survives; export sourced private env assignments before launching
STORAGE_HAPP_PATH=/absolute/path/to/app.happ just dev conductor alpha  # storage --happ-path in ordinary/release-channel launches; readable file required, unset keeps the storage default; HAPP_BUNDLE_PATH selects doorway provisioning only
just mesh status              # local multi-peer mesh
just mesh storage-restart <peer…> | conductors-restart   # restart arms (export MESH_TRANSPORT_BACKEND for the run; MESH_HAPP_PATH installs the deployed bundle)
just mesh doorway-restart <a|b|c>  # re-exec one doorway on the binary now at its path, with its captured env — how a rebuilt doorway (`just mesh build doorway`) reaches the running household
just mesh build [storage|doorway|beacon]  # build the mesh's iroh-featured storage and its doorway with the gate's pinned toolchain and caps, installed into `<pool slot>/mesh-bin/` (the path the harness prefers and `just gate` never writes), and the membership beacon in its own slot; MESH_BUILD_DRY_RUN=1 prints the commands without running them
just mesh join-peer <fresh-name>  # stage an organic late joiner on the RUNNING mesh (no incumbent restart; receipt: genesis/a2o/scripts/late-joiner-receipt.ts)
MESH_TRANSPORT_BACKEND=dual just mesh start  # storage Track-2 mode: libp2p | dual | iroh
MESH_CONDUCTOR_LAUNCH=ark just mesh start  # conductors run as children of an `ark` (tevah envelope; ARK_BIN = the elohim pool debug slot) — readiness checks the running executable; failed readiness and SIGKILL leave death witnesses in <peer>/ark/ (ark witness ls --berth <peer>/ark/berth.json)
MESH_CONDUCTOR_LAUNCH=direct just mesh start  # one conductor PROCESS per peer; only this and `ark` export HOLOCHAIN_PROMETHEUS_LISTEN=127.0.0.1:946<4+i> (matthew 9464, jessica 9465, james 9466) so `curl :9464/metrics` reads the conductor's own hc_* series (fork pin >= 61565f320 lineage) — the default `hc sandbox run` supervisor runs every conductor from one environment and exposes none
MESH_PORTAL=0 just mesh start  # skip the doorway sign-in portal (default: served on THRESHOLD_PORT 8081)
MESH_DOORWAY_GAMMA=0 just mesh start  # skip the third doorway, gamma (:8890, health :8099) — a second holder of a public name, so a non-holder doorway has two holders to choose between; it adds no conductor or storage peer (primary storage james, extras matthew+jessica) and is restartable with `doorway-restart c`
just mesh portal-restart       # reap + relaunch the sign-in portal (a supervised `ng serve` on THRESHOLD_PORT — no supervisor of its own, so the workspace RAM guard can shed it silently) and block for first paint (MESH_PORTAL_WAIT, default 180s); `just test mesh-browser` REFUSES before launch when `<doorway>/threshold/login` isn't 200, naming this arm instead of a scenario timing out mid-lane
BERTH_CLASS=verify BERTH_TTL=1800 just test mesh   # every mesh lane claims the household lease with a class and a TTL (defaults shown; verify max 3600); a live holder REFUSES exit 3 within ~0.04s; `--class measure` is REFUSED on the dev berth and names `just measure <scope>` — MEASURE_ON_DEV_BERTH=1 is the declared override: it claims, writes a `kind: override` ledger row and emits `dev-berth-held-by-measure@1` immediately (the cost renders on push-delivers-within-budget's headline line); `berth status` shows an EXPIRED lease past its TTL (taken over on the next claim); moorings carry the Claude pid + start time so a claim from any Bash of a moored session resolves by process ancestry (exit 4 = no session resolvable, the only non-blocking exit); a verify lane is FENCED by `timeout` at its TTL and ends `BUDGET-EXCEEDED` exit 3 past it — scope it down or send it through `just measure`; `just mesh start` holds the daemon `mesh` class (no TTL, taken over only when its holder's mooring is dead); `just mesh stop` refuses under another live holder (MESH_STOP_FORCE=1 = a recorded override); `matrix`/`recovery`/`recovery-matrix` are measure-class and refused on the dev berth
just measure <feature-path> [--on jessica|adam] [--gap <id>]   # send ONE feature as a peer-executed stage to the provider that holds the stores and return immediately (genesis/agentic/compute/measure.sh: grant | worker | status | poll | fixture); refuses before launch naming the fix; MEASURE_DRY_RUN=1 prints without running; the listener writes the brit ref, sprint report, gap fulfil and habit DELTA when the completion arrives — `--on adam` refuses today naming the operator items
just mesh preflight            # refuse-before-launch: binaries incl. fork-pair pin, per-peer transport capability, every mesh port free-or-ours — one ok/REFUSED line per check
MESH_ALLOW_STALE_BINARY=1 just mesh preflight  # preflight normally REFUSES a pool elohim-storage/doorway binary older than the newest tracked file under its crate's src — `just gate` runs `cargo test --lib --bins`, which never refreshes a `--bin` artifact; rebuild with `just mesh build [storage|doorway|beacon]`, or set this to measure an older binary on purpose
MESH_ALLOW_STALE_HAPP=1 just mesh start  # start/preflight normally REFUSE a DNA's coordinator wasm older than the newest tracked file under its zome crate's src, cascading wasm -> dna -> happ and repacking the packed .dna/.happ (never the wasm) when only the pack is stale; rebuild with `just build` in that DNA's directory (e.g. elohim/holochain/dna/elohim), or set this to measure an older coordinator on purpose — an explicit MESH_HAPP_PATH is never repacked or refused
just mesh start                # conductor = the PINNED FORK, auto-detected at /projects/.claude-config/tools/hc-fork-<submodule-pin12>/bin (MESH_TOOLS_DIR); runs preflight then launches detached, returning immediately (MESH_FOREGROUND=1 blocks inline instead); a conductor whose line differs from the DNA's hdk line (0.7) is REFUSED, not warned (MESH_ALLOW_TOOLCHAIN_SKEW=1 overrides)
MESH_DIR=<path> just mesh start  # explicit root for an independently owned mesh; the canonical Dowell household fixture executes persistently under genesis/local-dev/household-dowell, resumes complete stopped identities in place, and safely migrates a stopped legacy /tmp/elohim-local-mesh root once
MESH_RESET=1 just mesh start    # explicitly recast a fully stopped household; removes conductor, storage (libp2p + iroh), and doorway-account identity state together; ordinary stop/start preserves it
MESH_KEEP_DOORWAY_DB=1 just mesh start  # on a genuinely fresh household, preserve a leftover doorway Mongo archive for deliberately studying account/cell skew; an explicit MESH_RESET still removes it with the identities it names
MESH_COMPUTE_LOCAL_TOKEN=<token> just mesh start  # node-local compute capability a storage peer and the doorway backed by its pool share (default fixed dev value) — ELOHIM_COMPUTE_LOCAL_API=1 on storage plus HAPP_BUNDLE_PATH/POOL_COMPUTE_URL/POOL_COMPUTE_TOKEN/POOL_COMPUTE_PERFORMER on the doorway let hosted registration install a real cell and notarize it with a grant (POST /api/v1/compute/grants), replacing the retired dev-mode singleton path
just mesh wait [--timeout N]   # block until every conductor/storage/doorway/relay is ready, or exit on the first REFUSED line in the start log (default timeout 900s)
MESH_RELAY_BIN=<dir>/bin/iroh-relay just mesh start  # holochain 0.7: conductors home to a REAL iroh-relay (1.0.3, `--features server`); hc-mesh.sh launches one on :3340 (MESH_RELAY_PORT; MESH_RELAY=0 opts out) — a wrong/missing relay is 3 "ready" conductors at 0 connections, silently; every storage peer also gets ELOHIM_RUNTIME_CONFIG_PATH=<mesh>/<peer>/runtime-config.toml so the rung-4 watcher is armed from boot
MESH_HOSTED_CAST=lane|all just mesh prologue  # household hosted-cast scope for seed-humans.ts's HOUSEHOLD_HOSTED_CAST allow-list (14 names): lane forces it even against a remote doorway, all restores the full standing cast (~23GB conductor heap for 29); unset, the allow-list applies only when DOORWAY_URL is loopback
MESH_DOORWAY_MAX_AGENTS=<n> just mesh start  # per-conductor hosted-agent ceiling (DOORWAY_MAX_AGENTS_PER_CONDUCTOR; the fleet default 50 stalls kitsune2 arc convergence past ~30 agents); default 25 = the 14-name lane cast + 3 `prologue-hosted-*` registrants + 8 headroom for story-created humans that register and close within one scenario
just mesh conductors-restart && just mesh storage-restart <peers>  # recycle conductor heap between hosted lanes — each hosted human costs ~786MB of conductor heap, and that memory is never freed by closing a session
just mesh prologue            # Act I Prologue: cast + seed + stage + fixture manifest (run after `just mesh start`); stamps version.json on any locally-built dist before staging — package-angular-check.py refuses an archive with no build stamp, and only CI/`package-angular.mjs build` normally write one
just mesh recovery <warm|cold> <peer> [--label k=v]  # single warm/cold recovery run (hc-mesh-recovery.sh)
just mesh recovery-matrix     # recovery scenario library × warm/cold shapes × runs (MESH_PEER_TRANSPORTS in hc-mesh.sh + hc-mesh-recovery.sh)
just seed validate            # non-writing schema validation
just seed apply mesh content  # seed the household mesh THIS host owns (env from hc-mesh.sh mesh_seed_env)
just look page <url>          # eyes-first render
just status habits
just status device            # has this workspace ARRIVED as its human's device: six ok/REFUSED lines (epr binary · device key · roster bound · embed model · fold attested · berth moored), each REFUSED naming its cure; exit 1 on any
just codegen all verify

The eight stable verbs are gate, test, dev, mesh, seed, look, status, and codegen. Their parameters replace package-specific name matrices; package scripts remain implementation details for CI and focused specialist work.

Recovery / quiesce evidence is durable: just mesh recovery … and just mesh quiesce write under genesis/a2o/reports/recovery/ (gitignored, persistent). The canonical Dowell household fixture’s runtime state persists under genesis/local-dev/household-dowell; a one-time stopped-state migration leaves /tmp/elohim-local-mesh as a compatibility symlink. Pre-push carries a warn-only T2 receipt leg: a dataplane change (elohim-storage/src/{p2p,sync,reconcile,p2p_iroh}, doorway-service/src) with no household sprint-report newer than it prints NO-T2-RECEIPT naming just test mesh (T2_RECEIPT=strict refuses) — the evidence ladder's ascend-only rule at push time.

build-manifest.json gate.projects owns local gate detection and typed execution. Both just gate and pre-push use genesis/orchestrator/gate-runner.mjs; do not add a grep detector or a second project-name command switch. Native gates resolve explicit cargo-pool slots and crate-specific RUSTFLAGS. A per-project cargo resource cap (CARGO_BUILD_JOBS, RUST_TEST_THREADS) declares on the gate project's own run.cargo.env in its build-manifest.json (the rakia-validated schema accepts it since rakia 2b2cedb, 2026-09-23); genesis/agentic/pool-policy.json's cargo_env_overrides only fills a cap a manifest has not declared, manifest winning on conflict; its "*" entry pins the local gate's RUSTUP_TOOLCHAIN (1.96.1) for every cargo project, and the gate refuses if it is not installed. elohim/epr/rust-toolchain.toml declares the same version for the flake the epr and eprfs pipelines take their Rust from, and genesis/orchestrator/rust-toolchain-pin.test.mjs refuses a mismatch. elohim-storage declares CARGO_BUILD_JOBS: "1", measured: the gate's build phase peaks at 15.4 GB at cargo's default parallelism and is shed by the workspace RAM guard; at one job it peaks at 5.4 GB for ~12% wall-clock. DNA/WASM workspaces remain plain Cargo because Holochain packing requires their in-tree ./target.

Submodule pins are attested, not re-tested. A gate project whose run.kind is attested (brit, rakia, sophia — each declares itself in its own repo's manifest with the gitlink path as the step input) runs no local recipe: gate-runner.mjs dispatches it to gate-attest.mjs, which reads the pinned commit's named CI check on the upstream repo. A green check passes; red, cancelled, still-running, absent, or a commit the forge has never seen refuses; an unreachable read passes and prints attested: claimed. Selection asks rakia affected (GATE_ORACLE=rakia default; shadow prints a diff line, path ignores it) and keeps one hop, so a pin move re-selects the pin's direct consumers. Diagnostics go to stderr; the hook parses stdout as project names. Spec: genesis/docs/superpowers/specs/2026-09-23-submodule-pin-attestation-gate-design.md; habit pin-attestation in genesis/orchestrator/.epr-meta/.

Focused escape hatches:

cd app/elohim-app && pnpm exec vitest run --config vite.config.ts [pattern]
cd genesis/a2o && pnpm test              # deployed E2E
cd genesis/a2o && pnpm run test:browser  # browser scenarios
cd sophia && pnpm build && pnpm test -- --filter sophia-core

There is no content-seed dry-run: the retired flags were ignored and could write. Use just seed validate for a non-writing check. Direct native Cargo commands still require the correct CARGO_TARGET_DIR; prefer just gate so the pool and RUSTFLAGS contract cannot drift.

Durable traps in this layer — just gate buries the rest, but these still bite when you step outside it:

  • cargo nextest is NOT installed in this container (verified 2026-07-30) — use plain cargo test.
  • A dependency bump is not verified by cargo check. --locked --all-targets compiles tests without running them — that is how a diesel 2.3.5→2.3.11 strict-UTF-8 decode change reached dev green and broke a committed resilience test. Dependency work needs cargo test. And never judge a cargo run from piped/tailed output (| tee and the background harness have each reported exit 0 while cargo returned 101) — echo EXIT=$? on its own line.
  • Bypass the pre-push gate with git push --no-verify or HUSKY=0 git push — the hook honors HUSKY=0 itself (.husky/pre-push.bash checks it at the top, healed 2026-09-01 in doc; the earlier era where core.hooksPath=.husky made HUSKY=0 a silent no-op cost shift time 4×).
  • Gates are tiered and PVC-pressure-aware. sweettest-check runs by default only when the push targets dev/main (RUN_SWEETTEST=1 forces it elsewhere, SKIP_SWEETTEST=1 skips it; CI's DNA pipeline is the backstop). Under disk pressure the hook reads genesis/agentic/pool-policy.json: at the soft watermark it reclaims via cargo-pool enforce --yes; at the hard ceiling it defers heavy Rust gates with a DEFERRED-BY-PVC banner (FORCE_HEAVY_GATES=1 overrides). The same policy backs a PreToolUse hook that DENIES heavy cargo at the hard ceiling and denies native-workspace cargo lacking CARGO_TARGET_DIR (DNA/WASM workspaces exempt).

Architecture

Seam Map — concern routing (read FIRST when "where does this live?")

Before reasoning about where a concern belongs — across hardware · OS/packaging · runtime/footprint · mods/plugins · SDK · bridges · clients · app-manifests · the role seams (doorway / peer-hoster / aggregation / hub-cluster) — consult the concern-routing atlas: genesis/docs/content/elohim-protocol/architecture/2026-06-21-elohim-seam-map-concern-routing.md. It maps the full device spectrum (smartwatch → home storage rack) × the composition stack, with a concern-routing table that makes a problem self-locate. The dominant failure mode is misrouting (a substrate-identity bug wearing an aggregation costume; a packaging fact read as a dataplane fact; a UI-render gradient confused with hardware tiering).

The disambiguator you'll reach for most — what do you ADD? a manifest → SDK seam (compose inward, integrity by construction); a crate → bridge seam (translate an external protocol outward); native code → mod/plugin seam (extend the runtime downward). Orthogonal to those seams are the four participation tracks — how a running node participates: T1 DHT-notary floor · T2 substrate (libp2p/iroh) · T3 spoke (HTTP/WS) · T4 doorway projection. Seams are where you add capability; tracks are how a thing participates — they meet but never collapse. The atlas also carries a hyperscaler-parity crosswalk and names the inversion (the social/governance/trust/recovery plane has no hyperscaler equivalent — human-scale's ceiling, not its catch-up).

Operating the live substrate — read the trust contract FIRST when a dataplane probe reds: genesis/docs/content/elohim-protocol/architecture/2026-07-12-substrate-trust-contract-runbook.md — the invariants the dataplane holds (verify-locally-then-serve; canonical channels alone move declared heads; heal fills-never-moves; restart churn ≈20min; fresh actions need publish time), the probe watching each one (per-seam smoke in Dataplane Validation, GET /db/p2p/conductor-diagnostics, POST /admin/steward-peers/refresh, the per-deploy ✓ canonical head propagated line), and the per-red decision tree. It exists because scenario-2 divergence was five stacked invisible defects; the probes now name them. When the doc and live behavior disagree, the probes are the authority.

Domain Pillars

The Angular client is organized into Hebrew-named domain pillars, each with its own services, models, and components. Pillars do not all live in one workspace — lamad was extracted into its own EPR-app bundle, and the alias resolves across that workspace boundary:

Pillar Path Alias Lives in Domain
elohim @app/elohim app/elohim-app/src/app/elohim/ Protocol core - infrastructure, data loading, agents, trust
imagodei @app/imagodei app/elohim-app/src/app/imagodei/ Identity - auth, sessions, profiles, presence, relationships
qahal @app/qahal app/elohim-app/src/app/qahal/ Community - governance, affinity, consent
shefa @app/shefa app/elohim-app/src/app/shefa/ Economy - stewardship, banking, resource flows
avodah @app/avodah app/elohim-app/src/app/avodah/ Work - projects, stories, contribution
doorway @app/doorway app/elohim-app/src/app/doorway/ Gateway integration
lamad @app/lamad/* app/lamad/src/app/ (separate workspace) Learning - content, paths, assessments, mastery, practice

The in-workspace pillars expose barrel exports (import { IdentityService } from '@app/imagodei'). lamad has no barrel — only the @app/lamad/* wildcard is mapped, so imports name a deep path (@app/lamad/models/content-node.model). That asymmetry is a symptom, not a convention: app/lamad/ is a bundle seam (an independently served EPR app), and it is being used as a domain seam without a public API on either side. See app/CLAUDE.md §Bundle seams are not domain seams before adding any import that crosses it.

The elohim pillar owns cross-pillar services — and today it does not hold the cross-pillar content substrate. ContentNode/ContentType/ContentReach live in app/lamad/, so the dependency arrow currently runs core → pillar. The placement decision is open: genesis/data/timeline/backlog/arch-frontend-bundle-seams-backlog.md row 1. Don't deepen it; the lint-workspace-imports rail will refuse.

Data Flow: Rust-to-TypeScript Boundary

Types flow from Rust through auto-generation to TypeScript:

  1. elohim-storage (elohim/elohim-storage/src/views.rs) defines View types with #[serde(rename_all = "camelCase")] and #[derive(TS)]
  2. cargo test export_bindings generates TypeScript types to elohim/sdk/storage-client-ts/src/generated/
  3. storage-client-ts (@elohim/storage-client) exports ready-to-use camelCase types
  4. Adapters (app/elohim-app/src/app/elohim/adapters/) add computed/derived fields only - never transform wire format

Key rule: snake_case never leaves the Rust boundary. TypeScript receives camelCase with parsed JSON and proper booleans. No JSON.parse(), no case conversion, no toWire / fromWire functions in TypeScript.

ts-rs cross-crate trap: ts-rs computes import paths in generated TS from the Rust source crate's location, not the export_to directory. Moving a ts-rs-anchored type into a different crate while consumers stay put emits broken ../../../../ import paths in every referencing .ts. Move ALL #[derive(TS)] types together to one crate in a single atomic migration — never partial/incremental cross-crate moves — and verify byte-identical generated TS via sha256 diff.

Sophia Integration

Sophia (forked from Khan Academy Perseus) renders assessments in three modes: mastery (graded), discovery (psychometric), reflection (open-ended). It distributes as a web component <sophia-question> via sophia-element UMD bundle, wrapped for Angular by sophia-plugin in elohim-library.

Sophia is the rendering layer only - it produces Recognition callbacks. Session management, aggregation, and interpretation belong in the consuming app's services (lamad pillar).

Doorway Gateway

Rust service consolidating three functions: bootstrap (agent discovery), signal (WebRTC), and gateway (conductor proxy + caching). Serves both hosted users (browser via doorway.elohim.host) and local dev (proxied via Angular dev server at localhost:8888).

Bridges (bridges/)

Pluggable interop crates that translate external protocols to and from elohim's canonical EPR-REA substrate. Runtimes consume the bridges relevant to their job: doorway-service consumes web2 bridges (atproto, activitypub, planned); elohim-storage consumes protocol-shaped bridges (valueflows for hREA / VF-GraphQL). See bridges/CLAUDE.md for the pattern and genesis/docs/content/elohim-protocol/architecture/2026-05-20-wave3-valueflows-hrea-interop-design.md for Wave 3 substrate work.

Content Pipeline

genesis/ contains source content (markdown, Gherkin) and seeder tools. Content flows: genesis docs -> elohim-import CLI -> seed data JSON -> seeder -> elohim-storage -> doorway -> elohim-app.

Deployment Contexts

The app runs in four modes with different content loading paths:

  • Eclipse Che: Dev server proxy to doorway (avoids CORS)
  • Local dev: Same proxy pattern
  • Production: Browser direct to doorway.elohim.host
  • Tauri desktop: Direct HTTP to local elohim-storage sidecar at :8090

DNA changes don't redeploy by default: a DNA-content change (new hash, same role structure) does NOT reach running conductors on a normal edge redeploy — the conductor data dir is a persistent PVC and the install stale-check is role-structure-only, so a new hash reads as "not stale." Forcing a reinstall is gated behind ALLOW_DNA_REINSTALL (default false; reinstall mints a new agent key, which on prod needs migration/lineage, not a blind wipe). Wired per-env in elohim/holochain/Jenkinsfile (non-prod=true). If you force-reinstall on some peers but not all in a namespace, they land on different DNA hashes → different DHTs → P2P partition; the alpha genesis pair must both get the flag. Coordinator-zome-only changes never move the DNA hash at all (the hash covers integrity zomes + modifiers only — uhCok… in errors is a wasm hash, uhC0k… a DNA hash): that class is healed by happ_manager::sync_coordinators via the conductor's update_coordinators hot-swap (no re-key, no DHT churn), gated by ALLOW_COORDINATOR_UPDATE (defaults to ALLOW_DNA_REINSTALL's value). When a shipped DNA fix doesn't land, first check which zome class the diff touched.

Cluster ops are operator-owned: never run kubectl (read or write) from the dev environment. "Clean up X resource" / "fix the live ingress" means make the repo manifests in genesis/manifests/ (and orchestrator manifests) coherent so the next pipeline reconciles — the repo is the cleanup surface, the live cluster is the operator's. Read cluster state via Jenkins MCP or in-repo manifests, not kubectl get.

Development Workflow

Persistent workspace work

Che workspace restarts erase /tmp. Keep work in progress, helper scripts, checkpoints, and resumable campaign state under genesis/local-dev/<campaign>/, never /tmp. Keep Git worktrees under /projects/elohim/.worktrees/. Store required durable proof receipts under genesis/a2o/reports/recovery/ and operator runbooks under genesis/docs/superpowers/sprints/. /tmp is only for disposable scratch that can be regenerated; never make it the only copy of uncommitted work or evidence. After a restart, resume existing conductor databases and keystores in place; do not reset or recast identities.

A workspace is one of its human's devices, and arriving as one is six conditions just status device reads in one place (epr binary · device key · roster bound · embed model · fold attested · berth moored), each REFUSED line naming its cure. A fresh workspace binds to its human through epr actor device enroll | authorize | bind (no key ever crosses; the roster row travels by git) and puts the pinned embedding model on the PVC with genesis/agentic/bin/embed-model-provision (the devfile's setup-embed-model runs it on start); $HOME is wiped, /nix/xdg/{config,cache} persist. What the devfile should declare and what lvi's DevspaceSeed will: elohim/lvi/docs/specs/2026-10-10-workspace-arrival-as-declared-device.md.

Story-First Default

Before implementing a feature, find or write the a2o scenario that describes the learner's experience. The scenario is your specification. Implementation is done when the scenario passes.

  1. Feel the vision — read the epic/manifesto context in genesis/docs/content/elohim-protocol/
  2. Find or write the scenario — check genesis/a2o/features/ for existing coverage, or write a new .feature file
  3. Implement to make the scenario pass
  4. Commit scenario + implementation together
Pillar A2O Features Directory
lamad genesis/a2o/features/lms/
shefa genesis/a2o/features/rms/
avodah genesis/a2o/features/wms/ (TBD stub)
imagodei genesis/a2o/features/auth/
qahal genesis/a2o/features/ (governance scenarios)
elohim genesis/a2o/features/content/, federation/

Exploration Fallback

When story-first isn't practical (prototyping, spikes), capture implementation intent before committing by appending to .claude/data/dev-intent.jsonl — a 3-4 sentence summary of what was built, the learner impact, and which a2o feature file needs updating. Then run /close-loop to generate scenario updates from your intent.

Frontend Eyes (available rails)

Frontend review/refinement is eyes-first: render before reading source. Rails available to any agent (run from genesis/a2o):

  • pnpm look <url> [--as <FixtureHuman>] (genesis/a2o/scripts/look.ts) — render any URL headless; writes reports/look/<slug>/{shot.png,capture.json} (console/pageerror/failed-request/httpError capture). Deployed app: https://doorway-alpha.elohim.host.
  • pnpm graphos {list|story|sheet} (genesis/a2o/scripts/graphos.ts) — enumerate/render the graphos component library + design guide from the deployed Storybook (https://storybook.elohim.host, latest dev) or a local pnpm storybook (--base http://localhost:6006). sheet <component> = the full cell/theme matrix (Library A default vs Library B designed sections) in ONE composite image. Design: genesis/docs/superpowers/specs/2026-06-11-graphos-look-design.md.
  • pnpm reports:serve (port 4201) — the operator sees the same artifacts the agent reads (symmetric vision).
  • Can't-find ≠ never-implemented: if a described view doesn't render, suspect present reachability (capture.json httpErrors, routes, env) before concluding absence.

Story Harvest (on branch finish and after debugging)

When using finishing-a-development-branch, invoke story-harvest between Step 1 (tests pass) and Step 3 (present options). When using systematic-debugging and a root cause is identified and fixed, invoke story-harvest before closing the debugging session. The skill identifies engineering constraints discovered during development — especially parameter-bearing discoveries (memory limits, concurrency thresholds, cache sizes) that inform operator presets and peer diversity configuration — and scaffolds a2o regression scenarios to preserve them.

Story-Graph Maintainer (every agent, mid-flight)

Every agent working inside a value chain (a2o chapters, valueflow commitments, spec gaps) also maintains the chain itself. A seam discovered mid-flight — one assertion that proves to be a pipeline of truths, or an unnamed precondition between two nodes — is a missing node between named atoms, not prose for a report. Capture it at discovery in mintable shape: chain / between A→C / missing node B: <assertion + probe> / current state. Depth and scaffolding: the story-harvest skill ("The Maintainer Role — Atom Perspective" — station decomposition keeps the finish-line assertion untouched and adds stations before it). Minted nodes become measured nodes automatically (epr flow project re-mints commitments from the recipes).

P2P Design Gate (MANDATORY)

Before proposing design approaches for ANY feature involving data entities (tables, models, routes, sync messages), invoke the p2p-design-gate skill — gates brainstorming step 3. This exists because AI agents default to relational-DB patterns (UUID primary keys, REST-first, CID-as-column); the protocol requires P2P-native thinking (DHT entry types first, content addressing for identity, storage as projection not truth).

The skill forces you to answer: (1) Is the entity Notarized (A), Linked (A2 — an attribute of something already notarized), Private (B), Attested-Private (B2), or Ephemeral (C)? (2) Does a DHT entry type already exist for it? Read the answer from #[hdk_entry_types] in the DNA's integrity zome — entry-type capacity is NOT the deciding question, and any headroom tally quoted from a prompt is stale by construction. (3) What is the head-plane cost — how many items at 1 year, and what does that do to quiesce? (4) Is identity content-derived (CID), agent-composite, or slug (must justify)? (5) What coordinator function creates it, in which zome (integrity changes move the DNA hash; coordinator-only changes hot-swap), and what signal projects it? Answer BEFORE designing the HTTP route. If you're about to write GET /api/v1/thing without having answered these, STOP and invoke the skill — this summary is a pointer, not a substitute.

Memory cleanup trigger (SessionStart)

The SessionStart MEMORY BUDGET headline carries a cleanup: gate line, projected by epr flow report --headline from the declared cleanup-pressure-ceiling@1 bound. The value is DERIVED, never handed over by a script: it is the count of distinct drifted subjects accumulated in the flows sidecar since the last reset, read against a hard watermark of 120. Under it the line reads cleanup: N pressure-points since the beginning within hard 120 ✅ and you skip. At or over it the line reads cleanup: ⚠ failed — N pressure-points since the beginning has reached the hard watermark 120, and that is the trigger: run the memory-stasis-loop workflow before substantive work — it drains every memory discipline (compaction · dumps · decompose · MAP/path · roadmap · MemPalace) back to stasis. Drain the accumulation deliberately when the pass is done: epr flow note --kind observation --measure cleanup-pressure-reset@1 --subject . --value 1. The reset means it fires at most ~once per heavy-dev week. (This is the local, drift-accurate auto-trigger — the folds are repository-local, so the trigger lives here in gospel, not in a remote cron.)

Substrate scope trigger (SessionStart)

The same headline carries a scope: gate line — the planning-layer analog of how CI reconciles to ELOHIM_REMOTE_COMPUTE_STATUS. The substrate signal has two homes that must agree: ELOHIM_REMOTE_COMPUTE_STATUS (the Jenkins Probe Substrate stage sets it from a live pool probe — the CI/runtime layer self-reconciles each run) and genesis/manifests/cluster-state.yaml (the durable declaration the planning layer reads). When a capability goes down or comes back, flip cluster-state (evidence-backed — mirror the probe, never aspirational). A third home is now derived, never hand-written: genesis/orchestrator/data/deployments.json suspended flags (deploy-render + seed + a2o test gates) reconcile from each human's nodeTypes × cluster-state provides_node_types with suspendedBy: scope-reconcile:<cap> provenance — hand-flipping them is how the homes drifted on 2026-06-03 (11 shem-only humans stayed declared-deployed; 503 storm). epr flow hold --scope --apply cascades all of it; operator-manual suspensions (no marker) are never touched. epr flow report scope is the read (add --docs for the per-document reading), and the headline slot is its line verbatim. Then:

  • scope: aligned ✅ (plate matches substrate) — the held/ tree matches the substrate; nothing to do.
  • scope: ⚠ N to hold (cap) → epr flow hold --scope --apply — a capability was lost; live docs that need it move to held/ (out of the planner/runner scan path) so they don't false-fail. Run the command the line names: it git mvs them live↔held; inbound cites: flip HELD-CITE ↔ healthy automatically (content-addressed, so a move never breaks a link). Default is a DRY RUN — --apply is what moves.
  • scope: ⚠ N to return to plate → epr flow hold --scope --apply — a held doc now has satisfiable work (a capability returned, OR it's a mixed plan with household-testable gaps). It belongs back on the plate. Same command. This is stories + architecture shifting together with the substrate: narrow the plate when a capability is lost (focused dev loops + deployments on what's actually verifiable), expand it effortlessly when the capability returns — and continue with the enabled context intact.
  • ⚠ unknown-cap: X — a requires_env cap in a .md doc matches no cluster-state.yaml resource name (vocab drift, e.g. harbor vs harbor-registry). Reconcile the name; an unknown cap conservatively blocks held→live escape. (a2o .feature @requires: tags are a mixed hardware+fixture namespace — unknown ones there are fixture preconditions, not drift, and are ignored.)

Flipping state (developer control). shem is a runtime capability, but you flip it deliberately — you don't wait for a probe. epr flow hold --scope --set shem=off|on [--apply] edits the durable home (cluster-state.yaml) and prints the coherent runtime export; eval "$(epr flow hold --scope --env)" sets ELOHIM_REMOTE_COMPUTE_STATUS derived from cluster-state so the two homes cannot disagree. That's the full-control flip: narrow the plate (--set shem=off), run the verifiable slice as a focused dev loop, then expand it back (--set shem=on) and resume with the enabled context intact. Scope is gap-granular (iroh ≠ shem). A plan can be mixed: most gaps testable on household-nodes now, only a few needing an unavailable cap. Don't hold it whole — that benches the testable work. Each gap resolves a requires_env, defaulting to the doc-level frontmatter value and overridable per gap with an inline @requires:<cap> tag (isomorphic with a2o's per-scenario @requires:). A gap is BLOCKED-BY-ENV iff resolved_requires_env ⊄ available — regardless of which directory its doc sits in. Convention: a uniformly-blocked plan sets a doc-level requires_env (every gap inherits → held whole); a mixed plan declares no doc-level requires_env and tags only its divergent gaps. The budget (epr flow report placement --ledger) counts a held gap as BLOCKED-BY-ENV, not active OPEN; the scope mover holds a doc whole only if every gap is blocked. Resolver: elohim/eprfs/epr-cli/src/flow/scope.rs (the native port of _lib/env_scope.py's semantics).

The move stays one command (a scope decision, operator-in-loop) — the gate just makes the readiness impossible to miss. Spec: genesis/docs/superpowers/specs/2026-06-02-scope-tree-reconciler-design.md.

Schema & Manifest Sources of Truth

Two authoritative schemas govern content types and formats; all generated artifacts derive from these (never hand-edit generated files). Protocol Schema (elohim/sdk/schemas/v1/) governs DNA-notarized enums — generates <consumer>/src/generated/schema-enums.ts via pnpm run schema:codegen:ts and Rust constants via pnpm run schema:codegen:rs. Lamad Manifest (elohim/sdk/domains/lamad/manifest.json) governs app vocabulary (content types/formats, renderer mappings, relationships, signals, coupling rules) — generates <consumer>/src/generated/manifest-types.ts via pnpm run lamad:codegen.

Key distinction: core vs extensible formats

The protocol schema defines broad core formats (markdown, html, interactive, video, audio, external, epr-composite) that are DNA-notarized. The lamad manifest defines specific extensible formats (sophia-quiz-json, html5-app, gherkin, etc.) that map to Angular renderers. Seed data must use lamad manifest formats, not core protocol formats — the renderer map only knows about formats declared in the lamad manifest. Using a core format like interactive (which has no renderer) causes content to fall through to the raw JSON fallback.

Content Correct contentFormat Wrong
Sophia quiz/assessment sophia-quiz-json interactive
HTML5 simulation html5-app interactive
Discovery assessment sophia-quiz-json interactive

View Schema Contract (HTTP wire shapes)

View schemas in elohim/sdk/schemas/v1/views/ define the JSON wire format for HTTP API responses (source of truth for the Rust-to-TypeScript boundary). Pattern: Write the schema -> Rust structs match (#[serde(rename_all = "camelCase")]) -> validation harness (elohim/elohim-storage/tests/schema_contract.rs) catches drift -> TS codegen generates interfaces. Conventions: see elohim/sdk/schemas/v1/views/CONVENTIONS.md for the 10 rules.

Adding a new view: (1) write {name}.schema.json in elohim/sdk/schemas/v1/views/; (2) write matching Rust struct in elohim-storage; (3) add schema contract test; (4) add to INTERFACE_FILES in elohim/sdk/schemas/scripts/codegen-ts.mjs; (5) run pnpm run schema:codegen:ts; (6) pre-push hook validates codegen freshness automatically.

Critical Gotchas

RUSTFLAGS Override Required

The system sets RUSTFLAGS=--cfg getrandom_backend="custom" for Holochain WASM builds, which breaks native Rust builds. Use RUSTFLAGS="" for doorway/doorway-service and steward/node; keep the custom backend flag for elohim/elohim-storage.

Jenkinsfile Size Limit

Root Jenkinsfile (elohim-app pipeline) sits near the 64KB JVM CPS method size limit — breached 2026-06-10 at 1596 lines (MethodTooLargeException at Jenkinsfile compile; builds #1519/#1520 died before ANY stage ran, so the red looks total and stageless). Line count is only a proxy: large inline sh """…""" heredocs in helpers are what inflate the single CPS dispatch method (top-level helpers inline into it — // STAGE HELPER METHODS placement alone does NOT save bytecode). Rule: helpers stay heredoc-free — bash bodies live in scripts/ci/*.sh, called as sh "bash '${env.WORKSPACE}/scripts/ci/<name>.sh' args…" (secrets via withEnv, never argv).

Jenkins params.MODE Null on First Build

MultiBranch pipeline params are null until the Jenkinsfile runs once. Always use (params.MODE ?: 'auto').

sophia-element UMD Must Be Pre-built

Build before elohim-app builds; the prebuild script checks. Run: cd sophia && pnpm install && pnpm build && pnpm build:umd.

pnpm Workspace

All TypeScript/Node.js projects use pnpm workspaces (sophia excluded — submodule). Target packages with pnpm --filter <name> <cmd>. The libsodium-wrappers package is overridden to ^0.8.2 in root package.json to fix a broken ESM relative import in 0.7.x that fails with pnpm's strict module resolution.

libp2p API (steward/node + elohim-storage)

Both crates declare version = "0.54" and resolve 0.54.1 (Cargo.lock verified 2026-06-11 — the earlier "node 0.53 vs storage 0.54" split is STALE). Requires macros + ed25519 features. Both request_response constructors are live on 0.54: Behaviour::new([(Proto, ProtocolSupport::Full)], cfg) for default-codec protocols (steward/node's p2p/transport.rs), and RequestResponse::with_codec(codec, …) where a custom codec is needed (elohim-storage's BlobCodec / ViewFederationCodec at elohim/elohim-storage/src/p2p/behaviour.rs) — reach for with_codec() only for a non-default codec. Swarm event loop uses StreamExt::next() (not the deprecated select_next_event()).

CI/CD

Central orchestrator pattern: only genesis/orchestrator/Jenkinsfile receives GitHub webhooks, analyzes changesets, and triggers downstream pipelines. Downstream jobs use overrideIndexTriggers(false) and validate UpstreamCause or UserIdCause. Recurring CI/orchestrator traps (NOT_BUILT/superseded ≠ regression, host-green ≠ CI-green, baseline-rollback over-build, sccache poisoning, #[ignore] is a CI no-op): see the frequency-ranked museum record genesis/docs/content/elohim-protocol/history/2026-06-02-ci-orchestrator-recurring-anti-patterns-museum.md.

Recurring CI/orchestrator watch-outs (read before debugging a "regression"): NOT_BUILT/ABORTED/superseded builds read as 0-failures (lossy measure); #[ignore] is a CI no-op (DNA sweettests run --run-ignored all); webhook double-fire; baseline-rollback over-build. The frequency-ranked museum record is the canonical home: genesis/docs/content/elohim-protocol/history/2026-06-02-ci-orchestrator-recurring-anti-patterns-museum.md (orchestrator-specific watch-outs detailed in genesis/orchestrator/README.md).

Pipeline metadata is declared in per-project build-manifest.json files. The genesis/orchestrator/graph-walker.mjs + build-graph.groovy walk these manifests to build a dependency graph and determine which pipelines to trigger. pipeline-registry.mjs exposes the metadata to JavaScript consumers.

Force dispatch: Use commit tag syntax [build:edge|dna|app|genesis|sophia|steward|conductor|all] to force-dispatch specific pipelines on any trigger type (webhook, timer, manual, replay). Example: git commit --allow-empty -m "test E2E [build:edge]". Measuring without deploying: to record a Dataplane Validation measure (saga/notary banking) WITHOUT rolling the alpha fleet, pair the dispatch tag with the edge mode tag — [build:edge] [edge:validate-only] — which skips build/deploy stages and runs only the validation suite against the live fleet. A bare [build:edge] fired "just to measure" restarts the 7 pods it is measuring (~20min churn + hours of catch-up) and is the documented measurement-by-deploy anti-pattern. Preflight the quiesce gate's four legs first (matthew caughtUp via doorway /p2p/status, divergent_actionable<=2 + unmeasured=0 via per-pod Prometheus, both doorways 200 on /db/content/elohim-host-landing) — the gate's predicate reads matthew (storage-A) only.

Jenkins MCP is anonymous (OIDC-protected): all mcp__jenkins__* READ tools work; triggerBuild/updateBuild are denied — don't call them. Trigger builds only via a fresh git push with a [build:*] tag. Never add an Authorization header to the MCP registration (it triggers a 50-redirect OIDC login loop).

New Jenkinsfiles: any in-container git op (other than checkout scm) needs sh 'git config --global --add safe.directory "*"' first, or git fails with dubious ownership (checkout runs as a different UID than the build container). The multibranch job elohim-holochain loads the DNA Jenkinsfile (elohim/holochain/dna/Jenkinsfile), NOT the edge one — verify via the console's Obtained …/Jenkinsfile from <sha> line before editing "the holochain Jenkinsfile."

Pipeline Jenkinsfile Manifest
App Jenkinsfile (root) app/elohim-app/build-manifest.json
Edge elohim/holochain/Jenkinsfile elohim/holochain/build-manifest.json
DNA (all DNAs) elohim/holochain/dna/Jenkinsfile elohim/holochain/dna/build-manifest.json
Genesis genesis/Jenkinsfile genesis/build-manifest.json
Sophia sophia/Jenkinsfile sophia/build-manifest.json
Steward steward/device/Jenkinsfile steward/device/build-manifest.json
Conductor image che-devworkspaces/jenkins/Jenkinsfile-elohim-edgenode (job elohim-edgenode, SCM: che-devworkspaces) elohim/conductor-image/build-manifest.json

The custom conductor reaches the fleet via the submodule pointer. The conductor manifest watches elohim/holochain-conductor; its exact committed gitlink supplies the elohim-edgenode:conductor-<hc12> tag on the Holochain 0.7 line. The source-derived tag no longer includes tx5. elohim-edge consumes the pin through --build-arg CONDUCTOR_SOURCE_IMAGE; scripts/ci/build-storage-image.sh and elohim/conductor-image/build-manifest.json are the current derivation and dispatch evidence. Nothing is hand-edited to move a conductor — the committed SHA is the pin, and if its image is missing the edge build fails with a remediation line rather than deploying a mismatched conductor. Variants (iroh, jemalloc-prof, canary storage, fork-branch overrides) are selected from the commit with [conductor:…] — see elohim/conductor-image/README.md and genesis/orchestrator/README.md. Changes to the Dockerfile or job itself live in the che-devworkspaces submodule and are NOT auto-watched: force them with [build:conductor].

One DNA pipeline builds every DNA. There is no per-DNA Jenkinsfile: elohim/holochain/dna/Jenkinsfile packs each DNA in turn — lamad (packed from the dna/elohim/ directory, not dna/lamad-v1/), imagodei, infrastructure, node-registry, mishpat — and the DNA manifest's elohim/holochain/dna/** watch globs cover all of them. A DNA subdirectory holding only dna.yaml + zomes/ + a justfile is normal and fully covered: do not read the absence of a per-DNA Jenkinsfile as missing CI, and do not add one. (dna/hrea/ is a reserved placeholder — workdir/ only, no zomes, not packed.)

Code Style

  • TypeScript/Angular: ESLint 9 flat config with SonarQube parity rules; Prettier (100 char width, single quotes, trailing commas); import order builtin → external → @app/* → @elohim/*; strict TypeScript + Angular strict templates; path aliases in app/elohim-app/tsconfig.json.
  • Rust: cargo fmt + clippy with -D warnings; configs at doorway/doorway-service/clippy.toml and rustfmt.toml.
  • Sophia (React/TypeScript): pnpm workspace, Jest + @testing-library/react; packages prefixed @ethosengine/* (sophia) or @khanacademy/* (math utilities); psyche-core must NEVER depend on perseus packages.