EKOS · RFC 0111 / 0113 Partitioned & distributed storage Test campaign · 2026-08-31 → 09-01

Distributed Storage
Under Fire

Two autonomous end-to-end runs of EKOS's object-storage-backed, horizontally-distributed knowledge ledger — fault injection at the coordinator, the compile worker, and the query gateway. Run 1 found four defects. Run 2 — harder environment — found four more. All eight are fixed, with regression tests and zero workspace regressions.

2
runs · 7 acts each
95
live partitions on S3
8
defects found & fixed
6.56s
fencing recovery
0
test regressions
01 — The system under test

Four services, one KnowledgeStore trait

EKOS is a compiler for enterprise knowledge: it observes source systems, compiles the observations through deterministic passes into a Canonical Knowledge Model, and stores the result in an append-only, evidence-carrying ledger. Distributed mode splits that ledger across many independent fact-segment partitions in object storage and puts a metadata coordinator, a compile worker, query workers, and a read-only gateway around it — every existing reader keeps working because the gateway implements the same trait the local ledger does.

Component map

ComponentRFCRoleCrate
PartitionedLedger0111 ADrop-in KnowledgeStore routing across many FactLedger partitions by entity kind + time bucket; persisted catalog, run-file index, cold tiering.ekos-ledger
SegmentBackend seam0113 B1Interface behind which sealed-segment reads/publishes go. LocalFsBackend (default) or…ekos-segment-backend
ObjectStoreBackend0113 B2…an object_store 0.14 backend — S3, Azure ADLS, S3-compatible (MinIO), in-memory, file://. Feature-gated.ekos-segment-backend
Coordinator0113 B3Metadata service over newline-delimited JSON-RPC/TCP: fencing-tokened write leases, per-shard commit watermarks, partition catalog, entity→partitions pruning index, atomic JSON persistence.ekos-cluster
CompileWorker (Service A)0113 B3Runs the real build → recover → resolve → compile → commit pipeline under a heartbeated lease, then registers its partitions and commits the new generation — fenced.ekos-cluster
QueryWorker (Service B)0113 B4Stateless read compute: materialises a partition into a local cache (from object storage or a local root), opens it read-only, serves the EAV fold + tantivy search over TCP.ekos-distributed
DistributedLedger gateway (Service C)0113 B4Read-only KnowledgeStore: pooled connections, concurrent multi-partition fan-out, entity→partitions id-pruning, per-shard BM25 top-k search merge, worker failover.ekos-distributed
02 — Method & environment

Seven acts, executed sequentially, everything logged

Each run works through an executable test plan act by act. Every command's stdout, stderr, and exit code is captured to disk; metrics land in metrics/*.json; a REPORT.md is generated from the logs, not from memory. A step failure is recorded as-is — no softening, no "fixing" a result before recording it.

The actstest plan
  • Act 0 — Ingest. Fresh partitioned workspace; full pipeline; count partitions.
  • Act 1 — Local baseline. query object, query find, diff against the local partitioned ledger — the control numbers.
  • Act 2 — Backend seam. LocalFs test suite vs. an object-store backend on the same data.
  • Act 3 — Coordinator + Service A. Lease acquisition, a competing worker (fencing), kill -9 the holder, takeover.
  • Act 4 — Read path. Query workers + gateway; latency vs. Act 1; kill a worker mid-query.
  • Act 5 — Distributed search. A term present in several partitions; local vs. gateway result ordering.
  • Act 6 — Benchmarks. Criterion micro-benchmarks + an explicit coverage check.
Environmentboth runs
Toolchainrustc 1.93.0 · cargo 1.93.0
Host12 CPU · 15 GiB RAM · Linux 6.8
Base commite8e1ca3
object_store0.14.1 (stock build: absent)
ContainerDocker 29.1.3
Object storeminio/minio:latest @ :9100
LLMOpenAI gpt-4o-mini

The two runs

 Run 1 — baselineRun 2 — hardened
run idrun-20260831T212115Zrun-20260831T222159Z
binarystock e8e1ca3e8e1ca3 + 18-file fix diff
workspacePentaho ETL — 20 files (.ktr/.kjb/SQL/PDF)Plausible (Elixir) — 895 .ex/.exs in scope, 2 006 files copied
object storefile:// fixture dirs3://ekos-partitions/run2c on MinIO — real S3 API over HTTP
partitions1295
objects2436 999
LLMskipped — structural only[document-semantics] via OpenAI — 250 concepts
lease TTL30 s (default)8 s — recovery measured by polling, not slept through
query workers2 · caches stayed empty (shared FS)2 · caches pulled 12–13 MB each from MinIO
Run 2 deliberately raised every axis the user flagged as a Run 1 limitation.
03 — Run 1 → Run 2

What got harder, and what broke because of it

Run 1's fixes made the object-store path reachable through the CLI at all. Running that path against a real S3 endpoint, a 95-partition workspace, an 8-second lease, and concurrent RPC then exposed a second layer of defects that unit tests had never touched.

12 → 95
partitions
243 → 6 999
objects
file:// → S3
object storage
30 s → 8 s
lease TTL
4 + 4
defects, run 1 + run 2
shared FS → S3 pull
worker read source
structural → OpenAI
recover enrichment
8 / 8
fixed + tested
The chain that only a real run reveals

Fixing the object-store CLI panic (defect 1) let ekos compile-worker reach a real S3 endpoint — which then failed to authenticate (1b), and once authenticated published partitions that a remote query worker read as empty (6), under a lease its own heartbeat couldn't sustain (7), over a connection two concurrent calls corrupted (7b). None of these are visible until the whole path runs end to end against real infrastructure.

00
Ingest & partitioning · RFC 0111 Phase A

The pipeline builds a partitioned ledger and publishes it

A fresh workspace with [storage.partition] set compiles into many independent fact-segment partitions keyed by entity kind and week, with a persisted catalog and an entity→partitions index. Run 2 also publishes every partition to object storage.

PASS · both runs
Pipeline stage wall time
seconds · log scale
Run 1 · 12 partitions · localRun 2 · 95 partitions · MinIO
Compiled modelrun 2 · run2c
Objects written6 999
Relationships8 533
Evidence records1 874
Subsystem rollups51
Elixir modules / symbols908 / 4 250
Doc concepts / rels (OpenAI)250 / 114
Partitions95
entity→partitions entries18 608
Why 95 partitions

Entity-kind partitioning makes one partition per ObjectKind and per relationship kind. The [document-semantics] analyzer's LLM relationship extraction emits free-form kinds — bare prepositions (rel:from, rel:with) and phrases with spaces (rel:can be downloaded via) — so the count ballooned. The object-store key path handled the space-containing keys without complaint; the analyzer's vocabulary is the thing to tighten (tracked, not a storage defect).

StageRun 1Run 2Notes (Run 2)
build10.59 s38.38 s1 135 files observed
recover0.62 s5.29 sElixir AST + document-semantics via OpenAI (cached)
resolve --force0.09 s0.70 s19 identity conflicts (e.g. error as module & symbol) — continue anyway
compile0.37 s2.80 s13.8k candidate pairs, 171 auto-merged
commit19.43 s235.35 s95 partitions × manifest/segment PUT over HTTP — the S3 round-trips dominate
Commit is the cost centre in Distributed mode: every partition's metadata is written to object storage.
01
Local baseline queries

Control numbers, straight against the partitioned ledger

Three reads through the local PartitionedLedger: fetch an object by id, full-text find, and a full-range diff. These are the baseline Act 4 is measured against.

PASS
OperationRun 1Run 2Result
query object <id>0.109 s0.45 sRun 2: Plausible.Billing (ElixirModule), properties + evidence
query find0.109 s1.55 sRun 1: 16 hits for "customer" · Run 2: 113 hits for "Subscription"
diff (full range)0.676 s27.55 sRun 1: 1 292 versions · Run 2: 18 608 entities touched, resolved to name (kind)
Run 2's diff fans to all 95 partitions with no time-bucket pruning for a wide range — the slow number here, and in Act 4.
Defect 4, fixed here

ekos diff used to print opaque entry #0 … entry #N (per-backend row ids, restarting per partition). It now resolves each touched logical id to its name and kind, capped at 50 with an "… and N more".

02
Backend seam · RFC 0113 B1 / B2

LocalFs works. Object storage now works too.

The SegmentBackend trait is the seam. Run 1 proved the LocalFs default is untouched (146 ledger tests) but the object-store path panicked the moment the CLI opened it. Run 2 runs the entire pipeline against a real S3 endpoint.

LocalFs · PASS both runs ObjectStore · Run 1 FAIL (panic) ObjectStore · Run 2 PASS (real S3)
Run 1 — segment-backend-url = "file://…"
$ ekos build
thread 'main' panicked at object_store_backend.rs
Cannot drop a runtime in a context where
blocking is not allowed.
 7: drop_in_place<ObjectStoreBackend>
 8: store::with_segment_backend_url  store.rs:132
11: build::run
12: ekos::main  <#[tokio::main]>

The backend owned a tokio Runtime, dropped inside the async main. Every write-path command panicked; object storage was unreachable via the CLI.

Run 2 — segment-backend-url = "s3://ekos-partitions/run2c"
$ AWS_ENDPOINT=http://127.0.0.1:9100 \
  AWS_ALLOW_HTTP=true ekos build && … && ekos commit
Commit complete.
  Objects written:       6999
  Relationships written: 8533
  Ledger: .ekos/ledger/partitioned
$ mc ls --recursive ekos-partitions/run2c | wc -l
631  # 95 × manifest.json + dict.bin, 8.4 MiB

Backend now runs its object_store calls on a dedicated OS-thread runtime — safe to build, call, and drop from any context.

Crate test suites — post-fixgreen
ekos-ledger147
ekos-segment-backend --features object-store9
ekos-cluster (unit + harness)12
ekos-distributed14
cargo test --workspacegreen · 0 failed
clippy -D warnings · fmt --checkclean
What lands in the bucketafter compile-worker
Per partitionmanifest.json · dict.bin
Per partitionsegments/seg-000000.facts
Per partitionHEAD watermark
Per partitionfull search/ (tantivy)
ElixirModule/2026-W36292 KiB segment + ~130 KiB index
Total after Act 3~8.4 MiB / 630 objects

The plan's file:// fallback ("flag that it's not a full check") is no longer needed. Not covered: a real hyperscaler — MinIO is S3-API-compatible but self-hosted; IAM, regions, and TLS against AWS/Azure/GCS are untested.

03
Coordinator · leases · fencing · RFC 0113 B3

A competing writer is rejected; a dead writer is taken over

Worker A holds a fencing-tokened write lease. Worker B, on the same shard, must be rejected. kill -9 A — a new worker must acquire with a higher token, finish the pipeline, and commit the generation. Run 2 measures the recovery time by polling, at an 8-second TTL.

PASS · both runs
Fencing — verbatim from metrics/fencing.jsonRun 2
Lease TTL8 s (default 30 s)
Worker B rejectedtrue
Rejection error"partition main is already leased"
Token: A → C1 → 2
C acquire attempts7
C committed generation25 499
C partitions registered95
C lost leasefalse
Recovery (kill → "lease held")6.563 s
Coordinator state after the runcoordinator status
$ ekos coordinator status
partition                       cold  location
ElixirModule/2026-W36           false s3://…/run2c/ElixirModule/2026-W36
… 94 more, all s3:// …

shard                           generation
main                                  25499
state.json partitions95 · all s3://
watermarks{"main": 25499}
entity_partitions18 608 entries
Defect 7, found by the short-TTL methodology

The same scenario at 8 s TTL before the fix: worker C took over in 0.5 s (A's lease was already perpetually near-expired, because the heartbeat was a fixed 10 s > the 8 s TTL) and then lost its own lease before committing — watermarks {}. The fix derives the heartbeat from lease.expires_at; an 8 s lease now survives a multi-minute pipeline. Run 1's 30 s TTL had masked this entirely.

Run 1 result, for the record: worker B rejected (exit 1); token 1 → 2; generation 1720; 12 partitions; recovery bounded by the 30 s TTL (the 41 s wall figure included a 40 s scripted sleep — Run 2's polling removes that artefact).

04
Distributed read path · RFC 0113 B4

Query workers pull partitions from object storage; the gateway fails over

Two query workers start with empty caches. Because the partitions are registered as ObjectStore locations, each worker materialises what it needs from MinIO — not a shared filesystem. Then one worker is killed mid-query.

Reads · PASS Failover · Run 1 FAIL Failover · Run 2 PASS
Query latency — local vs. gateway
Run 2 · seconds
Local PartitionedLedgerDistributedLedger gateway
Worker caches after Act 4pulled from MinIO
Registered asPartitionLocation::ObjectStore
Worker 1 cache839 files · 12 MB
Worker 2 cache849 files · 13 MB
Per partition pulledmanifest · seg-000000 · HEAD · search/
Shared filesystemno

query object0.59 s → Plausible.Billing
query find "Subscription"0.84 s · 50 hits
diff12.10 s · 18 608 entities

Worker failure injection

Run 1 — no failover
# kill -9 query-worker-1 (7811); worker-2 up
$ ekos query object <id>
Error: io error: Connection refused (os error 111)
exit 1
$ ekos query find …
Error: ledger error: Connection refused  exit 1

Every query failed — the retry reconnected to the same dead address.

Run 2 — call_worker_failover
# kill -9 query-worker-1 (7811); worker-2 up
$ ekos query object <id>
WARN gateway: query worker unreachable —
     failing over  worker=127.0.0.1:7811
Object: Plausible.Billing (ElixirModule)   exit 0
$ ekos query find "Subscription"
50 result(s)                              exit 0

Served from worker-2; recovers fully when worker-1 restarts. Clean error only when all workers are down.

OperationLocalGatewayΔRun 1 (12 part)
query object <id>0.45 s0.59 s+0.14 (1.31×)0.069 s vs 0.109 s local
query find1.55 s0.84 s−0.71 (0.54×)0.064 s vs 0.109 s local
diff (full range)27.55 s12.10 s−15.45 (0.44×)0.254 s vs 0.676 s local
The gateway is faster than the local path on the fan-out reads — warm query workers vs. a cold per-invocation PartitionedLedger open with 95 tantivy writers. query object pays one extra RPC hop.
05
Distributed search · RFC 0113 B5

Same result set, different rank — shard-local BM25

The gateway fans each shard's BM25 top-k to a worker and merge-sorts the scored lists. Term statistics are per-partition (IDF is shard-local), so a match in a small partition can outrank a more globally-relevant match in a large one. This is the documented v1 approximation, not a defect — and both runs reproduce it.

PASS · caveat confirmed
RankLocal — PartitionedLedgerDistributed — gateway
1subscription — Concept (LLM)plausible/billing/subscription.ex — File
2subscription flow — ConceptMoney.Subscription — ElixirModule
3subscription business modelmix/tasks/cancel_subscription.ex
4Plausibleplausible/billing/subscription/status.ex
5Money.Subscriptionmix/tasks/pull_sandbox_subscription.ex
Term "Subscription", Run 2. Local ranks free-form LLM Concept objects top; the gateway ranks real Elixir files/modules top — the small Concept shard's local IDF differs from the corpus-wide one.
113 / 16
local hits · run 2 / run 1
50 / 16
gateway hits · run 2 / run 1
k = 50
find_objects cap (open item)

Run 1, term "customer": local and gateway returned the same 16-item de-duplicated set; local put the whole PDF first, the gateway put the small TransformNode shard's 10 hits first. Canonical test crates/distributed/tests/search.rs passes.

06
Benchmarks · Criterion

Ran the micro-benchmarks — and verified they don't cover the distributed path

The benchmark/ workspace has 11 Criterion benches. A grep over benches/ for PartitionedLedger, SegmentBackend, DistributedLedger, ekos_cluster, ObjectStoreBackend returns nothing. That gap is now a verified fact, not an assumption.

benches ran 0 distributed coverage — known gap
Criterion mediansRun 2 · short measurement window
ledger_append_object928 µs [906 / 928 / 954]
segment_append_batch2.17 ms [2.12 / 2.17 / 2.24]
segment_replay_1k_batches4.83 ms [4.78 / 4.83 / 4.89]

Criterion printed change: deltas against a prior local baseline, but those compare an uncontrolled earlier run at a different measurement window — not a before/after of this patch.

Coverage checkmetrics/criterion_distributed_coverage.txt
$ grep -rl 'PartitionedLedger|SegmentBackend|
  DistributedLedger|ekos_cluster|
  ObjectStoreBackend' benchmark/benches/
NO distributed / partitioned / cluster /
object-store code referenced by any
benchmark/benches/*.rs file

All distributed-path timing in this report is CLI wall-clock (Acts 0–5), carrying process-startup and RPC-framing overhead a real Criterion bench would isolate. Authoring partitioned_ledger.rs / cluster_coordinator.rs benches is separate work.

04 — Defects found & fixed

Eight defects, nine fixes, one commit

Every fix ships with a regression test. Branch fix/distributed-storage-issues, commit 2896481 — 23 files, +885 / −92. cargo test --workspace, clippy -D warnings, and fmt --check all pass.

1ObjectStoreBackend runtime-drop panicRun 1 · blocker · crates/cli/src/commands/store.rs:132Run 1
Symptom[storage.partition] segment-backend-url + a --features distributed binary → ekos build/commit panic: Cannot drop a runtime in a context where blocking is not allowed. Object storage was unusable through the CLI at all.
Root causeThe backend owned an inner tokio Runtime; it (or a throwaway one built just to validate the URL) was dropped inside #[tokio::main], where BlockingPool::shutdown panics.
FixDedicatedRt — a current-thread tokio runtime pinned to one private OS thread (ekos-objstore-rt). Every object_store call spawns onto it and blocks on an mpsc reply; the Runtime is only ever dropped on its own thread. Safe to build/call/drop from a plain sync test, a spawn_blocking thread, a current-thread runtime, and #[tokio::main]. store.rs now validates the URL parse-only.
Testobject_store_backend::usable_from_within_an_async_runtime
1bparse_url reads no configurationRun 2 · s3:// never authenticated to MinIORun 2
SymptomPast the panic, an s3:// URL against MinIO (or any non-AWS endpoint) never authenticated — feature for AmazonS3 not enabled, then unconfigured-credential failures.
Root causeobject_store::parse_url returns an unconfigured AmazonS3Builder::new(); it reads no env vars. The object-store cargo feature only pulled object_store/fs.
FixForward every AWS_* / AZURE_* / GOOGLE_* / OBJECT_STORE_* process var (lowercased) to object_store::parse_url_optsbuilder_opts! silently drops keys a scheme doesn't recognise, so it's safe for all backends. The feature now bundles object_store/aws + object_store/azure.
VerifiedFull pipeline + compile-worker against s3://… on MinIO with AWS_ENDPOINT / AWS_ACCESS_KEY_ID / AWS_ALLOW_HTTP=true.
2Gateway has no read-path fault toleranceRun 1 · one dead worker fails every queryRun 1
SymptomOne kill -9'd query worker → every DistributedLedger read fails io: Connection refused, exit 1 — even with a second live worker holding the same partitions.
Root causecall_worker's single retry reconnects to the same address.
FixDistributedLedger::call_worker_failover — on a connection error, rotate to the next worker in the ring (every worker can materialise any partition). Non-connection errors return as-is; clean error only when all workers are down.
Testsgateway_fails_over_when_a_query_worker_is_down · gateway_errors_cleanly_when_all_workers_are_down
3coordinator status always shows watermark 0Run 1 · cosmeticRun 1
Root causestatus queried watermark(partition_id) per catalogued partition; watermarks are keyed by shard (main), so every row read 0.
FixNew Request::Watermarks RPC + CoordinatorClient::watermarks(); status prints a "shard / generation" section. Verified main 25499.
4ekos diff prints opaque entry #NRun 1 · cosmeticRun 1
Root causeLedgerDiff.added holds per-backend entry ids (SQLite rowids / per-partition tx numbers); the counter restarts per partition.
FixIterate LedgerDiff.touched (real logical ids) instead — resolve each to name (kind) / relationship label, capped at 50 with "… and N more".
5[llm-description] ignores the provider configRun 2 · 1 112 × HTTP 401Run 2
Symptom[llm] provider = "openai" — but the OpenAI key was sent to api.anthropic.com: 1 112 × authentication_error.
Root causeselect_llm_provider_for_description handled ollama then fell through to AnthropicProvider; the openai branch that recover.rs has was missing.
FixAdd the openai branch, mirroring recover.rs::build_llm_provider. Descriptions then succeed via OpenAI (0 × 401).
6Object-storage partitions publish only sealed historyRun 2 · remote workers see empty partitionsRun 2
SymptomA partition below the 8 MiB seal threshold — i.e. almost every partition under entity-kind partitioning — published only an empty manifest.json. A remote-only query worker saw it as empty.
Root causeRFC 0113 B4 published sealed segments, manifest.json, dict.bin, and search/. The active (unsealed) segment — where all the data is when nothing seals — stayed writer-local by design.
FixSegmentStore::publish_active (active segment + HEAD) + FactLedger::publish_active_to_backend + PartitionedLedger::publish_active_segments, called by compile-worker's finalize_partitions; open_with_backend pulls the active segment when the local one is absent.
Testactive_segment_travels_through_the_backend — default 8 MiB threshold, nothing seals, a fresh reader still sees every row.
7Compile-worker heartbeat is a fixed 10 sRun 2 · any short TTL loses every leaseRun 2
SymptomAt --ttl-seconds 8: the lease expires between every 10 s beat → guard.commit(watermark) fails LostLease, the generation is never recorded, even with no competing writer.
Root causeCompileWorker::new hard-codes Duration::from_secs(10) regardless of the coordinator's TTL. The 30 s default made it work by luck.
FixDerive the interval from lease.expires_at - now (≈ TTL/3, floored 500 ms, never slower than the 10 s default).
Testheartbeat_adapts_to_a_short_ttl_so_long_work_keeps_its_lease — 1 s TTL, 4 s of work, default heartbeat → commit succeeds.
7bRPC clients cross response frames under concurrencyRun 2 · Coordinator("unexpected Ok")Run 2
Root causeCoordinatorClient::call / QueryWorkerClient::call guard write and read with separate mutexes; caller B can take the read lock between caller A's write and read and consume A's response line. Fires when a worker's heartbeat lease_renew races its guard's manifest_commit, or the gateway fans out concurrently.
FixHold the write lock across the whole write-then-read round-trip.
+ekos compile-worker run --forcenew flag · Service-A equivalent of ekos resolve --forcegap
WhyWithout it, any identity conflict aborted every compile-worker run. The Plausible workspace has 19 (e.g. error as both an ElixirModule and an ElixirSymbol). A co-located ekos resolve already had --force; Service A did not.
05 — Feature coverage matrix

Every feature, how it was exercised, in which run

"PASS (new)" marks behaviour introduced by this campaign's fixes. "PARTIAL" marks a feature exercised but not fully — noted, not hidden.

FeatureRFCExercised byR1R2Status
Partitioned ledger — entity-kind × weekly0111 Afull pipeline; catalog + index inspected1295PASS
SegmentBackend seam / LocalFsBackend0113 B1147 ledger tests + every local opPASS
ObjectStoreBackendobject_store 0.140113 B2file:// (R1) → S3/MinIO (R2)PASS
Coordinator — fencing-tokened write leases0113 B3worker B rejection; token incrementPASS
Coordinator — per-shard commit watermark0113 B3generation 1720 / 25499; state.jsonPASS
Coordinator — entity→partitions prune index0113 v1.11 292 / 18 608 entries; prune testPASS
Coordinator — persisted state / restart-safe leases0113 B3state.json round-trip; harnessPASS
CompileWorker (Service A) — real pipeline under lease0113 B3build→…→commit · --forcePASS
CompileWorker — lease loss & takeover0113 B3kill -9 A; worker C resumesPASS
CompileWorker — adaptive heartbeatfix 7survived --ttl-seconds 8 + full pipelinePASS · new
QueryWorker (Service B) — materialise from S30113 B4w1/w2 caches 12–13 MB from MinIOPASS · new
QueryWorker — read-only FactLedger + EAV fold0113 B4every gateway readPASS
DistributedLedger gateway (Service C) — trait parity0113 B4object/find/diff vs localPASS
Gateway — connection pool + concurrent fan-out0113 v1.1multi-partition reads over 95 shardsPASS
Gateway — id-scoped pruning via entity index0113 v1.1query object routing; prune testPASS
Gateway — worker failoverfix 2kill worker-1; served by worker-2PASS · new
Distributed search — per-shard BM25 top-k merge0113 B5"customer" / "Subscription" orderingPASS · caveat
Self-describing partition — sealed segments in S30113 B4few partitions reached 8 MiB~PARTIAL
Self-describing partition — active segment + HEAD in S3fix 695 × seg-000000.facts in MinIOPASS · new
Self-describing partition — tantivy search/ in S30113 B4ElixirModule 130 KiB index pulled by workersPASS
[storage.distributed] client config0113 B4client-ekos.toml + EKOS_CONFIGPASS
OpenAI provider — recover / document-semantics0026 / 0046250 concepts, 114 rels from README + CHANGELOGPASS
OpenAI provider — [llm-description]0088 / fix 5module descriptions via OpenAI, 0 × 401~PASS
Criterion micro-benchmarks + coverage grep11 benches run; coverage.txtPASS · 0 dist. coverage
06 — Limitations & open follow-ons

What this campaign did not prove, and what's still on the list

Limitations of the rundocumented, not hidden
  • MinIO, not a hyperscaler. S3-API-compatible and self-hosted; real AWS/Azure/GCS credentials, regions, IAM, and TLS are untested. AWS_ALLOW_HTTP=true for the local endpoint.
  • Single host. Coordinator + 2 workers + compile workers on 127.0.0.1. Network partitions and cross-host clock skew not exercised — but the workers genuinely materialise from object storage, so "shared filesystem" is no longer a caveat.
  • [llm-description] not run to completion. 1 112 sequential gpt-4o-mini calls (~30 min). Exercised far enough to prove defect 5. recover's own LLM path ran fully.
  • No new Criterion bench for the distributed path — that is benchmark-authoring work.
  • Recovery is TTL-bound. 6.56 s = 8 s TTL minus elapsed lease time. Lower TTLs need the defect-7 fix to stay safe.
  • 95-partition count is a side effect of the document-semantics analyzer's free-form relationship vocabulary, not a deliberate choice.
Open follow-onstracked
  • Interrupt-of-in-flight-work on lease loss. A fenced worker runs its pipeline to the end, then its guard.commit is rejected.
  • ekos compile-worker has no acquire-retry. It exits on already leased; fault-tolerant takeover needs an external supervisor (the test driver's loop stood in).
  • [document-semantics] relationship vocabulary. One partition per bare preposition (rel:from, rel:with) — tighten the analyzer.
  • find_objects is hard-capped at search(query, 50) — 113 vs 50 local for "Subscription".
  • ekos diff over many partitions fans to every one (12–27 s); no time-bucket pruning for a wide range.
07 — Appendix

Commands, config, artifacts

The distributed CLI surface exercised

# write side — Service A, under a coordinator
ekos coordinator serve --listen 127.0.0.1:7801 --state coord.json --ttl-seconds 8
ekos compile-worker run --coordinator 127.0.0.1:7801 --shard main --workspace WS --force
ekos coordinator status --coordinator 127.0.0.1:7801

# read side — Services B & C
ekos query-worker serve --coordinator 127.0.0.1:7801 --listen 127.0.0.1:7811 --cache CACHE
EKOS_CONFIG=client-ekos.toml ekos query object <id>
EKOS_CONFIG=client-ekos.toml ekos query find "Subscription"
EKOS_CONFIG=client-ekos.toml ekos diff --from 2020-01-01T00:00:00Z --to <now>
Workspace configekos.toml · Run 2
[storage.partition]
dimension = "entity-kind"
time-bucket = "weekly"
segment-backend-url = "s3://ekos-partitions/run2c"

[llm]
provider = "openai"
model = "gpt-4o-mini"

[document-semantics]
enabled = true
Client configclient-ekos.toml
[storage.distributed]
coordinator = "127.0.0.1:7801"
query-workers = [
  "127.0.0.1:7811",
  "127.0.0.1:7812",
]

# env for the object store (MinIO)
AWS_ENDPOINT=http://127.0.0.1:9100
AWS_ACCESS_KEY_ID=…  AWS_SECRET_ACCESS_KEY=…
AWS_ALLOW_HTTP=true

Test totals — post-fix

147
ekos-ledger
12
ekos-cluster + harness
14
ekos-distributed
9
ekos-segment-backend
0
workspace test failures
23
files in the fix commit
+885 / −92
lines
2896481
commit · fix/distributed-storage-issues

Artifact tree

test-runs/
├── run-20260831T212115Z/        # Run 1 — stock e8e1ca3, Pentaho, file://
│   ├── REPORT.md
│   ├── logs/{00-environment … 61-search-rs-test}.log
│   └── metrics/{baseline,fencing,latency_comparison,search_ordering,…}.json
└── run-20260831T222159Z/        # Run 2 — + fix diff, Plausible/Elixir, MinIO
    ├── REPORT.md
    ├── logs/00b-bugfix-diff.patch    # the 18-file fix
    ├── logs/{10-baseline … 70-criterion-bench}.log
    └── metrics/{baseline,fencing,latency_comparison,search_ordering,
                 benchmark_summary,criterion_distributed_coverage}.{json,txt}

Both runs carry a generated REPORT.md built from the logs. Narrative and rationale: devlogs/devlog_144.md.