Home - noetl/ai-meta GitHub Wiki
Last refreshed: 2026-10-10 (Claude session β ββ B3 + D2 are LIVE ON PROD and proven with real data. Server v3.143.0 / ehdb v0.9.0, tick 60 s (not the shipped 15 s β chosen from prod's measured 314 appends/hour, where 15 s would carry ~1.3 records per object and write 5,760/day), cleanup default ON. A 32-minute sampler caught the entire chain: 188 unsealed records left the node in 3 batches, objects appeared under tail/ counted externally with gcloud, the age seal fired at ~900 s, and D2 then reclaimed all 6 objects (3 per replica Γ 2) as a contiguous run of durable parts covered them β retained 6β0, bucket 3β0, tail_failed=0 throughout. β The first ~400 s read appends=0; that was the window, not the code β measured independently, prod had no execution traffic at all. β β The D2 safety property is the design: the watermark stops at the first non-durable part, because max-over-durable skips a local-only part in the middle and would authorise deleting the only off-box copy of those events; both mutants are caught by that one test. β β And a defect I introduced in B3 and caught before enabling: the driver held the engine lock across the remote put, and that lock is taken by shadow_append on the live write path β p50 77 ms / p99 234 ms per colliding append, forever; ehdb v0.8.1 splits prepare / upload / commit. β
#457 CLOSED on measurement β /data 31% β 9%, local 1.2 G β 11.9 M β with the one goal item that was NOT done (β 0 of 19 alert policies watch any volume metric) carved out as #466 rather than buried. ββ The prod worker roll is BLOCKED by a broken release, not by its accepted risk, and my first fix was the wrong diagnosis. release-worker failed twice (v6.2.2, v6.2.3), each ~45 min in, on commits whose cargo test --locked was green: noetl-tools = "~4.0" floated to 4.0.6, which needs async-nats 0.47 against our 0.38 β two incompatible Context types. β Adding --locked did not fix it β cargo chef cook works from recipe.json and writes its own lock over the one copied in, so the cook stage resolved 4.0.6 anyway. The fix is an exact pin (=4.0.1), verified by building the real image (1,008 crates, 0 errors, one async-nats). β Also corrected: the pools already run v6.2.1, not v6.0.0 β I had read build_info off the writer, the only reachable :9090, and generalised it. C2+C3 HELD; election-before-enforce absolute. Β· Earlier today: B3 closes the last in-flight durability gap: the UNSEALED tail now leaves the node. A2/A3 made sealed history survive node loss; a part is local-only until it seals, so the window was the seal interval β B2 bounds it and cannot close it. Measured on prod while building: SEAL_MAX_AGE=900 with oldest_unsealed_age=719 and unreplicated_oldest_age=0, i.e. nothing waiting to upload and ~12 minutes of records living on one disk. ehdb v0.8.0 writes the pending records as write-once per-batch objects under tail/ β never parts/, or plan_retention could drop them β and cold_load_replicated replays them, deduping against the manifest's max_sequence because a part that seals after its tail objects makes them redundant by design. β Window: 900 s β the tick. Bounded, NOT zero β no remote ack gates the local append, so it is neither consensus nor a quorum, and the tick is the number to quote. β The RED control is the load-bearing test: with the flag off, 16 of 21 records come back, so the loss is real and every green is falsifiable; with it on, destroying the local root and cold-loading from the replica alone returns 21 of 21, and 13 of 13 through a real GCS emulator. β β A defect I introduced in v0.8.0 and caught before enabling anything: replicate_tail performed the remote put while holding the engine lock β and that lock is taken by shadow_append inside emit_events on the live write path, so every append colliding with a tick would have waited out the put (p50 77 ms, p99 234 ms, once per tick, forever). A durability feature becoming a write-path latency regression. ehdb's uploader thread had documented the rule the whole time ("the substrate writes happen OUTSIDE the lock so a slow store never blocks appends/reads"); v0.8.1 splits prepare (no I/O) β upload (free fn) β commit (no I/O), pinned by a test whose RED control restores the defect. β β β And a correction to my own shipped default: prod's measured rate is 314 appends/hour, so a 15 s tick carries ~1.3 records per object and writes 5,760 objects/day β the count is driven by the tick, not the data. 60 s is the better choice (still 15Γ better than 900 s, a quarter of the objects) and the 15 s default I shipped is likely mis-set. B3 is emulator-proven and READY but NOT enabled on prod: enable needs D1 (tick) and D2 (tail-object cleanup β a data-deletion policy on the event log), both owner calls, now with measured numbers on #460. D3 resolved as recommended (a failing tail put fails nothing). C2+C3 HELD; election-before-enforce absolute. Β· Earlier today: the off-box replica was armed on prod and the acceptance proof found the defect instead of rubber-stamping it. A2 reached a genuine second failure domain (domains=[local-device:66320, remote-gcs]; survives_node_loss 0β1, single_point_of_failure 1β0) and parts sealed from then on replicate β the age seal fired at t=934s of a 1853s window and the part plus manifest landed in the bucket, counted externally with gcloud. β β Then the proof refused: read back from the remote's own manifest, 40 parts listed and 39 at replica_count 1. Uploads are enqueued on seal and nowhere else, so arming a replica replicates nothing that already exists β and every durability gauge read healthy the whole time because they describe the declared replica set, not the parts. β β β Worse than an empty remote: the bucket holds a manifest naming all 40 parts and the bytes of one, so it reads as a complete store and is not a recoverable one β which makes the backfill a correctness prerequisite, not an optimization. Fixed rather than redefined: ehdb#400 β v0.6.0 backfill_under_replicated, reading bytes back out of a replica that holds them because durable_view nulls local_path, so a repair path that only knew fs::read would have compiled, run, reported success and copied nothing β the same defect one level down. Backfills also carry their own counters: a part sealed in an earlier process has no sealβdurable interval, and folding it into upload_lag_micros_total would divide the real total by a larger denominator and deflate a mean a capacity decision reads. β The worker's v0.3.2βv0.5.2 pin bump had exactly one compile break across two minors β the new ShardLag.inflight β and refusing to default it to 0 was the whole point: render_snapshot publishes that field and high-lag-with-zero-inflight is specifically the stalled consumer reading, so a hardcoded zero makes a healthy bus publish an outage signal on every scrape (ehdb#402 β v0.7.0). β
#463 CLOSED β vendor/ removed (19,670 files, 572 MB) via server#528. β Three of my own mutants did not compile before one did, and a mutant that does not compile is not a caught mutant. β β Rule 0a drift found and fixed on this very page: the Active-umbrellas section was missing #457, #459, #460, #462 and #463 β every umbrella of the last two sessions, including the one the session was about. C2+C3 remain HELD; election-before-enforce absolute. Β· Earlier (2026-10-09): a retention + object-store archival tier for the event log, built end to end and proven against a real GCS emulator; the hot volume becomes a WINDOW rather than an accumulator (#459, closing #457). Audit first: four things already existed and were not rebuilt β the retention planner (plan_retention, drops whole parts below a sort-key floor, never splits a part), its executor (apply_retention), a GCS client live in prod (ObjectBackend/GcsBackend, ADC via Workload Identity, 21,569 objects written) and the archive bucket itself. β β And four that did not: no cloud DurableSubstrate (only LocalFs + InMemory, so nothing goes off-box and prod's "second replica" is the same PVC); apply_retention had ZERO callers in the server β built-and-dormant, the #332 pattern; no time- or completion-based floor; no per-execution layout, date index or config. ββ The constraint that shaped everything: partition = shard_for_execution with shard_count=1, so a part holds many executions interleaved β an execution owns no part, execution_bloom cannot enumerate, and dropping an old part would destroy the early events of a still-running execution. Resolution: archive per-execution (exact), prune per-part (coarse), with a floor as the bridge. P1βP6 merged: config, archive to execution_id=<id>/, a by-date index that is one object per execution (GCS has no append, so a per-day manifest would be read-modify-write and lose entries under concurrency β 60 concurrent archives on one date all survive), prune-after-durable, retrieval by id and by date, and the metrics. β Proven on a real L0Engine: with a live execution unarchived the floor pins at sequence 2, dropped=0, 20 of 20 of its events survive; archive it and the floor rises, dropped=9, parts 9β0, 10,736 bytes reclaimed. β A real bug in my own floor was caught only because the test asserts on reclaimed bytes rather than on the call returning Ok β the all-archived branch used the highest FIRST sequence, so a fully-archived log reclaimed nothing while every call succeeded. β β Scope correction: the store this prunes β /data/ehdb-embedded, 2.4 GiB β is a SHADOW; verified with a control grep that its only readers are the shadow writer and the parity verifier, and GET /api/executions/{id} reads Postgres noetl.event, which is append-only and never purged. So pruning it cannot make API history unreadable, and wiring a fall-through there would have been an instrument that cannot see its own target. β Blocked on one owner action: the server SA has no IAM binding on the archive bucket, so archiving fails closed; the exact gcloud command is on #459. Flags default-off β nothing in prod deletes anything. Β· Later: P7/P8 audited and specced (#460, design only, nothing built). β β β PROD RUNS RF=1 AND EVERY DURABILITY SIGNAL READS HEALTHY β both "replicas" are on device 66320, yet replica_domain_violations=0: L0Engine::open wraps the substrate in a replica set of exactly one and the check is gated on len() >= 2, so the 0 was never evaluated. FailureDomain::Remote exists and is constructed in tests only, so survives_node_loss is false by construction everywhere. β β The exposure is the unsealed tail, and on the tier it is unbounded in time β verified in code: with_seal_max_age is called at exactly three sites and tier_store.rs calls it zero times, so the two buses get the 5 s bound and the store at risk gets 256 MiB and no clock. β Bounds stated rather than softened: EHDB is not etcd-with-sharding, it is a WAL with a lease (etcd has consensus and no shards; EHDB has shards and no consensus); P8 gives mutual exclusion, not consensus, and reduces write availability under partition; and by CALM there is no coordination-free linearizable cross-shard read, ever β a theorem, not a gap. β The #459 archive is not P7 (terminal+expired only, lagging, downstream of sealing) but it retires the dependency blocker, because DurableSubstrate is a sync trait written on the uploader's dedicated thread, so GcsSubstrate can live in the server and be injected. Measurement phases unblocked; the three semantics changes are owner-gated. Β· A1/B1/C1 now MEASURED (server#511): four always-computed replica gauges seeded pessimistically (an unevaluated set must not read as safe) with the alert keyed on survives_node_loss rather than a count; the tier's active catalog.jsonl sits 7.1 MiB unsealed for ~7 hours, so a byte-only trigger bounds the busy tiers and leaves the quiet ones exposed; and five durable-state mutation paths are enumerated, none wrapped. β β Two of my own framings corrected: the tier is not an L0 engine so with_seal_max_age does not apply (age-based segment sealing would be a new feature β stays specced), and I had checked the wrong binary for the fence β the writer links ehdb-reference and the fence is already a selectable decorator, so what is missing is the election, not the fence. β β β Which sharpens C3: with no election live, enforcing is an immediate outage, so C2 strictly precedes C3. Β· C1 guard landed (worker#348, zero src changes): a second store-construction site or a new unregistered append* now fails a test. β β Three further guards were written and deliberately not shipped β the plain bypass and the read-after-wrap are compiler-guaranteed (move semantics; a match data dependency), my mutants would not compile, and a mutant that does not compile is not a caught mutant. β The ehdb skew is drift, not a correctness risk (FORMAT_VERSION 1 at both pins, ehdb-service/src unchanged) β the cost is capability, which is why C2 needs a pin bump first. β P7/P8 is now blocked on user decisions (#460) + the IAM grant β no further safe autonomous work; prod is 4 releases behind (v3.131.0 live, v3.135.0 tagged) and one deploy delivers the archival tier, P5 retrieval and the A1/B1 instruments. Β· Later (same session) β THE ARCHIVAL TIER IS LIVE AND DRAINING ON PROD, and three of the claims earlier in this very line are now STALE. β Corrections first, because the dashboard contradicting reality is the thing Rule 0a exists for: the IAM grant is DONE, prod is not 4 releases behind (it runs v3.137.2, rolled by digest a3bb8087, rollback c46a032a, 1/1, 0 restarts, env 71 = baseline), and P7/P8 is no longer blocked on no-further-safe-work β A2 and B2 both shipped. ββ Archival verified with real data: ARCHIVE_ENABLED=true, 1,472 of 6,251 archivable executions archived, every one also verified_durable and indexed, 0 failures, 4,431 real GCS puts. β β But the reclaim number is NOT measured and is not claimed β the floor correctly refuses while any archivable execution is unarchived, so enabling prune today would reclaim nothing; reclaim is gated on the drain (~4 h), not on code. β The GCS-put latency that #460 decision (i) was taken WITHOUT: p50 77 ms, p90 98 ms, p95 139 ms, p99 234 ms, n=300 in-region β ~19x a ~4 ms local fsync, which closes synchronous per-record remote append harder than the "tens of ms" estimate did. β β Four defects found by controls rather than reasoning: (1) compute_prune_floor returned NoFootprint when everything is archived β the exact steady state the drain produces β so the whole prune path would have reclaimed zero while every call returned Ok; caught only by a positive control asserting a fully-archived log DOES reclaim. (2) collect_candidates capped at 200x100 and returned Ok on a silently truncated list, and a floor from a partial population can sit above an execution never classified. (3) The put metric's "pin" was observe(0.0) on a histogram β a FABRICATED sample; in prod the only reading below 50 ms was that invented zero, on the very number the decision turns on. (4) My own B2 gauge understated its exposure: it stops at sealing, but replication is async so a sealed part still has a local-only window β and unreplicated_snapshot() has computed the whole durability window all along with zero server callers. β A2 is proved by moving bytes, not metadata: the log cold-loads from GCS alone (6 sealed parts, sequences 1-48, confirmed by listing the bucket outside the test; control bucket 0 objects) β a substrate that declared Remote while dropping every upload would pass the unit tests. β Two GCS buckets are ONE domain, and they do survive node loss, so the gauge that would catch a two-bucket plan is domains_distinct, not single_point_of_failure. β β B2 existed in ehdb and was unreachable from the server β no config, no caller outside ehdb's own tests; ehdb's own doc names the trap (a configured seal_max_age with no timer is "inert on exactly the shard it was added for"), so both halves now ship together, guarded including the spawn in main.rs. β Owner-gated: A3 needs a replica bucket + IAM (exact commands on #460); and PR #355 has 38 commits stranded on a design branch, including the pointer bumps for several prod rolls, now CONFLICTING β all four gitlink conflicts measured as "this branch is strictly ahead", STATUS.md needs a human call. β β β C2+C3 remain HELD; election-before-enforce is absolute. Releases v3.138.0-v3.140.1 are cut and not deployed.)
Refresh cadence: every session that lands meaningful cross-repo work (per agents/rules/wiki-maintenance.md Rule 0a)
Standing direction (2026-06-04). Per memory entry, Python tiers are deprioritized. Forward Rust-only e2e work is tracked under [#54][i54] (Phase F R5). Python pieces stay deployable for backwards-compat on GKE but are NOT a target for new feature work.
Single pane of glass for the NoETL platform. Every active umbrella, every submodule, every release lands here so a single page shows what's in flight, what shipped, what's next.
Convention. This wiki is the cross-repo dashboard. Per-repo wikis (e.g. noetl/server wiki, noetl/ops wiki) document that repo's surface; this wiki documents the system of repos. See Wiki convention for the split.
Everything in this block was read from the live system or the repo in one pass. The Sessions log below is an append-only history where each entry was correct on its date; where it disagrees with this block, this block is current.
| verified value | how | |
|---|---|---|
| server | v3.135.0 |
noetl_server_build_info{version="3.135.0"} on the serving pod (verified 2026-10-09 16:27Z) |
| digest | 231e8eeb |
pod imageID (rolled by digest; rollback 94bf4efb=v3.131.0. β β v3.135.0 makes prod report replica_single_point_of_failure 1 / survives_node_loss 0 β redundancy unchanged, visibility new; and the retention/archival tier is deployed with flags OFF) |
| health | 1/1, restarts 0 | pod status; gen=59 observed=59, currentRev == updateRev
|
| env count | 67 β name-set identical to the pre-roll baseline | STS container env |
| EHDB library |
v0.5.0 (ehdb-feed + ehdb-l0, both pins, tag=v0.5.0#d61530b2) |
Cargo.lock on noetl/server@main
|
EHDB FORMAT_VERSION |
1 β unchanged across the upgrade, so the layout gate did not refuse the existing store |
release-ehdb verification build |
| ehdb-l0 metrics | β 34 series scraping, incl. all four C6 state gauges |
/metrics on the serving pod (0 before v3.125.0 β the server never called the renderer) |
ehdb_l0_manifest_parts |
311 β non-zero and read from the live manifest, so the gauge is reading real state rather than pinned-at-zero decoration (~576 KB held, per the ~1.8 KB/part model) | /metrics |
ehdb_l0_parts_under_replicated |
0 β the signal that had no reporter now reports, and reports healthy | /metrics |
| D8 runtime registry | β
REACHED β the server self-registers and renews its lease: noetl_runtime_registry_total{registered}=1, {heartbeat}=4, lease age reset 59s β 6s
|
/metrics + GET /api/runtime/topology
|
| discovery surface | β
GET /api/runtime/topology β 200, live_total=1, Server: noetl-server-rust-embedded-0:2 noetl-server/3.126.0, unknown_timestamp=0
|
live probe |
watch_since |
β
/api/runtime/watch?after=0 β 5 ops {Register:1, Heartbeat:4}; after=5 β count=0, QUIET (a watch, not a scan) |
live probe |
| β remaining P-work | workers / gateways / EHDB do not self-register yet (discover(Worker) empty); executions unregistered; SecretRef unused |
grep + live topology |
| β auth gate mode | the /api/runtime/* layer is attached, but NOETL_INTERNAL_AUTH_MODE is absent so the gate is in shadow and rejects nothing β same posture as every other gated route (#303) |
STS env + an unauthenticated 200 |
| β #391 compaction |
inapplicable by construction β the server uses only D1EventLog (does not opt in) and has 0 VectorStore refs, so records_superseded = 0 is the correct reading |
grep + /metrics
|
| what v3.124.0 actually changed | the ToolSpec.params sequence fix β it rejected all 53 adiona playbooks, which are now runnable in prod |
git log v3.123.2..v3.124.0 = 2 commits |
| chain gates |
ARMED and working β NOETL_CHAIN_{POPULATE,ADVANCE,SOURCE} present in the process; chain_populate{extended}=229
|
kubectl exec β¦ env + /metrics
|
| chain bad states | 0 (diverged / fork / multiple_roots / stale_log) | /metrics |
| eventlog divergence |
7, kind=count only, with mirror_attempt{unavailable} pinned at 2
|
/metrics |
β β CORRECTED 2026-10-07: there is NO mirror loss, and the shutdown drain already exists
and works. I previously wrote that unavailable counted lost batches, "one per roll".
It does not β it is AttemptOutcome::Retryable, a failed attempt that gets retried. The
arithmetic over a 22-hour window closes exactly:
enqueued 11330 = drained 11330, shutdown_abandoned 0, queue loss 0
mirrored 11323 + recovered 7 = 11330 (every event reached the tier)
parity match 1332 / divergent 7 = 99.48% agreement
flush_on_shutdown() (ehdb_eventlog_mirror_queue.rs:449) is called from main.rs:1464
on SIGTERM, and shutdown_abandoned is 0. β I also attributed unavailable=2 to rolls
that happened before this pod started β a counter cannot record those. divergent = 7
equals recovered = 7, consistent with a comparator sampling during a retry window (a
timing artifact, the #325 family), though counters cannot prove the sets identical.
#343 β no code change; the residual is
comparator tuning, which is owner-gated.
β The chain gates were disarmed at 18:36Z on 2026-10-06 and re-armed at 19:23Z when the "is it blocking?" test returned NO. Net state is the original baseline. #410 is applied and CLOSED.
All 8 Rust repos gate clippy with -D warnings. All 8 pin the toolchain at 1.99.0.
Only cli carries named allows (2: large_enum_variant, result_large_err, justified in
#378).
| repo | clippy step | pin |
|---|---|---|
catalog Β· server Β· worker Β· tools Β· gateway Β· ehdb Β· signal-mesh
|
β
-D warnings β gates, 0 allows |
1.99.0 |
cli |
β
-D warnings, 2 named allows
|
1.99.0 |
So #374 (the || true inert gate) and
#378 (the allow-list paydown, 23 β 2) both
closed correctly, and there is no clippy-gate debt outstanding.
β β β CORRECTION β this table said the opposite earlier on 2026-10-07, and I was wrong.
It claimed "3 of 8 gate clippy; 5 cannot fail (
server,worker,cli,tools,gateway)". I measured the local submodule checkouts, which are 7β23 commits behindorigin/main, so I read the pre-#374 state and published it as current.β The irony is the point: this happened inside the sweep whose purpose was killing stale assertions, in the same edit where I told readers to "read the version from the pod digest, never from this table" β while reading a workflow file from a stale tree. The lesson generalises past versions: measure a repo claim against
origin/<default>, never against a local checkout, which is already recorded as a false-zero source.β A second, independent error in the first pass: the probe took
head -1of the clippy lines, which forcatalogis thecargo clippy --versionreporting step and carries no-D warningsβ so even against a fresh tree it would have misreadcatalogas ungated. Two defects stacked in one three-line probe.
22 PRs merged, 0 open, 142 tests, AC3 holds at exactly 4 Dataset impls.
/api/catalog/* is the only interface β no SQL surface of any kind. Its wiki repo
does not exist yet: catalog.wiki.git returns Repository not found because GitHub
creates it only after a first page is saved through the web UI. Content is staged at
docs/wiki/Home.md with publish instructions; nothing claims the wiki is live.
Opened 2026-10-09, advanced 2026-10-10 β the durability frontier: prod ran RF=1 and
every signal read healthy (#460). A2
(off-box replica) is now armed on prod against a genuine second failure domain β
domains=[local-device:66320, remote-gcs], with replica_set_size 1β2,
survives_node_loss 0β1 and single_point_of_failure 1β0 β and parts sealed from then on
do replicate, proven over a 1853s window (stated because a window shorter than the
~900s seal period would have proved nothing) with the objects counted externally by
gcloud. β β The acceptance proof then refused to pass, which is the point of having
one: read back from the remote's own manifest, 40 parts listed and 39 at replica_count
1. Uploads are enqueued on seal and nowhere else, so arming a replica replicates nothing
that already exists, and the healthy gauges describe the declared replica set rather
than the parts β a claim about configuration being read as a claim about data. β β β And the
result is worse than an empty remote: the bucket holds a manifest naming all 40 parts
and the bytes of one, so it reads as a complete store and is not a recoverable one. Fixed
rather than redefined: ehdb#400 β
v0.6.0 backfill_under_replicated, whose own trap was that durable_view nulls
local_path, so a repair path that only knew fs::read would have compiled, run, reported
success and copied nothing β the same defect one level down. β A second defect fell out of
the worker pin bump: ehdb#402 β v0.7.0,
because ClaimCoordinator could not report in-flight depth and a literal 0 there does not
publish "unknown" β it publishes the stalled-consumer reading, every scrape, on a healthy
bus. Honest framing throughout: bounded-loss consistent prefix of sealed history, not "no
data loss." β C2+C3 (election + fencing) remain HELD; election-before-enforce is
absolute.
Opened 2026-10-09 β two durability gauges contradicted each other on prod
(#462, RESOLVED). Neither lied: unrep_age = unsealed_age β Ξ΄ where Ξ΄ is a 1 ms capture skew between writer.append() and
on_append, measured in 4 of 8 runs. β The valuable by-product was catching that the A3
divergence check I had drafted was invalid: unrep_age at a seal boundary is the part's
lifetime, not upload latency, so it diverges identically for a local-only replica and
would have "passed" with no remote at all. The A3 check is now defined on instruments that
were validated (ehdb#399).
Opened 2026-10-09, CLOSED 2026-10-10 β vendor/ was 19,670 tracked files consumed by
nothing and already stale for 57 of 344 crates
(#463, landed via
server#528). Removed: 7,331,526 deletions,
572 MB. Build, 1510 tests and clippy verified clean without it, and the repo's one
--offline call (ci/stamp-version.sh:47) still succeeds. .gitignore carries the
reasoning inline so it cannot silently return.
Opened 2026-10-09 β a retention + object-store archival tier, so the hot EHDB volume
is a window rather than an accumulator (#459,
closing #457). ββ Reclaim is banked and
measured, not estimated: 2.30 GiB growth-corrected β df_used 3,283,072 β 885,496 KiB,
manifest parts 329 β 26, PVC 32.1% β 8.7%, 306 parts pruned. The constraint that shaped the
design: partition = shard_for_execution with shard_count=1, so a part holds many
executions interleaved and dropping one would destroy the early events of a still-running
execution β hence archive per-execution (exact), prune per-part (coarse), with a floor
as the bridge.
Opened 2026-10-07, advanced 2026-10-08 β the EHDB north star: a distributed registry and control plane (#455, see Umbrella: EHDB north-star registry): EHDB as "like etcd but truly distributed" β append log, catalog relations, topology/service registry with discovery including ephemeral runtime units, secret references (references, never material), and vectors. All internal EHDB, cleanly separate from the external business catalog. P1βP5 are now REACHED in prod (Server, Gateway, Worker, ephemeral Execution, and the secret-material guard); Ehdb is shipped and awaiting its first command dispatch. ehdb v0.5.0 is cut and live, both server pins moved, and the server has shipped v3.124.0 β v3.131.0.
ββ ehdb#391 CLOSED β Dataset::supersede_key
lets a merge collapse superseded keys, taking a vector collection from 4,608 stored
records / 65.02 ms to 500 / 7.13 ms, converging to exactly the live set. β β But only
one of the three named consumers may opt in: RuntimeDataset is disqualified by
watch_since β the op-log reader added in P2 β because one history reader disqualifies a
dataset. β
ehdb is now releasable (it had no automation at all; every v0.4.x tag was
hand-pushed), with GH006 and the bot-tag trap avoided by construction β and β β gated OFF
behind EHDB_RELEASE_ENABLED, verified by merging it: the job ran on main and was
skipped, tag still v0.4.5. β
RESOLVED β ehdb v0.5.0 was cut, both server pins moved, and auto-release is now ON. β Auto-release is not auto-deploy: a cut tag does not roll prod; every deploy still takes the full baseline / by-digest / rollback discipline. ββ P6 reframed its own deliverable: top_k is an exact
brute-force scan, so recall@k is 1.0 by construction and becomes falsifiable only once
an ANN index exists β while the number that actually bounds the design is cost, which
tracks op-log depth, not live points (500 points re-embedded 10x costs the same as 5,000
written once, within 2%). β β Compaction cannot fix it β merge combines parts, not keys,
and the Dataset trait has no supersede hook at all, so the prerequisite is a
key-level compaction primitive (ehdb#391),
shared by the D8 registry folds and the catalog folds. β P7 (unsealed-tail replication) and P8 (writer election + fencing) are specced and deliberately not started β both change durability or safety semantics, and a lease without fencing is not safe under partition. β election.rs/fencing.rs live in ehdb-reference, which the server does not link: existence is not reachability. Two findings filed: ehdb#388 (lazy-ack O(nΒ²)) and ehdb#389 (drain attribution + the #298 cap tradeoff). Still open on the acceptance list: A5, fold determinism has no engine-side enforcement point.
Opened 2026-10-06 β the catalog model on EHDB (#427, see Umbrella: Catalog model on EHDB): a generalized, event-sourced catalog of NoETL internal resources, playbooks first, in the new noetl/catalog repo. Storage is EHDB and only EHDB. β It finishes a named predecessor β catalog_log.rs already writes CatalogRecord in shadow and CatalogRelation is a fold nothing calls on a read path. P0 (spec + repo + 4-of-4 RED-proven CI) and P1 (model types, 30 tests) have landed; P2 is the four Dataset impls, where β the attribute and relation folds must not copy ProjectionStore's latest-wins shape. Spin-off: #429, the kind filter returning a partial set.
Umbrellas open in the ai-task queue: the chain-store repoint blocker (#357 β the store cannot tell a genuine chain root from a post-restart one; populator built + kind-proven, repoint ON HOLD), now ARMED ON PROD with the comparator at 100.00% over 772 comparisons after #360 fixed the two defects that failed the first ramp at 6.3% β a snapshot behind the store is staleness, not conflict, and coverage is monotonic; the #358 second-writer alarm moved into chain_for, which re-reads the log to tell the two apart, and two server tests that never ran are tracked as #361), request latency on POST /api/execute (#319 β the seconds decomposed into layers: the ~4 ms per-append fsync (P1, parked behind ehdb#322), four redundant catalog reads (fixed, server#394), and the mirror's inline fallback (fixed, server#395, v3.100.4 live). See Umbrella: Request latency), the system worker pool ceiling (#318 β a fixed 2-shard capacity with no autoscaler; the ClaimCoordinator route is built, the KEDA ScaledObject was deployed, regressed c=6 and was rolled back, and the autoscaler stays held β see Umbrella: System Pool Design), the EHDB projection tier (tier 2) (#265 β mirror the incumbent snapshot store and make it serve-capable; phase A proven in kind 2026-08-14, 9 arms / 0 failures, NOT flipped and NOT deployed. β the charter's noetl.projection is a dead table β 0 rows, no Rust writer β so the real target is noetl.projection_snapshot, which has exactly one INSERT site. β β a 13-event execution completes and gets no snapshot row at all, so the tier can only hold what the incumbent writes and the prod soak must measure coverage, not only divergence), the EHDB primary serve path (#257 β a writer-fronted tier service; wiring + reachability slices shipped and kind-gated, no tier is primary anywhere), EHDB-only IaC β declared, kind-validated, prod converge pending (#222 β automation/ehdb/ via ops#245; the post-NATS topology as noetl playbooks in local mode), the T3 events-bus migration (#212 β noetl.events.> off NATS; shadow LIVE on prod, publish + projection parity PROVEN, consume path merged but unproven), the Domain-Specific
SLM platform RFC (#139 β
build + continuously improve org SLMs via NoETL playbooks; travel#63 is the
reference implementation), the Rust server
parity-port umbrella ([#49][i49]), the distributed-OS program
(#107), the
event-WAL + derivable-storage model
(#104), the decouple-result-data RFC
(#115), the worker-driven orchestrate
E2E coverage (#111), the Rust regression
baseline migration (#98), the
Python-era deploy legacy cleanup (#97), the provider-code duplication (#295 β after #286 the HotelBeds dispatch code exists in the planner AND the MCP playbooks, with repos/ops a stale third copy; which one wins is an owner decision), and the flights roadmap (#291 β multi-city, baggage-aware filtering and fare-rule semantics, none expressible on Duffel offer_requests).
Opened 2026-08-27/28 β from the IaC-completeness and secret-migration work:
#303 the credential API answers 200 with decrypted data and no Authorization header, reachable from the user worker pool (not internet-exposed; a router-level gate now ships in shadow, rejecting nothing); #306 rbac.yaml declares an unapplied ClusterRole binding the worker SA to cluster-wide delete on namespaces and secrets β so the DR procedure itself is the escalation vector (scoped-down proposal in ops#281, left as a draft); #307 projection recovery coverage was ~0 by construction β the comparator runs against completed executions and the spine route only holds in-flight ones β fixed in server v3.92.0, prod live in verify (folds from the durable tier, digests compared, served_tier still 0; the flip to serve is a separate decision); #305 noetl-cmdbus-writer-1 is 0-replica debris with no IaC holding a bound 20Gi PVC; #304 the prod Postgres password wants rotating; #300 noetl-secret carries an orphan POSTGRES_PASSWORD key nothing binds.
Rule 0a asks that every open ai-task issue appear here, so that comparing
this table against gh issue list --label ai-task is a real check rather than a
partial one. These were tracked in their own issues but had no row on this page β
listed 2026-08-28 after the mismatch was found by running exactly that comparison.
Reconciled 2026-10-02 by running that comparison again: 12 rows were
closed issues still listed as open, and 19 open issues appeared nowhere in the
live sections. Both fixed β the table is now 54 rows and matches
gh issue list --label ai-task exactly (0 absent, 0 closed-listed-as-open).
β Measuring this needs the Recently closed / Sessions log / Releases
sections excluded and the prose links in Active umbrellas counted: a naive
table-row grep reported 84 stale rows and 67 missing, and both numbers were wrong.
Reconciled again 2026-10-04: 60 rows, 0 closed-listed-as-open, 0 open
issues absent β against 101 open ai-task issues. Two closed rows removed
(#385, #211)
and two open issues added (#380,
#204).
β The slice boundary bit again: cutting the table at the next ## heading
swallows Recently closed (it is ###), which read as 139 rows, 81 closed.
Bound it on a line-start ### Recently closed β β this very sentence put that string in backticks mid-line, so a plain str.index now truncates the slice to 1,485 bytes and the table reads empty. A note describing a trap can instantiate it. β And removing the #211 row made #204 absent β
its only live citation was inside that row's title, so a deletion can silently
orphan an unrelated issue. Re-run the comparison after editing, not before.
| Issue | Title |
|---|---|
| #422 |
noetl.event_dead_letter is write-only: once the flag is armed a parked event is the only copy and nothing can list it |
| #415 | Three pinned metric label sets have no guard, so an unpinned label would stay absent and nobody would notice |
| #410 | The serving server pod has been scraped by nothing since 2026-09-09, so six parity/mirror alert families cannot fire β manifest fixed, prod apply held |
| #406 | Watch for upstream patches: quick-xml DoS (needs an object_store provider choice) and rsa/Marvin (no fix exists) β accepted wait, not a blocker
|
| #204 | Build noetl images on Cloud Build into Artifact Registry (additive to GHCR) |
| #380 | Re-bump repos/docs to include the sharded-state-builder RFC once noetl/docs#188 merges |
| #130 | Off-server CQRS drive: per-step transition latency ~1.8β2.3s/hop (off-server state-builder, not the publish-only write path) β ~13s for a 4-step playbook |
| #141 | SLM platform β Phase B: extract + parameterize the template pack + land platform foundations G1/G2/G3 |
| #142 | SLM platform β Phase C: continuous-improvement loop + registry/experiment/lineage |
| #151 | keychain kind: secrets / provider: gcp wallet shape does not resolve on Rust runtime |
| #154 | Fix orchestrate ref-not-found infinite re-drive loop + rework SLM shadow off inclusive multi-match fork |
| #155 | Drive Muno planner turn latency from ~22-45s toward instant (eliminate orchestration tax) |
| #177 | Disable Cloudflare Pages native Git integration for travel project (keyless-bundle race) |
| #180 | e2e: unblock container_postgres_init fixture (custom image not in kind) |
| #181 | e2e: unblock tradedb_create_db fixture (absent private IQT submodule SQL) |
| #182 | e2e: unblock tradedb_bootstrap fixture (absent private IQT submodule SQL) |
| #184 | EHDB external driver: outward-facing read access for third-party apps (design/RFC) |
| #187 | B2: migrate e2e/ops DuckDB fixtures off kind: duckdb, then drop DuckDB from the worker image |
| #195 | EHDB boundary: keep the permanent event log (D1) lean; transient processing context bounded + evictable |
| #196 | EHDB boundary: tier command input like results (reference-based in the durable command dataset) |
| #198 | EHDB write-behind sink: transient processing cache β customer system-of-record + eviction policy |
| #199 | EHDB write-behind sink: wire the sink-confirmation signal + auto-sink (follow-up to #198) |
| #201 | Ship the NoETL CLI as a Python 5.0.0 wheel on PyPI (PyO3 over the real command tree) |
| #207 | Untangle noetl/core's imports of the retired Python services (R1-R3 Python-retirement gate) |
| #227 | In-flight executions do not resume after a writer restart β they stall and the guardrail re-issue loops |
| #228 | Re-issue idempotency is state-derived, not key-derived β it fails exactly when state rebuild fails |
| #254 | Execution status is derived five times in five places; unify it |
| #256 | Playbook registration accepts any tool kind; invalid kinds fail only at execute β 6 live paths are dead-since-written |
| #258 | EHDB shadow parity never compares against the authoritative log |
| #262 | EHDB tier read: decide whether one torn record should fail the whole scan or be skipped and reported |
| #267 | Prod deployment manifests omit 84 live env vars: a re-apply would silently unconfigure prod |
| #283 | Apply the noetl.command (event_id) index β blocked on a superuser GRANT |
| #294 | Archive the _probe-empty-results playbook from the prod catalog |
| #296 | Rebuilt WorkflowState embedded fold-time, not event-time: replicas diverge on entered_at/completed_at |
| #299 | NOETL_AUTH0_AUDIENCE holds a client id, not an audience |
| #301 | Keychain rationalization: migrate, eliminate, or prune the remaining 18 credential aliases |
| #309 | A DR apply of ci/manifests/noetl/ would overwrite prod with dev specs AND replace the live encryption key |
| #310 | URGENT: live credentials are committed in the public noetl/ops repo β rotate |
| #312 | POST /api/postgres/execute runs arbitrary SQL with no authentication |
| #315 | Reconcile poller re-drives a permanently-stuck execution forever: no attempt cap, no eviction, unbounded orch_cache
|
| #322 | System pool is killed by liveness 14Γ/day behind a TCP readiness probe that never withdraws the endpoint β the trigger for the #320 mirror loss |
| #323 | Every prod image pin in ci/manifests is behind live β a DR apply would roll back past two incident fixes |
| #325 | The two parity oracles disagree by construction: the cross-store comparator compares 3 fields, the fold compares content |
| #327 | Two more noetl.event writers truncate the row: project_events (LIVE, drops parent_execution_id) and events_materialize |
| #329 | Worker adoption of #328: bump noetl-executor 0.5 β 0.9.1 on its own merits |
| #330 | semantic-release publishes struct-field additions as minor: noetl-tools 3.27.0 is breaking |
| #331 | noetl-executor's version is outside semantic-release: publish-crate fails ~2 in 8 releases |
| #332 | Umbrella: EHDB embedded per shard β routing, distribution, multi-region |
| #335 | WAL read path double-applies every event: sequential loops can stall (iterations_dispatched) |
| #336 | Spec: 2026-09-11-d3-projection-serve-flip β let a behind snapshot serve, make an ahead snapshot unserveable |
| #338 | Hotels return nothing: a stale date literal in hotel-cards, laundered into an empty result |
| #343 | Mirror repair lands only partially: relay transport still fails, and a 1 MiB tier frame cap blocks large executions |
| #344 | EHDB mirror: chunk the tier batch append, then arm it β the un-batched fan-out is what times out |
| #345 | EHDB: two executions carry tier records mirrored with a result reference β repair cannot clear them |
| #351 | EHDB tier service saturated: ~36s tail reads stall the writer, server and UI |
| #353 | ehdb-selfcheck tier-load reports ok:true for an explicit tier REFUSAL |
| #354 | Catalog page is slow: /api/catalog/list ships every workflow step body |
| #356 | test_validate_weather_loop_outputs needs untracked status.json/event_log.json β the last 2 failures on noetl/noetl main |
| #359 | Prod IaC: the server manifest targets a DORMANT Deployment and would create a duplicate-event_id replica if applied |
| #363 | The materializer retries a permanently-unmaterializable event forever: no dead-letter for an FK failure |
| #364 | docs: the blueprint page generates NO heading anchors, so every inbound anchor link to it is broken |
| #367 | chain-certified projections: no profitable consumer on prod β measured, and why |
| #400 | A pypi release that forgets the version bump publishes nothing and reports success: --skip-existing plus no tag/version cross-check |
Three cutovers were refused this session rather than forced. Each is blocked on something an agent should not decide or cannot safely do:
| Issue | What is blocked | On whom |
|---|---|---|
| #234 | Repointing google-places, travel/runtime, firestore and the planner off the stale project |
IAM grant (roles/serviceusage.serviceUsageConsumer β the same one as #211) + API enablement (places/maps, firestore, aiplatform) in shastaratech-noetl-prod. β firestore is a data migration, not a repoint. |
| #247 | EHDB phase 6-10 tier serve-flips β and even shadow
|
Host/storage decision: worker pods have no PVC while KEDA scales them 2β20. Options are worker PVCs (converts a KEDA-scaled Deployment to a StatefulSet), the writer StatefulSet (durable volumes exist, but replicas: 1 already hosting both buses), or a dedicated tier workload. |
| #248 | Sink-gated eviction, and boundary compaction of ~1.7 GB | A code fix (a failed sink leaks an eviction block forever) + ops#246 merged. Compaction needs an RFC β no UPDATE/DELETE on noetl.event exists anywhere, by design. |
Also open and deliberately unresolved: #246 β the
measurement half shipped (worker v5.107.0), but whether a payload-level
provider error should terminate the step is a semantic decision still to be
made; and #169 β Auth0 JWT
verification stays in shadow until one real login supplies the aud value.
Opened 2026-08-13 β #264: the EHDB parity endpoint writes crossstore_divergence_total, the counter its own paging alert reads, so investigating a divergence alert by querying executions inflates it with nothing in the response saying so.
Opened 2026-08 β standing platform gaps rather than umbrellas, listed here
because each is something an operator would want to know before trusting a
dashboard or a manifest:
#238 (no alerting exists on
prod β zero rule objects, zero notification channels, and almost nothing
scraped),
#242 (four metric recorders with
no caller, plus the two production flags that do nothing),
#240 (three MCP agents still leak
raw provider bodies in prod β the source is merged, the catalog is not
re-registered),
#239 (git worktree on
repos/noetl silently reports the wrong directory),
#237,
#236,
#235, and
#234 (the pre-migration project
is a live dependency of 36 registered playbooks, not manifest drift).
#241 (env vars absent from the
deployment-spec pages) closed 2026-08-05 β all 225 are now documented.
#101 (orchestrator scaling β subsumed by
the #115 RFC) and #95 (postgres timestamptz
NaiveDateTime bug) both closed 2026-06-22.
#100 (cursor/claim loop mode) closed
2026-06-15 β server v3.8.0 + tools v3.10.1; test_pft_flow_v2 all_passed:true on kind (see Recently closed).
#99 (transfer-tool credential aliases) closed
2026-06-14 via tools#65 + worker#87 + e2e#58 (see Recently closed).
The subscription / listener tool RFC (#90)
closed 2026-06-12 with all 7 phases shipped + live-proven (see Recently
closed); refinement follow-ups #91β#94
- tools#57 are tracked separately.
| # | Opened | Last update | Umbrella | Status | Wiki page |
|---|---|---|---|---|---|
| #226 | 2026-08-04 | 2026-08-04 | PROD: the graceful SIGTERM seal does not reach the events log under load β reopened tip regressed 390 records |
π οΈ ROOT-CAUSED + FIXED β worker#212 + ehdb#312 open, NOT deployed. What settled it was an absence: main wraps the sequence in tokio::time::timeout(15s, worker.shutdown()) and its expiry line never appeared either β a slow seal cannot do that, a starved runtime can. seal_and_hold leaked the engine's MutexGuard through exit, which stops post-seal appends by parking every appender on a mutex never released; every ehdb-feed append path takes that std::sync::Mutex blocking from inside an async task, so each parked appender burns a whole tokio worker thread β and the prod worker's CPU limit is 2. Under load the runtime had no threads left, the per-host loop was never polled again, and the events log went out unsealed. Idle it completes in 568 ms, which is why every existing test passed. Fixed twice: FeedWriter::seal_and_close keeps the guarantee with a flag set before the lock and re-checked under it, then releases it; and graceful::seal_all phases across hosts (stop all ingest β quiesce once β persist all cursors β seal all engines on one blocking thread), host-at-a-time having been wrong independently. Partial seals now log at ERROR + noetl_worker_shutdown_hosts_{sealed,total}. Regression holds a backlog in flight at SIGTERM: 2 of 2 sealed, both logs reopen at exactly what they acked, clamped=false, 0 records below cursor. |
playbooks/223-ehdb-prod-runbook/P2-result.md |
| #225 | 2026-08-04 | 2026-08-04 | PROD: events-side consumers stop consuming after an idle period, while every health signal stays green |
π οΈ ROOT-CAUSED + FIXED β worker#212 + ehdb#312 open, NOT deployed. The events group-claim face (:9104) had set_nodelay and nothing else β no liveness at all; the WAL fan-out (:9108) had TCP keepalive but no application-level heartbeat, so it could not tell "writer alive but stuck" from "feed idle" β the one case keepalive structurally cannot catch, being answered by the peer's kernel. Neither ever got what #208 gave the command claim face. The sharp part: claim_loop and run_drain_loop_ehdb already had correct redial logic β a half-open read simply never produces the Err that reaches it, so no downstream retry could have helped. Both faces now carry keepalive on accept and connect plus the negotiated heartbeat (primitives lifted to the ehdb-feed crate root so all three faces share one definition), backward compatible both ways. noetl_worker_events_consumer_redials_total{face} makes the reattach visible β a counter that could not have moved while the consumers were wedged. 7 tests incl. two negative controls asserting a half-open consumer still parks silently with heartbeats off. Prod's 4 wedged executions have aged out; the 9 stuck RUNNING rows are #226's tail loss and were left in place (no cancel endpoint; clearing them means fabricating terminal events into an append-only log). |
playbooks/223-ehdb-prod-runbook/P2-result.md |
| #223 | 2026-08-02 | 2026-08-03 | Sequenced prod runbook for the three gcloud-blocked EHDB workstreams |
π‘οΈ P0 RUN (read-only). P1βP5 not run β prod untouched, nothing applied. P0 filled every row of the discovery table against shastaratech-noetl-prod and four runbook premises came back wrong: prod runs NOETL_STATE_BUILDER=offserver (not server, and on the server and both system pools, with a live :9108 WAL client); the writer PVCs are shard-indexed (noetl-cmdbus-writer-0-data, β¦), not the un-indexed names the IaC carried; the gateway is Deployment gateway in ns gateway; and prod runs all three materializer groups, so state_shard_write and the three-group verify_groups were right all along β narrowing them would have made verify read a live group as absent. P4c also drops from a live change to an idempotence check: the ScaledObject is already unpaused with a 3d-old HPA and a single EHDB metrics-api trigger, so the #210 signature is absent. P3/P4 then hardened and made copy-run-safe β six IaC defaults now carry prod's measured value (ops#245 @ 5e28543), so the P4 line is the P3 line with plan β converge and nothing else changed; each value that moved into a default has a row naming the kubectl command to re-confirm it at apply time. New P4 gate: K get endpoints noetl-cmdbus-writer-0 must resolve. Unchanged from the plan: P1 must precede P4 (the handover drops the writer pod and #211's sequenced seal is what makes it survivable β before it the events writer never sealed at all, and that feed is the sole writer of noetl.event); P2 must precede P4; the prod hard-kill can permanently lose noetl.event records, so the non-destructive exposure bound is the default and the SIGKILL is gated. |
playbooks/223-ehdb-prod-runbook/ |
| #222 | 2026-08-02 | 2026-08-03 | No EHDB-only IaC: declare the post-NATS topology as playbooks, then converge prod |
π‘ Declared and hardened; prod converge still pending. ops#245 (automation/ehdb/, noetl playbooks in local mode) covers the writer StatefulSet + per-shard PVCs + per-shard ClusterIP hosting all nine faces, the server/pool/gateway env with the NATS-era vars stripped, the KEDA scaler on EHDB per-subject lag, and a paired-evidence proof. 2026-08-03: P0 discovery found six defaults that would have broken or misconfigured prod β state_builder=server would have switched a running subsystem off, the claim_* names matched no prod PVC, user_pool_container=worker would have aborted the converge half-applied (a wrong container name is rejected, not ignored β a strategic merge appends an imageless container), the gateway target missed its namespace entirely, and the writer's memory limit would have dropped 4Gi β 2Gi. All six are now defaults carrying prod's value. A seventh defect surfaced only by running the adoption path on a kind rig rebuilt into prod's shape: kubectl apply's three-way merge preserves the pre-existing app: Service selector, ANDing it with the new pod-name selector the StatefulSet's pod does not satisfy β endpoints <none> behind a 1/1 Running writer with all nine faces up, cutting off server, both pools, gateway and KEDA at once. Forced with a JSON patch, reported in plan, gated on endpoints in status. Kind: plan β intended diff, converge adopts the three pre-existing PVCs verbatim and returns VERDICT: PASS (published == projected == 380, all three group cursors +380 lag 0), second converge a clean no-op (no generation moved, writer pod UID unchanged, 0 restarts). |
automation/ehdb/README.md |
| #209 | 2026-07-30 | 2026-08-02 | EHDB writer: the SIGTERM seal races in-flight ingest, and a crash loses the unsealed tail |
π‘οΈ Graceful half FIXED, RELEASED and PROD-PROVEN 2026-08-03 (worker v5.92.0, worker#211 merged ddb4de8). The first attempt did not work: it signalled with Notify::notify_waiters(), which stores no permit while the acceptor registers on its first poll, so a shutdown firing before that poll lost the wakeup and the ingest listener was never closed β caught by the release gate, and settled by lsof showing the writer's own acceptor still in LISTEN inside the test process after shutdown.run() returned (24/30 full-suite runs under contention, 0/15 idle). c44ebf2 makes the signal a watch plus an explicit ack after the fd is dropped, so "stop accepting" is confirmed. Prod proof: a controlled SIGTERM sealed hosts=2 in 568 ms β listener closed β cursor persisted β log sealed, for the command bus and the events feed; every cursor resumed at its pre-SIGTERM value with clamped=false / replay_records=0. The events writer sealing is the new part β it previously never sealed at all, and it is the sole writer of the durable noetl.event log. Prior kind proof: Sequenced, awaited shutdown β stop ingest β quiesce β cursor β seal-and-hold. Mid-burst SIGTERM under 120 concurrent execs: tip 7781 with lag 570 in the unsealed region β reopened tip 7781, clamped=false, all 570 survived. Survey found it worse than reported: the seal also raced process exit, and the events writer never sealed at all β the sole writer of the durable noetl.event log. seal_max_records (1024), so a healthy low-backlog system is MORE exposed than this rig. Bound stays the deterministic unit test: 0/300 survive with no seal. Needs L0 active-part replay on open. New sibling gap: the KV face is unsealed even gracefully (KvCoordinator exposes no flush). |
playbooks/220-ehdb-only-kind-soak/ |
| #194 | 2026-07-15 | 2026-07-31 | EHDB takeover of NATS β server-owned push transport + storage-tier cutover |
π’ EHDB-lag autoscaler LIVE on prod (2026-07-31) β the user pool scales on ehdb_feed_subject_lag{subject="commands.shared.shard.0"}: 2 β 4 β 8 β 16 β 20 β 2 under 200 real executions, every rescale attributed to that metric by name; 200/200 COMPLETED, 0 dup. Soak 240/240, out_of_order_appends=0. #208 re-verified live under load (mid-burst writer restart β 60/60, 0 publish errors). server v3.58.4 / worker v5.83.0. noetl_events is live with 4 consumers (3 worker materializers + the gateway SSE) β T3 unchecked; deleting NATS today costs the SPA its live updates and breaks async callbacks. Also nats-jetstream KEDA trigger was not the harmless no-op the plan assumed β 149 messages frozen since the T4 cutover drove the pool to 15 replicas within 40 s of unpausing; removed. Pre-T5 inventory: playbooks/210-t5-readiness/pre-t5-checklist.md. |
ehdb: L1 Command-Bus Cutover |
| #166 | 2026-06-30 | 2026-07-13 | RFC: object-store-backed sharded state_builder (durable fix for the unbounded in-memory WAL index) |
β
Phase 5 ROLLED TO PROD + validated (2026-07-13); only the deferred Leg 3 remains. Phases 1β4 LIVE on prod (bounded WAL index + Feather state-shard writer + cold-load-on-miss + execution-affinity, worker v5.52.0). Phase 5 Legs 1+2 (server v3.51.0) NOW ROLLED TO PROD + validated 2026-07-13 β Stage 1 Mode A (NOETL_SHARD_SUBJECT_ROUTE=true + NOETL_COMMAND_SHARD_COUNT=2 on noetl-server-rust v3.52.0 β noetl_command_publish_total{pool="system",route="sharded"} climbing, shard.0.>/shard.1.> flowing); Stage 2 both system-pool workers on per-shard durable consumers (noetl_worker_system_rust_shard0/_shard1), GATE-1+GATE-2 PASS, 0 restarts, old broad consumer noetl_worker_system_rust idle/drained, redirect tax eliminated (Leg 1 server#273; Leg 2 state-shard GC NOETL_STATE_SHARD_GC server#275). Correctness confirmed in prod: server shard-routes every pool==system command under Mode A (incl. system/scheduled_cleanup) β the earlier "keep legacy subject in the filter" caveat was WRONG/unnecessary. Leg 3 (P2P warm-handoff) deferred by design β sole remainder; #166 OPEN tracking only Leg 3. 2026-07-12: merged the kind validation harness ops#238 (49c0948) + full kind dry-run of the rollout sequence (Mode A + Leg-2 GC dry-run + rollback, all green, baseline reversible) + copy-pasteable prod runbook playbooks/166-phase5-prod-rollout.md. Rollout is human-gated (GKE); agent does not run it. β history: π RFC opened β design only, no code, no prod change 2026-06-30. Root-caused the recurring system-pool OOM/wedge (#163/#161): the off-server state_builder's in-memory WAL chain-index is O(all event history) β measured 654 execs / 17,803 events / ~1.28 GiB idle, ~73 KiB/event because IndexedEvent::raw clones the full envelope; terminal-only eviction (no LRU/TTL/byte-cap), + a full 24h WAL replay on every boot. The 768Miβ2Gi bump only moves the OOM date. RFC: object store = source of truth for state/event-index shards (Arrow Feather, extending the #104 tier from result bytes to state shards); workers = bounded LRU/TTL/byte-ceiling cache + cold-load-on-miss β O(active working set) memory. Sharding reconciles the two existing hashes (#104 FNV-1a locator folder for object layout; #116 XxHash64 execution-affinity for cache ownership). Slim chain (drop the full-envelope clone) + result_ref URN; stateful vs stateless worker split; failover = cold-load (state stops being a single resident copy that dies with the pod); preserves sole-writer (#103), causal order (#117), expected_head guard (#156), never-scan (#115). Phased + reversible; Phase 1 (bound the existing index + slim chain, worker-only, flag-gated) ships the memory relief first and measures the working set before any object-store latency hits the hot path. Doc noetl/docs#188 (docs/architecture/sharded_state_builder.md, review-only). |
docs RFC |
| #139 | 2026-06-25 | 2026-06-27 | Domain-Specific SLM platform β build + continuously improve org SLMs via NoETL playbooks (MLOps-as-playbooks) |
πΆοΈ Shadow rollout Option A built + kind-validated (#153, review-only) 2026-06-27 β local-MLX slm_serve + a default-OFF shadow_slm_compare planner leaf (inclusive fork, byte-identical served turn) + slm_replay --shadowβdataset_build flywheel; kind proofs pass; v3 22-turn stratified agreement tool/arg/slot 0.86 / intent 0.82 / widget_type 0.73 / schema validity 100%; PRs ops #222 + travel #78, not merged, nothing on prod (operator turn-on = endpoint + slm_shadow.enabled flip). Prior β Phase A in progress β #140 dataset_build + eval shipped as generic config-driven playbooks (ops PR #212 + travel PR noetl/travel#73, review-only); config-only proven on a 2nd toy domain; baseline = 100% schema validity + sub-ms floor latency (deterministic floor; OpenAI ceiling deferred on teacher budget). RFC draft otherwise design+tracking only. Generalizes the travel-domain SLM (noetl/travel#63, the reference implementation) into reusable tooling any org uses to (a) stand up a domain SLM by writing one slm.config.yaml + schemas/prompts/seed-data, and (b) keep it continuously improved by a scheduled playbook loop. Three parts: a parameterized automation/mlops/slm/<stage> template pack (org provides config; framework supplies every stage DAG); a continuous-improvement loop (traffic capture β drift β teacher/human relabel β scheduled retrain β shadow-eval vs incumbent + deterministic floor β gated auto-promotion with registry versioning + instant rollback, with Β§4 retrain/promotion gates); and domain-agnostic platform foundations generalizing travel#70/#71/#72 β G1 container/GPU job dispatch (#144), G2 long-async (#145), G3 artifact storage + model/dataset/eval registry on #104 (#146), G4 experiment store (#147), G5 lineage (#148), G6 cost controls (#149), + the loop engine (#150). Phases AβD = #140β#143. RFC docs/rfc/domain-slm-platform.md. 8 open productization decisions for the platform owner (RFC Β§10). |
Umbrella: Domain-Specific SLM Platform |
| #107 | 2026-06-17 | 2026-06-17 | Program: Distributed Multitenant OS β Server Dissolution β Global Grid | The strategic roof over #101β#105. Blueprint names NoETL a distributed multitenant OS (serverβstateless edge; NATS WAL + object store the only durable state; processing = event-driven system playbooks on a sharded grid; foundation for quantum-cloud-hybrid). 5-step path: CQRS cutover (step 1, shadow green) β orchestrator-as-plug-in (#108, done 2026-06-18 β drive core fully wasm-resident (#109 closed); the system/orchestrate plug-in compiles to a 0-import .wasm (server#224), runs identically to native in wasmtime (server#225), is seeded into the registry on boot + servable (server#226), and drives the real workload identically live β shadow over the 10Γ1000 PFT, 529 evals 0 mismatch (server#227); worker-driven cutover β the drive runs OFF-SERVER on the pool, kind-validated β entry/run_state dispatch (worker#113) + apply_orchestration_result (server#228) + the flag-gated scheduler/apply/state-guard (server#229, NOETL_ORCHESTRATE_PLUGIN_DRIVE); simple_python drove startβendβCOMPLETED through the worker round-trip. #108 CLOSED 2026-06-18 β (c) the default-flip shipped (server#233, v3.28.0, drive default ON) after a clean scale soak; orchestrator-as-plug-in is done; the in-server shadow + wasmtime server dep retired in #110 / server#234) β per-shard WAL β drop Postgres β cross-shard federation. |
docs blueprint |
| #115 | 2026-06-19 | 2026-06-20 | RFC: Decouple result data from context β reference-only schema + one-level event chain + worker-side state builder |
PROGRAM-SCALE STEP 2 SHIPPED + multi-replica validated 2026-06-20 β execution-affinity write ordering (#116). Step 1 (KV coherence, server v3.38.0) was necessary-not-sufficient; affinity routes every trigger for an execution to the replica that ShardConfig::owns it (non-owner forwards) so the readβadvance is atomic per execution β 2+ replicas produce one unforked chain. server#252 β server v3.39.0 5e00d0a (src/affinity.rs; NOETL_EXECUTION_AFFINITY/NOETL_PEER_URL_TEMPLATE/NOETL_SHARD_INDEX_FROM_HOSTNAME, all default off) + e2e#71 66b6e1b (2-replica StatefulSet topology + rig HARD gate). Multi-replica gate-ON kind PASS: linear/loop/fanout COMPLETE, every chain roots=1/dangling=0/walk==total, forwarded_ok proof, never-scan + sole-writer across replicas; single-replica unchanged; 595 server tests + clippy green; baseline restored. Remaining #117: off-server from_events spine ordered by event_id wedges fan-in under a chain-orderβ id-order inversion (affinity + high-concurrency fanout) β fix = order spine by prev_event_id walk; linear/loop already reliable. PROD GKE untouched; all affinity flags default off. Phase 1 SHIPPED 2026-06-19 β references-in-state consume side. Worker resolve_context_references made selective (resolve a noetl:// ref only when this command's tool input binds the step's bulk β a path the bounded extracted summary can't satisfy; predicate / scalar / _ref access reads off the summary; worker#117 v5.36.0). Server hydrate_result_references surfaces _ref/_store/_uri on the kept summary + refs_in_state default flipped to true (server#243 v3.30.0). Kind gate-ON: all 9 #113 stalls COMPLETE (max command ctx 412KB, 0 __orchestrate__ event rows, materializer lag 0); the 3 targets bounded (output_select 1.32MBβ10KB, storage_tiers 17.4MBβ36KB, lease_expiry 201 spinning orch cmdsβ16). Closed #113 + #114. Off-server-drive prod cutover (#107/#111) unblocked on the size axis; PROD GKE untouched. Phase 2 MERGED 2026-06-19 β one-level prev_event_id event chain (server#244 β server v3.31.0 f5bd4a8 + noetl#667 β noetl ecd16a2; ai-meta pointer afdb365): each noetl.event/noetl.command carries the chain link, stamped at the emit chokepoint from a per-execution chain-head watermark, covering both gate paths + the materializer; chain-correctness proven walkable / 1-root / no-gap / no-scan across 6 gate-ON executions (incl. a real fan-out branch origin), sole-writer + Phase-1 bounded sizes intact, 573 tests + clippy green. Post-merge verified on live kind (both prev_event_id columns present, server image reflects merged code, gate-ON baseline live). Phase 3 MERGED 2026-06-19 β chain-walk state builder (server-side, flagged) (server#245 β server v3.32.0 8338417; ai-meta pointer bumped): the drive rebuilds WorkflowState by walking prev_event_id headβroot (in-memory ChainHeads head + (execution_id,event_id) PK lookups, no WHERE execution_id scan) β same from_events (orchestrate-core unchanged; parity by construction), behind `NOETL_STATE_BUILD_MODE=chain_walk |
event_scan(defaultevent_scan, prod unchanged), event-scan kept as the fallback (cold-head / lag / non-genesis). Gate-ON validated: parity 41/41 MATCH 0-mismatch (tx-isolated), NO-SCAN proven (event_scans_total=0, 40 builds / 1064 PK hops / 0 fallbacks), all topologies COMPLETE, sole-writer + lag-0 + gate rig PASS, 577 tests + clippy green. Self-merged (no classifier block). **Phase 4 KERNEL + FLAG SHIPPED + shadow kind-validated** ([worker#118](https://github.com/noetl/worker/pull/118) β worker **v5.37.0** fef961c+ [server#246](https://github.com/noetl/server/pull/246) β server **v3.33.0**3e6006d): the pool-side state_builderreconstructsWorkflowState from the **noetl_eventsWAL** β a per-execution chain index walksprev_event_id headβroot, caching the spine keyed by the immutable chain head + incremental tail-advance + cold-rebuild on miss/restart. A live WAL **shadow** loop (NOETL_STATE_BUILDER_SHADOW, default off) + the NOETL_STATE_BUILDER=offserver |
| [#49][i49] | 2026-06-02 | 2026-06-14 | Rust server FastAPI parity port β primary | server v3.6.0 (system worker pool + cleanup/purge endpoint, server#193); prod is Rust-only (server-rust + worker-rust + system pool). Remaining entangled refactor tracked in #97. | Umbrella: Rust Server Port |
| #104 | 2026-06-16 | 2026-06-23 | Event WAL + derivable result storage: NATS-as-WAL, logical-URI naming, Feather tier | In progress β πͺΆ OQ5 Option A SHIPPED 2026-06-23 β producer-staged result tier (worker v5.46.0, NOETL_RESULT_PRODUCER_STAGE default-off) decouples the over-budget tier write from result_store; result_store-retirement soak LIVE on prod β T0 2026-06-23T21:45Z (48h floor / 72h target): both worker pools on v5.46.0 + PRODUCER_STAGE=true; the original mint_authoritative{legacy_fallback}=0 gate was vacuous (MINT unset on worker-rust) so it was repointed to resolve_total{fallback_*}=0 + materializer_skip_exists>0 + staged>0 (ops#207), and a GMP rule-label fix noetl.io/*βnoetl_io_* (ops#208) restored evaluation of both this gate and the #103 flip guardrail; ai-meta ops pointer 8940fd5. β
Phase D ENABLED LIVE on prod 2026-06-23 β staged AβD enablement complete. NOETL_RESULT_MINT_AUTHORITATIVE=true on system-pool + server only (NOT worker-rust β would spawn a stray result materializer at the wrong cell; its consume already resolves from the tier via Phase C): the URNβFeather/GCS tier is now the authoritative result store with noetl.result_store kept as the reversible dual-write fail-safe. Validated across 4 over-budget execs β Feather object only at env=prod/cell=usc1-a (no stray), result_store_dual_write_total 1:1 with execs (+ result_store row each time), resolved_feather correct (1200/1100/test_passed=true), event-mat sole-writer projected==acked, lag 0, never-scan 0, 0 restarts, 0 auth errors. LEFT ENABLED, revert armed (set env β¦ NOETL_RESULT_MINT_AUTHORITATIVE-); GC/DR stay OFF. Remaining: OQ5 byte-source re-plumb + result_store retirement (gated). Prior β Phases E + F MERGED 2026-06-23 (the FINAL build phases; all PRs squash-merged in dependency order; flags default-off β inert in prod). Phase E β side-effect durability barrier: tools#78 publishes noetl_tools::registry::kind_is_side_effecting (noetl-tools v3.17.0 1d49dd5; members noetl-directives + noetl-locator re-published first per the workspace publish order), worker#130 the consume-pool barrier (NOETL_SIDE_EFFECT_BARRIER, adopt-only β side effects fire exactly once across re-drive) β noetl-worker v5.44.0 d696f7e, e2e#79 rig. Phase F β result-tier GC + DR: server#264 conservative dry-run-first GC sweeper (NOETL_RESULT_TIER_GC, never deletes a live-referenced object) β noetl-server v3.44.0 341b614, worker#131 materializer verify-and-repair DR (NOETL_RESULT_TIER_DR, byte-identical re-derive) β noetl-worker v5.45.0 dd07016, ops#205 + e2e#80. worker#130/#131 depend on the published noetl-tools 3.17 (no patch/path/git dep). ai-meta pointers bumped. AβF BUILD phases now complete β only operational items remain on #104 (prod GCS infra, OQ5 byte-source re-plumb, staged prod enablement/minting cutover). #104 stays OPEN. Prior β Phase D MERGED 2026-06-23 (all 4 PRs squash-merged in dependency order server β worker β ops β e2e; flags default-off β inert in prod): the minting flip ships the dual-write window β NOETL_RESULT_MINT_AUTHORITATIVE makes the URN β Feather/GCS tier the authoritative result store (materializer = authoritative tier writer, resolve-by-URN = primary consume path) with noetl.result_store kept as the reversible dual-write fallback. No new crate publish β no Cargo.toml dep change in either PR; server resolves noetl-locator 0.1.1, worker resolves from the registry. semantic-release cut noetl-server v3.43.0 + noetl-worker v5.43.0. server#263 β noetl-server v3.43.0 6f6b9ef; worker#129 β noetl-worker v5.43.0 be6863a; ops#204 b19b759; e2e#78 07e85aa. ai-meta pointers bumped. OQ5 β result_store retirement β DECIDED metric-gated (drop dual-write once mint_authoritative_total{path=legacy_fallback} holds 0 across a staging soak + retention time floor), gated on a not-yet-done byte-source re-plumbing prerequisite. Prod minting cutover (rolling server 3.43.0 + worker 5.43.0 to GKE) is a separate next task. #104 stays OPEN (Phase E/F + minting cutover remain). Prior β Phase C MERGED 2026-06-23 (all flags default-off β inert in prod): the result-DATA read half β server GCS object backend + cell-endpoint registry + GET /api/internal/cells, worker resolve-by-URN read path (closes OQ6) + fixes B/B1; server#262 β noetl-server v3.42.0 c2d5ca9, worker#128 β noetl-worker v5.42.0 7971041, e2e#77 39dc880. Prior β Phase B MERGED 2026-06-22 (flag default-off β inert in prod until a future rollout enables NOETL_RESULT_MATERIALIZER_ENABLED): the shadow Feather result tier β a separate noetl_result_materializer consume-loop on the system pool writes the over-budget result (tabular β Arrow Feather, non-tabular β JSON, small β inline no-op) to the derived Β§7 key alongside noetl.result_store; nothing reads it until Phase C. All 5 PRs squash-merged in dependency order; noetl-locator member bumped 0.1.0β0.1.1 pre-merge so the additive ResultCoordinates::parse/from_locator API publishes. tools#77 β noetl-tools v3.16.0 7da39d8 + noetl-locator 0.1.1 on crates.io; server#261 β noetl-server v3.41.0 4a6659e (sibling consumer); worker#127 β noetl-worker v5.41.0 4b1c15b (consume-loop); ops#203 c92753c (flag + cell seed); e2e#76 04c3332 (kind rig). No downstream repoint needed β worker resolves noetl-tools ^3.14.2 + a self-contained local inversion; server resolves noetl-locator ^0.1.0; both from the registry, no git/branch dep. #104 stays OPEN (Phases CβF remain). Prior β Phase A MERGED 2026-06-22 (flag default-off, inert in prod until a future server rollout): slim dependency-free noetl-locator 0.1.0 extracted + published to crates.io (resolves OQ7 β control plane parses the canonical result URI without noetl-tools' heavy graph; duckdb/kube/arrow/tonic/rhai/gcp_auth confirmed absent from the server tree; noetl-tools re-exports it), and the server accepts the canonical URI behind NOETL_RESULT_URI_ACCEPT. tools#76 β noetl-tools v3.15.0 dc0c5d8; server#260 β noetl-server v3.40.0 c89d078 (dep repointed gitβ0.1.0 pre-merge, 623 tests green); e2e#75 rig eeca8b7. Prior β RFC upgraded for review (2026-06-22): now that the event-WAL half is live on prod (materializer sole writer, off-server chain-walk, prev_event_id, off-server CQRS cutover v3.39.5), the RFC is re-scoped to the result-DATA half β move result bytes off Postgres noetl.result_store into a derivable-URN-addressed Arrow Feather tier in object store, add the resolve-by-URN read path + cell endpoint registry, place the durability barrier at side-effecting tool boundaries. Decided/proposed/open separated; phased plan AβF; #104 is on the critical path for #107 steps 4β5. Review requested on #104. Prior: off-server-drive Γ gate reconciliation PROVEN (2026-06-19): gate-ON (PUBLISH_ONLY=true) with the off-server drive (PLUGIN_DRIVE=true) + materializer sole writer is now green on kind (the combo #103 left unproven) β fresh exec + cursor fan-out β COMPLETED, server wrote 0 noetl.event rows (all PUBLISHED), materializer materialized all exactly once (rows==distinct ids, 0 dup), read-your-writes held via the relocated trigger. Server #238 β v3.29.2 rebuilds WorkflowState from the durable log on cold-cache apply (crash-recovery, kind-proven cold_rebuild); committed e2e rig kind_validate_orchestrate_gate.sh (e2e#61). This unblocked the #103 flip (the 2 cancel/finalize sites are now also done β server v3.29.3 #240; #103 is FLIP-READY). Prior: naming foundation noetl_tools::locator (tools#68/#70, v3.12.0) + worker URI stamp (worker#99); blueprint (docs#180). |
Umbrella: Event WAL Storage |
| #98 | 2026-06-14 | 2026-06-14 | Grow the Rust regression baseline: migrate Python-era e2e fixtures |
Snowflake key-pair JWT validated β the last external-tool gap is closed. tools v3.9.2 (tools#62/#63/#64); create_sf_database + setup_sf_table COMPLETED via JWT on kind. Transfer step deferred to #99. Core green: 64 fixtures. |
β |
| #97 | 2026-06-14 | 2026-06-14 | Retire remaining Python-era deploy legacy (manifests, kind automation, helm chart) | Open β Todo. Python manifests, kind redeploy automation that hardcodes Python deployment names, stale helm release rev 185. | β |
| #111 | 2026-06-18 | 2026-06-19 | E2E: worker-driven orchestrate topology coverage + server-API-only gap tracking | In progress β three committed kind rigs now: kind_validate_orchestrate_offserver.sh (e2e#59) asserts the off-server topology (gate-off), kind_validate_orchestrate_gate.sh (e2e#61) asserts it composes with the PUBLISH_ONLY gate, and kind_validate_cancel_finalize_gate.sh (e2e#62, 2026-06-19) dual-mode-asserts the ExecutionService cancel/finalize writers honour the gate (gate-off byte-identical INSERT; gate-on PUBLISHED, materializer sole writer, terminal state, 0 loss/dup) β the rig that closed the last #103 flip blocker. Off-server rig live-green (COMPLETED, __orchestrate__ in noetl.event = 0, dispatched=applied, shadow metric absent). Durable home for the server-API-only gap (server still sole-writer + rebuilds state β moves under #103/#104) + two operator decisions: (A) retire in-process drive fallback (gated on prod adopting a post-#108 image; prod still pre-#108), (B) reap accumulating __orchestrate__ PENDING delivery rows in noetl.command. |
β |
| [i43]: https://github.com/noetl/ai-meta/issues/43 | |||||
| [i49]: https://github.com/noetl/ai-meta/issues/49 | |||||
| [i77]: https://github.com/noetl/ai-meta/issues/77 | |||||
| [i78]: https://github.com/noetl/ai-meta/issues/78 | |||||
| [i79]: https://github.com/noetl/ai-meta/issues/79 | |||||
| [i80]: https://github.com/noetl/ai-meta/issues/80 | |||||
| [i54]: https://github.com/noetl/ai-meta/issues/54 | |||||
| [i61]: https://github.com/noetl/ai-meta/issues/61 |
| # | Closed | Title |
|---|---|---|
| #402 | 2026-10-04 |
The patchable half of the Dependabot-vs-RustSec gap is closed on the published artifact. noetl 5.1.1: shipped-lock RustSec 5 β 3 (h2 0.4.19, rustls 0.23.45). β β The number is only a result because the previous release's lock was scanned first as a positive control and reported exactly the five. Remaining 3 are accepted waits, not blockers β #406. β On the same lock: Dependabot 0 open, RustSec 3 β a clean Dependabot page is not a statement about the Rust dependency graph. |
| #390 | 2026-10-04 |
arm64 is built every release, not back-indexed. The honest option rather than the cheap one: build-deb is a 2-entry matrix on ubuntu-24.04-arm, so newest arm64 moved 2.8.7 β 5.0.3. Live repo after: .debs in no index 22 of 23 β 0 of 26, [amd64] β [amd64, arm64], Release fields 1 β 8, self-entry gone. β β Latent and never reported: without --arch, every .deb lands in one index β it would have served arm64 binaries to amd64 clients the moment arm64 existed. Promoted REPORTβFAIL (apt#3), each check proven able to fire. |
| #398 | 2026-10-04 |
All 7 Rust repos pin 1.99.0. Cost was the 2 lints measured in advance (cli needless_borrow; gateway result_large_err, kept as a scoped #[allow] because axum's middleware signature makes the error variant a whole Response). β β The pin found what the measurement had not: gateway would not build β base64-compat declares [lib] name = "base64" (E0464), referenced nowhere. I called it a local artifact because it reproduced on both local toolchains; CI proved the location real. |
| #395 | 2026-10-04 |
PUBLISHED noetl 5.1.0 β the shipped wheel now carries the patched crates. Verified uploaded, not skipped (Skipping 0, already exist 0; PyPI 5.1.0 absent before, 4 files after) and the four crates confirmed three independent ways. β β twine --skip-existing + no tag/version cross-check would have published nothing and gone green (#400). β β Closure is scoped to the 7 tracked alerts β RustSec finds 5 more in the same lock (#402). |
| #378 | 2026-10-03 |
Clippy allow-lists 23 β 2 and all 7 Rust repos pinned. server 14β0, worker 7β0; the 2 left are cli's and are justified by measurement (boxing ToolSpecDoc::One makes schema_guard fail). β β server's -A dead-code was hiding #361 β and the entry hiding the cause sat adjacent to it. β β Pinning found ehdb's main already queued to fail on a 1.99.0 deprecation with no commit at fault. Residual: #398. |
| #361 | 2026-10-03 |
Two server tests carried no #[test] and had never run β armed, and both pass. Found because #378 removed the -A dead-code that was suppressing the report. β β A stray #[test] above a doc block bound BOTH attributes to the earlier function (duplicate_macro_attributes, also allow-listed) leaving the next one inert β the two entries that hid it sat adjacent. An allow-list hides the next finding of the same kind.
|
| #385 | 2026-10-03 |
All 7 Rust advisories cleared; open alerts on noetl/noetl = 0 (control-verified: state=fixed returns 290). Arc 214 β 31 β 7 β 0. β But the published wheel still embeds every one of them β pypi.yml publishes only on a pypi-v* tag and none was cut, so Dependabot's 0 describes the repo, not the artifact (#395). |
| #375 | 2026-10-03 | Every repo that can host a PR gate now has one. ops (#320), e2e (#93) and apt (#2) had none; the 15 wikis cannot host Actions, so a scheduled ai-meta sweep covers them (#392). Each gate measured green first, prints its denominator, and fails below a population floor. β β the sweep's first version printed FAIL and exited 0. |
| #374 | 2026-10-03 | clippy now GATES on all five Rust repos β the ` |
| #221 | 2026-08-03 |
An unset internal event-bus source no longer selects the deleted NATS transport β SHIPPED + IN PROD. from_env_value(""), default() and a typo like ehbd all resolved to Nats, a transport deleted 2026-08-01, and the failure was silent: the worker registers, heartbeats, reports ready, and its group cursor never moves. Now a hard error resolved on the startup path for all four internal consumers (two had been resolving inside their spawned task, where an error was only a tracing::error! and a dead materializer). Released as worker v5.92.0 (worker#211 ddb4de8) and deployed to shastaratech prod; the P1c precondition check confirmed all four consumers on both system pools already carry _SOURCE=ehdb, so nothing crashlooped β 5/5 pods up on the new digest, 0 restarts. Carve-outs untouched: the user-facing nats tool kind and state_builder's user-endpoint paths. |
| #210 | 2026-07-31 |
User-pool KEDA autoscaling restored and moved onto the EHDB bus β LIVE on prod. The pool had had no autoscaling of any kind since 2026-07-26 (KEDA 2.15 deletes the HPA while paused is set, so the object looked configured while nothing watched it). Now scales on ehdb_feed_subject_lag{subject="commands.shared.shard.0"} β 2 β 4 β 8 β 16 β 20 β 2 under 200 real executions, every rescale attributed to that metric by name, 200/200 COMPLETED / 0 dup; soak 240/240 with out_of_order_appends=0. nats-jetstream trigger was not the harmless no-op the plan assumed β 149 messages frozen since the T4 cutover drove the pool to 15 replicas within 40 s of unpausing; removed (ops#244). |
| #206 | 2026-07-31 |
out_of_order_appends exposed on the command-bus writer :9102. The #203 delivery-loss detector had been asserted in ehdb's tests since the fix but was unobservable in production β inferable only after the fact from delivery accounting in noetl.event. T5 removes the fallback bus, so it had to become a gauge a soak can watch, not a postmortem query (worker#198, worker v5.83.0). |
| #203 | 2026-07-28 |
EHDB command bus: records ingested into the writer feed but never delivered to a claimer (~10% loss under load, feed lag=0) β FIXED + PROD-VALIDATED. Root cause: the producer (noetl-server) assigned each feed record's sort key from the command's snowflake event_id; snowflake ids are sparse, so under concurrent publish a lower id could reach the single writer after a higher one. The follower cursor had already advanced (ChangeFeed::poll sets cursor = max sort_key read), refill reads strictly > cursor, so the late record never entered pending and was never delivered β and because lag() is cursor-relative too, a record below the cursor was never counted, making the loss silent. PartWriter::append had never enforced the ascending-sort_key contract the Dataset API promises. Fix: the writer assigns the ordering key (append_writer_assigned) β the only component that can guarantee it (ehdb#300 β ehdb d4b6235 β worker v5.81.1 + server v3.58.1). Kind before/after, identical 40-concurrent burst: 17/40 (23 permanently stuck) β 41/41 (0 stuck); exactly-once 335=335, pod-kill redelivery 25/25. Then prod-validated in the successful T4 full flip. New out_of_order_appends counter β not yet on :9102, tracked as #206. |
| #202 | 2026-07-28 |
EHDB L1 T4 canary blocker: per-shard user-pool split vs collapse command-bus to 1 shard β DECIDED (Option 2), executed, prod-validated. The EHDB bus partitions every command by execution_id including the shared pool, but the user pool is a single deployment claiming from a single writer β on the 2-shard prod that would strand roughly half the shared commands. Option 2 chosen: collapse the command-bus axis to one shard. The two sharding axes were proven independent in code first (sharding.rs:172 "correctness never depends on affinity"; the command bus reads only NOETL_COMMAND_SHARD_COUNT, the state materializer only NOETL_RESULT_SHARD_COUNT), so #166 state sharding was never touched. Kind-validated on a prod-mirroring rig (0 dup across 5 competing consumers, redelivery 8/8 0-loss) β which also caught three config bugs before prod (shared NATS consumer or double-delivery; writer NATS_STREAM; a writer in ehdb mode also needs NOETL_COMMAND_BUS_CLAIM_ADDR) β then prod-validated in the successful full flip. The per-shard user-pool split was not needed and not built. |
| #189 | 2026-07-14 |
NoETL cloud provider tool (kind: provider) β all 5 rounds DONE, umbrella CLOSED. Terraform-style infra management via NoETL playbooks (local mode). R1 Google REST MVP (3.21.0) β R2 apply-mode converge + LRO + local-mode CLI dispatch (3.22.0/cli 4.14.0) β R3 confirm-gated destroy + EHDB ownership projection + endpoint override (3.23.0/cli 4.15.0) β R4 reconcile policy report/enforce/adopt + confirm-gated drift adoption (3.24.0/cli 4.16.0) β R5 noetl provider {plan,drift,orphans,adopt} CLI verbs + wire-shape eventlog-tier fold adapter + stack-scoped ownership (noetl-tools 3.25.0 [tools#90] / noetl-cli 4.17.0 [cli#68]), crates.io-tarball + released-binary verified. Final fork settled by ADR (2026-07-14): keep the raw eventlog-tier fold as the SUPPORTED ownership mechanism; the first-class provider.resource.converged event type + curated projection column is REJECTED β it would carve out #178's secret-free-by-construction invariant (the desired spec is credential-free but payload-derived). Revisit only if the raw-tier fold's cost measurably bites (fold latency at scale / tier retention-GC reclaiming needed facts / raw tier unavailable to the query path). Separate defect tracked on its own: #191 (the 3.23/3.24 broken fixed-path extractor). ai-meta d4a4df06 + 56a9b82e + 2a343875. |
| #171 | 2026-07-02 |
Guardrail: orphaned-command sweep SHIPPED + LIVE on prod (server v3.50.0). Server-side flag-gated sweep terminates dead-worker-orphaned RUNNING execs append-only (playbook.failed) once the owner is absent/stale in noetl.runtime β fixes the recurring worker-roll zombie re-drive. server#271; ghcrβAR server-rust:v3.50.0; NOETL_ORPHAN_SWEEP_ENABLED=true. Prod-proved LIVE: a deliberately-orphaned muno/ exec (worker killed mid-call_duffel_offers, no NATS redelivery) swept to FAILED at grace(180s)+one tick, {candidate=1,terminated=1} idempotent 0-error 0-collateral; a pre-ack orphan instead auto-recovered via redelivery. 0 restarts, login 0.4s. Rollback = flag off β v3.49.0. |
| #167 | 2026-07-02 |
Gateway auth client-timeout decouple SHIPPED + LIVE (travel#90, bundle index-CCFdWtGQ.js). SPA gateway-auth client timeout 15β40s + one silent retry so a slow-but-succeeding auth drive no longer locks the user out; complements the structural cure #168 (already live). Shipped with #91's slow-turn robustness (friendlyTurnError + checkPlaybookAccess timeout/retry + Retry button). Verified on prod (login healthy + Paris turn renders place_list, no regression). Rollback = prior bundle index-Buo8vFKk.js. |
| #162 | 2026-06-29 |
Muno HOTELS bookable end-to-end on prod β planner v58 + HotelBeds MCP v4 (book_hotel). Hotels were search-only; added a Book CTA carrying the search-time rate_key to mcp/hotelbeds.book_hotel (APITUDE checkRateβbookings, TEST/SANDBOX-guarded β api.test.hotelbeds.com, refuses any non-test host) + a new hotel_confirmation widget. Registered live (latest-wins): MCP v4 (cid 660409660586853242, backward-compatible) + planner v58 (cid 660417115383399292). Verified on prod (muno/hotelbook-*): real TEST booking refs 197-14375473/74/76 (De la Jatte, Paris, CONFIRMED, env=test) incl. live-path β hotel_confirmation; plain hotel search + "Trip to Paris" + login all healthy; 0 core restarts. Rollback = re-register planner v57 cid 660221263901623160. ops#228 + travel#87; refs #155. |
| #134 | 2026-06-26 |
Gateway session-validate 404 β Muno (travel) auth unblocked on prod. The PythonβRust cutover left /api/auth/session/validate unimplemented on noetl-server-rust; on a NATS session-cache miss the gateway POSTed that dead route β 404, rejecting every cache-missed Muno sign-in since ~01:54 UTC. No config toggle existed (auth_playbooks.validate_session was vestigial) β gateway code fix: validate cache-missed sessions by executing the auth0_validate_session playbook via the workerβcallback mechanism (mirrors the live check_access); flag GATEWAY_SESSION_VALIDATE_VIA_PLAYBOOK (default true, =false β legacy REST). gateway#31 β v3.4.1 (8abef26) + e2e#83 (36dced2) merged; playbook prod catalog v87, check_playbook_access v88. Prod gateway ns runs the same-code fix image (fix-validate-20260624 @sha256:90dc6cec, 18h/0 restarts; v3.4.1 CI image is ghcr-only). Core/OQ5 soak untouched; 0 restarts. Live proof: 0 session/validate 404s over 18h + probe POST /api/auth/validate {bogus} β 200 {valid:false} with playbook executed + callback delivered. ai-meta ec0fa35. Sibling of #133. |
| #95 | 2026-06-22 |
postgres pg_value_to_json returned null for tz-naive timestamp (NaiveDateTime) + date/time/uuid/numeric/bytea β FIXED + shipped to prod. The Rust postgres tool probed i64/i32/f64/bool/String/json/DateTime<Utc> and fell through to Value::Null for everything else; a plain timestamp column hit the fall-through and serialized to null even though the value was present (the auth0_login expires_at: null repro that tripped the gateway's validation). Added arms for timestamp (ISO-8601, no offset), date/time, uuid, numeric/decimal (exact decimal string via lossless binary-wire decode), bytea (base64). tools#75 β noetl-tools v3.14.2 6d9b674; worker#126 pin bump β noetl-worker v5.40.5 da24952. Built prod noetl-worker-rust:v5.40.5 @sha256:45212dbeβ¦ (Cloud Build us-central1) + rolled by digest onto noetl-worker-rust + noetl-worker-system-pool (server stays v3.39.5, CPU 250m/2 kept) β rolling restart clean, off-server cutover stayed healthy (materializer sole-writer lag 0, command lag 0, WAL rehydrated per #119). 409 tools tests + clippy + a kind-gated live-postgres before/after test. |
| #103 | 2026-06-22 |
Step 2 β CQRS event log β DONE. Off-server CQRS cutover is live + validated on prod (server v3.39.5 PUBLISH_ONLY=true+STATE_BUILDER=offserver / worker v5.40.x Rust in-process materializer src/materializer.rs as sole noetl.event writer, replacing the Python system/event_materializer playbook). Prod-scoped regression validator landed via e2e#74 (e2e@1deadf1) β 28/30 PASS against live prod (2 non-PASS = pg-cred prod-env diffs, not cutover bugs). Superseded Python ops PRs #189/#190/#192 closed in the same pass. |
| #102 | 2026-06-22 |
Step 1 orchestrator throughput β DONE. Batch noetl.event INSERTs shipped (server#198 v3.10.0 + server#199, v3.15.x); Part B's worker-side batched event emission was superseded by #103 (worker publishes granularity-preserving events to JetStream instead of HTTP-POST), now live on prod. No residual. |
| #101 | 2026-06-22 |
Orchestrator scaling β DONE. Acceptance met + kind-validated: incremental OrchStateCache, server-side hydrate_result_references, env-configurable worker inline budget NOETL_EVENT_RESULT_CONTEXT_MAX_BYTES. Remaining consume side (worker render-time ref resolution) was reassigned to #115 Phase 1 β no residual on this issue. |
| #127 | 2026-06-22 |
Batch PFT throughput plateau β CLOSED: the task_sequence per-sub-task context optimization shipped to prod, compounding with the CPU-limit bump. Two compounding levers landed against the same hot path: (1) the prod worker CPU limit raised 1β2 (applied live earlier this session β removed the 38β47% CPU throttle); (2) this change β the behavior-preserving per-sub-task context optimization. The task_sequence drain rebuilt the template context per sub-task (running_ctx.clone() + 2β4Γ to_template_context() deep-clones + per-block ExecutionContext clones + a fresh context_to_value() per templated field). noetl-tools v3.14.1 (tools#74, squash 9dd9aa6 β release c8656c1, published to crates.io): TemplateEngine::render_value builds the proxied minijinja context ONCE and threads it through the recursion (render_value_with/render_with; minijinja Value is Arc-backed β reuse is a refcount bump), and new build_context_with_overlay(&variables, overlay) builds straight from &variables + a small overlay, skipping the intermediate to_template_context() HashMap deep-clone + per-block ExecutionContext clones in the set/policy paths. Isolated micro-bench (CPU held constant): per-sub-task context cost 2988.9Β΅sβ1147.1Β΅s (β61.6%, 2.6Γ); 407 lib tests + 2 new equivalence pins + clippy clean. noetl-worker v5.40.4 (worker#125, squash 1a10a73 β release 0afbf5c): Cargo.lock pin 3.14β3.14.1, deps-only, no worker source change, clippy clean. Built the prod image via Cloud Build (noetl-worker-rust:0afbf5c) and rolled it onto prod noetl-worker-rust + noetl-worker-system-pool (server stays v3.39.5/.6, worker CPU req 250m/limit 2 kept) β rolling restart clean (pods Ready, 0 crashloop), off-server CQRS cutover stayed healthy (materializer sole-writer projected==acked, lag ~0, command lag 0, executions completing, system-pool WAL index rehydrated per #119). ai-meta repos/toolsβc8656c1 + repos/workerβ0afbf5c. The code-opt + CPU-bump compound: more headroom AND less work per slot on the batch hot path. |
| #123 | 2026-06-22 |
Loop with a non-iterable in: silently wedged (commands=0, RUNNING forever) instead of failing loudly β FIXED: the off-server drive now decodes the error envelope and emits a terminal playbook.failed. evaluate_loop already returns CoreError::Validation("β¦ did not evaluate to an iterable") and the in-process drive already turned that into a terminal playbook.failed β but under the prod-default off-server drive (NOETL_ORCHESTRATE_PLUGIN_DRIVE=true) the system/orchestrate wasm plug-in returns a structured {"error":β¦} envelope instead of an OrchestrationResult, which apply_worker_orchestration couldn't decode β logged a WARN, recorded decode_error, returned Ok(0) β no terminal event β the run sat RUNNING forever (this observability gap produced the false-positive #122 during the #120 2Γ2 repro). Fix: server apply_worker_orchestration decodes the drive ERROR envelope (decode_orchestrate_error) and emits a terminal playbook.failed (metric noetl_orchestrate_drive_total{stage="drive_error"}, structured execution_id), matching the in-process drive β a transient decode miss stays on the benign re-drive path; orchestrate-core prefixes the offending step name onto the existing evaluate_loop error (prefix_loop_step_error). An empty iterable ([]/{}) still short-circuits to next β only a non-iterable errors. noetl-server v3.39.6 (server#258, squash 275b914 β release 7f109a9); 600 server + 135 orchestrate-core tests + clippy clean; kind-validated prod-exact (PLUGIN_DRIVE=true+PUBLISH_ONLY=true+STATE_BUILDER=offserver): absent workload.batch_slots β FAILED with loop step 'process': Loop expression '{{ workload.batch_slots }}' did not evaluate to an iterable (got null), a valid [1,2,3] loop still COMPLETED 3-way, #120/#124 unaffected, 0 pod restarts. Code-only fix (worker stays v5.40.3) β ships to prod on a future server rollout; PROD stays healthy on v3.39.5 + the live off-server cutover (NOT redeployed this session). #127 stays OPEN (perf follow-up, separate). |
| #121 | 2026-06-22 |
Off-server WAL-chain-incomplete loop on system/ executions β FULLY FIXED in two halves (v3.39.4 #256 + v3.39.5 #257). Second half (server#257, squash 54ac277 β release v3.39.5 c421273): #256 alone did not hold β a live-prod re-cutover on v3.39.4 still wedged system/scheduled_cleanup because the system-pool worker drives system execs off-server regardless of the server-side gate (STATE_BUILDER=offserver on the worker β INSERT-not-publish chain leaves a NULL-prev orphan). #257 gates both off-server-drive decision sites in trigger_orchestrator_inner on should_publish(catalog_id) (new pure is_system_path helper + unit test) so system/* execs fall through to server-built run_state; regular publishable execs keep the off-server path (preserves the #256 win). 612 tests + clippy; kind full-gate system/scheduled_cleanup BEFORE 9Γ WAL chain incomplete β AFTER COMPLETED 0 loops, regular test/simple_loop still COMPLETED off-server. Server-only (worker stays v5.40.3). First half (server#256, v3.39.4): two distinct defects. (1) The gate-off claim_command raw in-tx INSERT (and the handle_batch_events gate-off batch INSERT) bypassed the event_write::emit_events/ChainHeads chokepoint, so command.claimed got prev_event_id=NULL and the chain head was never advanced β the next event (command.started) linked back to command.issued, skipping the orphaned claim, so the off-server chain_walk_from hit a NULL-prev non-genesis head β build_spine_to Incomplete. Fired for every system/ playbook because should_publish is false for system executions even under global PUBLISH_ONLY=true. Fixed by stamping prev_event_id via link_batch on both gate-off INSERT paths (same advance-then-write ordering emit_events uses). (2) The actual loop β the stateless off-server drive builds state from the noetl_events WAL, but system-execution events INSERT to noetl.event and never enter the WAL β the worker's WAL build can never complete β __offserver_retry__ re-drive loop. Fixed by gating the off-server WAL drive on should_publish(catalog_id); system/ executions drive server-built (reads noetl.event). noetl-server v3.39.4 (server#256, squash 28b17cb β release 77aaa06); changes confined to src/handlers/events.rs + src/state.rs β server-only rebuild, no worker bump. 598 tests + clippy clean; kind-validated prod-exact (PUBLISH_ONLY=true + STATE_BUILDER=offserver): system/scheduled_cleanup went orphan + 17Γ+ WAL chain incomplete (wedged RUNNING 120s+) β linked chain, 0 loop lines, COMPLETED in 6s; non-system off-server unaffected. Live prod repro: the full off-server cutover on prod (server v3.39.3 + off-server gate) WEDGED β drive applied froze while dispatched_offserver_stateless/offserver_retry climbed in lockstep to ~389, system pool logged the WAL-chain-incomplete no-op for multiple execs (incl. prod's own 327130580493803520), all prev_event_id=NULL; the armed revert (PUBLISH_ONLY=false+STATE_BUILDER=server) recovered cleanly β this is what reopened #121 and motivated the #257 second half above. PROD GKE default (STATE_BUILDER=server) + all defaults untouched. #123/#127 stay OPEN (separate). |
| #125 | 2026-06-21 |
task_sequence runner implemented only do: fail, silently ignoring do: jump/break/retry β FIXED: noetl-tools v3.14.0 honours all control-flow variants. The Rust task_sequence tool parsed the do: directive but only matched "fail", so do: jump/break/retry were silently ignored β each slot ran once and returned, leaving batches in progress with no retry/loop/jump path (pft_flow_test produced 16 done / rest pending, patient loss). Fix in tools#73 (noetl-tools v3.14.0, squash 62d0948 β release 638c3c6): honour do: jump/to: (loop to named step), do: break (exit the sequence), do: retry (with attempts/backoff + infinite-jump guard). Worker adopts via worker#124 (v5.40.3). Together with #126, makes the full 10Γ1000 batch pft_flow_test pass end-to-end on kind (zero patient loss, invariants clean). PROD + defaults untouched. |
| #126 | 2026-06-21 |
Rust http tool nested parsed response body under data.body instead of data β FIXED: noetl-tools v3.13.1 restores the Python-era output.data.data contract. The Rust http tool exposed the parsed response body under data.body, but playbooks written against the Python-era contract read output.data.data (or equivalently output.data for the body). pft_flow_test's save_batch step called jsonb_to_recordset on a non-array value β error. Fix in tools#72 (noetl-tools v3.13.1, squash 86f0216 β release 8dd0e1f): expose the parsed body under data again; keep body as a back-compat alias. PROD + defaults untouched. |
| #124 | 2026-06-21 |
Distributed task_sequence forward set:/sibling bindings rendered empty (data not propagated between sub-tasks) β FIXED: defer rendering of sub-task templates whose vars aren't resolvable at command-build time. Inside a multi-tool (task_sequence) step, a later sub-task's templates that reference a value a prior sub-task produces at runtime β a forward set:, a policy-rule set:, or a sibling result β were rendered to empty at command-build time, before the worker's per-sub-task binding ran. orchestrate-core/src/commands.rs::render_pipeline_config preserved set/args/spec/command verbatim but rendered every other sub-task field (url/params/method/β¦) against the step-entry context under UndefinedBehavior::Chainable, so runtime-only refs ({{ iter.* }}, sibling labels) silently collapsed to empty β concretely pft_flow_test's batch path: claim_batch writes iter.data_type via policy set:, then fetch_batch's url: β¦/api/v1/pft/batch/{{ iter.data_type }} pre-rendered to β¦/batch/ β 404 β 0 rows. Fix: new TemplateRenderer::render_value_deferring_unresolved renders only templates whose variable paths all resolve in the build-time context; any template referencing an unresolved path is preserved verbatim so the worker re-renders it against the per-sub-task running context (a superset). noetl-server v3.39.3 (server#255, 365d3be); cargo test 134/134 + clippy clean; kind-verified (fetch_batch now hits the real per-type URLs). PROD + defaults untouched. #121/#123/#125/#126 stay OPEN (separate). |
| #120 | 2026-06-21 |
Reduce barrier deadlocked (commands=0) on open/asymmetric loop joins β FIXED: a runtime liveness filter in the orchestrate-core reduce barrier. A back-edge U β T whose forward return path from T is absent (T does NOT forward-reach U) was counted as a genuine fan-in parent of T, so dispatch of T deferred forever (U never runs on the taken path; pft_flow_test setup_facility_work stalled commands=0 for 4.5 min). The barrier now blocks on an upstream only if it is live on the current path (done/skipped, entered/in-flight, or reachable from an active not-yet-terminal step); build_incoming_arcs unchanged (an open back-edge is still a genuine static fan-in). Affects the in-server and off-server drives identically (shared orchestrate-core); closed loops + genuine reduces unaffected. noetl-server v3.39.2 (server#254, 28e8950); new unit test test_open_loop_back_edge_does_not_block_dispatch; 133/133 + clippy clean; kind-validated (post-fix 2Γ2 off-server/gate matrix all COMPLETE, fanout_reduce/pagination/loop green). PROD + defaults untouched. |
| #119 | 2026-06-20 |
Off-server WAL state-builder drain stranded executions after a worker restart β FIXED: rebuild the in-memory index from the retained noetl_events WAL on every boot. The authoritative drain used a durable noetl_state_builder consumer whose cursor persists across restarts while the in-memory WalEventIndex rebuilds empty β the cursor outran the fresh index β build_spine_to(expected_head) permanently Incomplete β off-server execs looped offserver_retry and never completed (this is what hid the #118 symptom). Fix (worker-only, inside NOETL_STATE_BUILDER=offserver; PROD runs the in-server drive so untouched): the drain defaults to an ephemeral DeliverPolicy::All consumer rebuilding the full index from the retained WAL on every boot (no persisted cursor to outrun; also correct for >1 worker pod); instant revert NOETL_STATE_BUILDER_DURABLE=1; proof = index rehydratedβ¦ log + new noetl_worker_state_builder_indexed_executions gauge; never reintroduces a noetl.event scan. noetl-worker v5.40.2 (worker#123, 48b0bde). Gate-ON kind: forced mid-flight pod delete --force β new pod index rehydrated β¦ indexed_executions=17 wal_events=200; single-replica 6/6 stress + multi-replica 21 execs COMPLETE, zero scans. |
| #118 | 2026-06-20 |
Single-replica off-server terminal-finalize chain fork (a duplicate playbook.completed orphaned the chain as a NULL-prev_event_id 2nd root) β FIXED: a bounded FinalizedGuard (exactly-one-terminal-per-execution) suppresses the duplicate at emit_events before the chain linker. A straggler drive on a materializer-lagged single replica re-drove off the lagged WAL (state not terminal yet) and emitted a 2nd terminal that linked to the now-evicted head β 2 roots + a benign event-scan. The guard makes the first terminal win; gate-off byte-identical (a duplicate never occurs on the synchronous in-process drive); metric noetl_terminal_dedup_total{suppressed}; rig gains a HARD terminals==1 assertion. Absent under multi-replica execution-affinity (#116 serializes finalize to the owner). noetl-server v3.39.1 (server#253, c5f8cb2) + e2e (e2e#73, fe97d92). Gate-ON kind (unblocked by #119): single-replica 6/6 stress iterations / ~126 execs every chain roots=1(incl. terminal)/terminals=1/zero-scan; multi-replica 21 execs clean. |
| #117 | 2026-06-20 | Off-server from_events spine ordered by event_id broke fan-in under a chain-orderβ id-order inversion (high-concurrency fan-out reduce wedge) β FIXED: order the spine by the prev_event_id chain walk + walk from the real tip (expected_head). worker v5.40.1 (worker#122, baeae78) + e2e (#72, cdf1768). 2-replica affinity gate-ON stress 6/6 iterations / 108 execs COMPLETE; 15 real id-inversions all fired the fan-in reduce. The residual single-replica terminal-finalize chain-linking race is now FIXED as #118. |
| #113 | 2026-06-19 |
Worker-driven orchestrate drive stalled when the drive result / accumulated context crossed the inline budget β fixed at the source by #115 Phase 1 (references-in-state consume side). All 9 of 9 stalled core fixtures now reach playbook.completed gate-ON (PUBLISH_ONLY + off-server drive + materializer sole-writer), server v3.30.0 + worker v5.36.0: output_select / storage_tiers / lease_expiry / pipeline_heavy_payload / save_edge_cases / large_result_extraction / http_to_postgres_{direct,simple,bulk_python}. Max next-command context across the 9 = 412KB (save_edge_cases); 0 __orchestrate__ event rows on every run; materializer lag = 0. The #113 decode fix (server#241 v3.29.4) + #114 offload cap (server#242 v3.29.5) stay as safety nets; the growth that pushed past budget is gone (worker selective resolve keeps foreign bulk out of the render; refs_in_state-true keeps the drive state + command.issued bounded). Landed via worker#117 + server#243. |
| #114 | 2026-06-19 |
Off-server drive oversized command.issued (full upstream context embedded) β resolved by #115 Phase 1 (refs_in_state default true). With the worker selective consume side in place, refs_in_state defaults true so the next command's render_context carries {reference, extracted} (small) instead of the full upstream payload β command.issued no longer approaches NATS max_payload. The #114 offload safety cap (maybe_offload_command_context, 512KB) stays as belt-and-suspenders. Verified gate-ON: all 4 of this issue's fixtures COMPLETE; max next-command context 412KB across the full 9. Landed via server#243 + worker#117. |
| #112 | 2026-06-18 |
Worker /dev/shm SIGBUS β k8s default 64 MiB tmpfs vs the 256 MiB Arrow IPC cache budget. Every worker process (Rust noetl-worker + legacy Python worker) allocates an Arrow IPC shared-memory cache at init (NOETL_IPC_CACHE_BUDGET_BYTES, default 256 MB) backed by POSIX shm on /dev/shm; the k8s container-runtime default /dev/shm is a 64 MiB tmpfs, so under shm-heavy load the cache writes past 64 MiB, the store page-faults against the full tmpfs, and the worker dies with SIGBUS (exit 135) and crash-loops. Surfaced during #103 CQRS kind validation on the system pool; a transient live fix was reverted, leaving the committed manifests latent. Fix (ops#193) gives every worker deployment a memory-backed /dev/shm (emptyDir medium: Memory, sizeLimit: 320Mi > budget), pins NOETL_IPC_CACHE_BUDGET_BYTES=268435456 next to the sizeLimit so the two can't drift, and raises the memory limit to 768Mi (tmpfs is charged to the pod cgroup). Applied to all 7 worker manifests (system / shared-rust / subscription / subscription-runtime / Python-cpu + 2 prod variants). Kind-validated on the system pool: reproduced SIGBUS (exit 135) on the 64 MiB tmpfs β after fix /dev/shm is 320 MiB and a full 256 MiB write completes (exit 0, peak 256M/320M) with the pod healthy (restarts=0, no OOM); cluster restored to baseline. ai-meta β ops f4df4c1 + wiki worker deployment-specification. |
| #110 | 2026-06-18 |
Retired the in-server orchestrate shadow + the wasmtime server dependency. The separable server-slimming follow-up to #108 (closed): the in-server shadow was the slice-4 cutover-confidence harness (ran the system/orchestrate plug-in inside the server via an embedded wasmtime host, diffed 529/0 against the in-process drive). With the worker-driven drive default-on + proven, the live drive uses the worker's wasmtime host β never the server's β so the shadow was dead weight. server#234 removed orchestrate_shadow.rs, the orchestrate-shadow cargo feature + the optional wasmtime dep (the cranelift/wasmtime tree β ~1000 Cargo.lock lines β fell out; cargo tree -i wasmtime now matches nothing), the trigger_orchestrator_inner shadow hook, the main.rs boot loader, the NOETL_ORCHESTRATE_PLUGIN_SHADOW config field, and the noetl_orchestrate_shadow_total metric. Kept run_state + NOETL_ORCHESTRATE_PLUGIN_DRIVE (default true). refactor: = no version bump (stays v3.28.0). Kind smoke on a 4-page cursor loop, both drive modes: COMPLETED with __orchestrate__ rows in noetl.event = 0 (worker-driven: 10 drive cmds on the system pool, dispatched=applied=10, event_suppressed=30); shadow metric gone from /metrics. ai-meta β server f3043c9 + wiki deployment-specification. See Umbrella-System-Pool-Design. |
| #108 | 2026-06-18 |
π― The orchestrator drive runs OFF-SERVER on the system pool, and it's now the DEFAULT. Step 2 of the dissolution (#107) β the server's brain moved onto the worker pool as the system/orchestrate WASM plug-in. Arc: slices 1β3 (off-server drive, server#229) β 4a (cursor+fan-out validated) β 4b + follow-up a (the __orchestrate__ meta-command touches noetl.event zero times, server#230/#231) β (b) system-pool isolation (server#232 + worker#114 + ops#191) β (c) the deliberate default-flip (server#233, v3.28.0): NOETL_ORCHESTRATE_PLUGIN_DRIVE defaults true. Gated on a scale soak (kind): a 694-drive cursor+fan-out run COMPLETED with __orchestrate__ rows in noetl.event = 0 and all 694 drives claimed on the system pool (shared pool got only the real steps); the default-on path (no env var) reproduced the identical shape (361 drives, isolated, 0 burst); 15/15 regression fixtures green; revert (=false β in-process drive) verified. ai-meta β server 80cc0e6 + worker 437b0be. See Umbrella-System-Pool-Design. |
| #109 | 2026-06-17 |
Event-ABI round β orchestrator + evaluate moved into the wasm core (#108 final slices). Slice 3 (server#223) relocated orchestrator/evaluate to noetl-orchestrate-core; the whole drive (renderer, playbook, commands, evaluator, state, orchestrator switch) now compiles native + wasm32-unknown-unknown. evaluate reads the pure core::event::Event; db::Event converts at the trigger_orchestrator boundary. 122 core + 565 server tests green, 0 WASI imports, kind PFT 10Γ1000 clean (0 errors, 0 restarts). Plug-in round (data-plane ABI + command_emit + scheduler + shadow-diff) tracked under #108. |
| #106 | 2026-06-17 |
EventEnvelope rejects timezone-less timestamps β blocked the CQRS materializer. The tailer publishes to_jsonb(noetl.event row) and created_at is timestamp WITHOUT time zone, so the timestamp had no offset; EventEnvelope.timestamp: Option<DateTime<Utc>> rejected it with the misleading premature end of input. Fixed (server#217) with a flexible deserialize_with (RFC3339 + tz-lessβUTC). Validated live: materializer projects {projected:0, duplicates:20} = byte-identical. The #103 shadow gate is green. |
| #105 | 2026-06-17 |
Plug-in compilation & hot-reload: WASM-compiled system playbooks + managed library. The WASM plug-in runtime is complete + fully live-proven on kind: dispatch (tool_kind: "wasm" β wasmtime host β digest from registry β run), capability flush (object_put β noetl.object_store), real data flow (inputβargs, worker#110), and hot-replace (worker#112) β republishing the same path@version swaps the running pool's behavior with 0 restarts. Landed across worker#93/#95/#97/#99/#101/#103/#105/#107/#108/#110/#112 + server#210/#212/#214 + tools#68 + docs#181. Remaining (executor: author sugar; compiled materialiser port) deferred to #104/#103. See Umbrella-WASM-Plugin-Compilation. |
| #100 | 2026-06-15 |
Cursor/claim loop mode: loop.spec.mode: cursor in the Rust orchestrator. noetl-server v3.8.0 (server#196) cursor loop engine + output namespace; noetl-tools v3.10.1 (tools#66) postgres -- comment splitter fix; worker#88 dep bump. test_pft_flow_v2 all_passed:true, 5/5 per data type on kind against the throttling/error-injecting paginated-api. See Umbrella-Cursor-Loop-Mode. |
| #99 | 2026-06-14 |
Transfer tool: SnowflakeβPostgres both directions, with credential-alias resolution. Both transfer arms implemented in noetl-tools v3.10.0 (tools#65); worker v5.22.0 (worker#87) pre-resolves source.auth/target.auth aliases; SFβPG coerces string cells via $n::text::<udt> + reformats Snowflake-epoch timestamps to RFC3339; PGβSF generates SQL-escaped INSERTs. e2e fixture migrated (e2e#58). Kind-validated bidirectionally against the live sf_test account: every step COMPLETED, real types preserved. |
| #90 | 2026-06-12 |
Subscription / listener tool (RFC) β all 7 phases shipped + live-proven. Bounded-drain tool (Mode A) β kind: Subscription continuous runtime (Mode B) + header-directive engine β gateway push-ingress (Mode C) + auth-gated directive trust β store-and-forward spool + circuit breaker β out-of-cluster Cloud Run + gcs spool β CLI local noetl subscribe β Phase 7 scale hardening. Final phase: server v3.5.0 (server#189) POST /api/execute/batch (NβN, partial-failure contained) + opt-in exactly-once dedup window (noetl.subscription_dedup, bounded-by-age, race-safe, default off); worker v5.19.0 (worker#79) batch dispatch + dedup opt-in + per-subscription rate limits (token-bucket fetch-side backpressure β source keeps backlog, no loss); ops (ops#176) + e2e (e2e#48); no tools change. Live on kind: batch 12β12 COMPLETED on the subscription pool + per-message traceparent; dedup duplicateβ1 execution + subscription.message.deduplicated; rate-limit engaged + 10/10 β executions (no loss). Refinement follow-ups tracked: #91β#94 + tools#57. ai-meta β server 7b217d8 + worker 7531f4a + ops 6db69b9 + e2e 203593b. |
| #89 | 2026-06-11 |
JSON null re-injected via {{ step }} serialized as JS undefined β fixed in the server template renderer β the cursor pagination fixture's terminal page returns next_cursor: null; re-injecting the whole {{ fetch_page }} envelope into the next step's input rendered that field as the bare token undefined, producing invalid JSON. render_to_value then failed serde_json::from_str and returned the entire envelope as a raw string, so the consuming Python step got response as a str and crashed ('str' object has no attribute 'get'). Root cause was the server, not the worker (the issue's hypothesis): src/template/jinja.rs::json_value_to_minijinja maps JSON nullβValue::UNDEFINED and minijinja's map repr emits undefined; the server's renderer was a divergent copy missing the | tojson retry the noetl-tools engine already had. Fix (server#177, v3.0.6) adds that retry β a lone {{ expr }} rendering container-shaped-but-invalid JSON re-renders with | tojson; minijinja_to_json maps undefined/none β JSON null. 5 new regression tests; 619 lib + 8 parity green. Kind-validated: cursor walks all 4 pages, terminal next_cursor: null handled, validate_results collects 35 (first_id=1, last_id=35, success) β matching offset. ai-meta β server 8e17fbe. |
| #88 | 2026-06-10 |
e2e offset/cursor pagination fixtures read response.body.* not response.data.* β the Rust http tool nests the parsed JSON payload under body ({{ fetch_page }} β {body, headers, status_code}), so the fixtures' response.get('data', {}) resolved to {}, has_more/next_cursor defaulted falsy, and the loop exited after page 1 even with the post-#85 loop machinery correct. Fixed both check_pagination steps to response.get('body', {}) via e2e#40. Kind-validated against the live paginated-api test-server (Rust server/worker :dev): offset walks 0β10β20β30, has_more T/T/T/F, users 10/10/10/5, validate_results success 35 (first_id=1, last_id=35), playbook.completed COMPLETED. cursor path-fix correct + walks all 4 pages (Mg==βMw==βNA==βnull, 35 events fetched) but final collection blocked by a distinct worker bug β filed #89 (terminal next_cursor: null serialized as JS undefined). Other pagination fixtures (retry/max_iterations/pipeline*/loop_with_pagination) share the same envelope-key assumption over /api/v1/assessments|flaky ({data, paging}) β flagged, left for follow-up. ai-meta β e2e 72a7525. |
| #85 | 2026-06-10 |
Workflow-arc loops now advance across iterations + terminate cleanly β built on the dispatch-guard re-entry layer, two coupled orchestrator fixes via server#176 (v3.0.5). (1) Durable event-sourced loop-ctx propagation: step-level set: ctx.* loop variables were recomputed per pass and reverted to the workload default (loop thrashed 0,0,1,0,1,2,β¦); root cause was start's initializer set re-firing every pass in random HashMap order against check_pagination's advancing set. Fix persists each completion's rendered set: as a ctx.updated event (latest-wins fold + build_context overlay), emitted once per completion keyed by the stable completion event_id (not Utc::now()-fallback completed_at). (2) Loop-exit hang: the exit branch was marked step.skipped on a loop-body-completion pass (recency-based branch-point detector missed it), turning it terminal so the exit dispatch was suppressed; fixed with a structural loop-branch-point test. 614 lib tests (6 new; 2 verified to fail without their guard); clippy-clean. Kind-validated: counter loop advances 0β1β2β3 + terminates; real-http offset pagination advances 4 pages collecting 35. (Separate finding, filed follow-up: the e2e offset/cursor fixtures read response.data.users but the Rust http tool nests it at response.body.users.) ai-meta β server e519fdc. |
| #87 | 2026-06-10 |
Multi-tool step: a later sub-tool can now reference an earlier sibling's output β in a tool: [list] step, each sub-tool's result was stored for the aggregated output but never injected into the running context, so {{ <label>.<field> }} rendered empty (masked in quoted positions; a syntax error at or near "," in unquoted numeric SQL positions, e.g. save_edge_cases test_large_payload). Fixed via tools#48 (v3.1.1): inject each sub-tool's result under its label (with a synthetic .data self-ref) so later siblings resolve it. Worker adopts 3.1.1 via worker#69. Kind-validated: save_edge_cases test_large_payload β record_count = 100 (no syntax error), save_delegation_test clean. ai-meta β tools 76f942a + worker b97f642. |
| #83 | 2026-06-10 |
Orchestrator fan-in barrier deadlocked workflow loops β build_incoming_arcs counted a loop back-edge (check_pagination β fetch_page) as an upstream so the barrier deferred the loop head forever. Fixed via server#175 (v3.0.4): exclude back-edges via a new forward_reachable helper (genuine fan-in unaffected). Kind-validated; fanout_reduce green. ai-meta β server 480ba72. |
| #84 | 2026-06-10 |
Orchestrator never populated event.name β loop.done arc gates always skipped β when: {{ event.name == "loop.done" }} (10+ fixtures) never matched, so in-step loop: steps hung after completion. Fixed via server#175 (v3.0.4): inject event.name = "loop.done" into a completed loop step's next-arc context. Kind-validated (test_pagination_basic completes). ai-meta β server 480ba72. |
| #86 | 2026-06-10 |
e2e fixtures: duckdb tool field is command/query, not commands β commands: (plural) failed pre-dispatch (missing field 'query' / malformed tool config). Renamed across 4 storage/gcs fixtures via e2e#39; save_all_storage_types green. ai-meta β e2e b0a5c85. |
| #78 | 2026-06-10 |
noetl-worker: pre-dispatch errors now emit terminal call.error instead of hanging β credential-alias resolution + tool-config deserialization failures used to ?-propagate out of execute_with_server_url; the dispatch loop only logged them, so the execution sat at command.started forever. Fixed via worker#68 (v5.15.1): typed CredentialResolutionError (terminal AliasNotFound/Invalid vs retryable Transient) + CredentialHttpError carrying the HTTP status so classify_fetch_error decides retryability by code (terminal: 404/400/401/403/500; retryable: 408/429/502/503/504 + transport), and handle_predispatch_failure emits call.error + command.failed. Diagnosis correction: the live pg_noetl_k8s repro is an HTTP 500 "Decryption failed: aead::Error", not a 404 (the worker has no /api/keychain/ call). Kind-val GREEN: test/postgres now β call.error/command.failed/playbook.failed (no hang); hello_world still completes. ai-meta β worker 99e2c66. |
| #80 | 2026-06-10 |
container_callback chain green end to end β fixing the watcher's missing curl (retired bitnami/kubectl β alpine/k8s:1.30.3, ops#168) surfaced two more layered bugs. Server: container-callback call.done insert targeted a non-existent attempt column β HTTP 500; fixed to the deployed noetl.event schema (server#173, v3.0.3). OOM path: the watcher only read Job-level conditions so failed_oom could never fire β added pod-level OOMKilledβfailed_oom / ImagePullBackOffβfailed_image_pull classification + RFC3339 completed_at (bare now was returning HTTP 422) (ops#168); and the e2e fixture's calloc-lazy bytes(40MiB) never dirtied pages so it never OOM'd (e2e#38). kind_validate_container_callback.sh both probes GREEN β happy_path β succeeded, oom β failed_oom. ai-meta β ops cacc513 + server 5d2cf58 + e2e 6aaf06e. |
| #79 | 2026-06-10 |
e2e kind-val runner scripts updated to current noetl CLI surface β both scripts/kind_validate_*.sh aborted on unrecognized subcommand 'playbook'; the validation logic + event taxonomy were intact, only the invocation layer drifted. Fixed via e2e#37: register playbook / exec <catalog-path> --runtime distributed --json / status --json / event log via noetl query (.result, order by event_id); fail-fast CLI-surface guard. fanout_reduce PASS start-to-finish on kind (server-rust v3.0.1); container_callback drives cleanly and stops at the watcher curl gap (#80). ai-meta β e2e a3594b3; wiki Kind-Val Runners. |
| #82 | 2026-06-10 |
GUI: credential View/Edit recovered for pre-wallet (legacy-encrypted) records β Secrets Wallet (#61) made credential storage forward-only, so pre-wallet records 500 on decrypt and the GUI View/Edit flow dead-ended on a generic toast. Fixed via gui#36: View explains the cause + points to Edit; Edit reopens with the list-row metadata + a warning banner so re-saving re-seals the record under the current wallet. ai-meta β gui 8cacc9e (v1.11.1). |
| #81 | 2026-06-10 |
Container tool unusable: ToolSpec.command (String) vs ContainerConfig.command (Vec) type contradiction β landed via server#172 (v3.0.2). ToolSpec.command Option<String> β Option<serde_json::Value> so the container tool's array command decodes server-side + passes through to the worker's Vec<String> (scalar stays a JSON string for shell/db tools); ToolCall::from_spec forwards verbatim. 2 regression tests; clippy clean. Kind-val GREEN: server accepts command: ["/bin/sh","-c"], worker creates the K8s Job, Job reaches Complete 1/1. Chain counter-bump validation stays gated on #79/#43. |
| #77 | 2026-06-09 |
Explicit input:/set: forward-only data binding β BREAKING v3.0.0 across noetl-tools + noetl-server. All 5 PRs merged: tools#45 (v3.0.0) + server#169 (v3.0.0) + e2e#35 (13 fixtures migrated) + cli#57 (v4.10.0, executor 0.5.0) + worker#66 (dep bump). Kind-val GREEN. |
| #76 | 2026-06-08 | Sequential-mode iterator dispatch β LoopMode enum (Sequential default / Parallel), StepInfo.iterations_dispatched guard. Landed via noetl/server#166 (v2.62.0). First Claude-direct Rust PR under agents/rules/handoff-routing.md. Kind-val GREEN: test/loop COMPLETED 5/5 + iterator_save_test COMPLETED 4 steps. |
| #70 | 2026-06-08 | noetl-server missing PUT /api/result/<execution_id> endpoint β landed via noetl/server#160 (v2.58.0). Durable result-store endpoints (PUT + GET /api/result/<eid>/<step>/<ref>) ported to the Rust server. Kind-val GREEN: output_select_test reached playbook.completed with test_result: "PASSED". |
| #69 | 2026-06-08 | noetl-worker: over-budget call.done returned reference-only envelope, missing inline _ref for downstream {{ step._ref }} templates. Landed via noetl/worker#63 (v5.15.0): build_call_done_result's durable-success branch now embeds context: { data: { _ref: <noetl://...> } } alongside the existing reference block. Kind re-val pending noetl/ai-meta#70 (server-side PUT /api/result/<eid> endpoint missing β falls back to degraded shm-only path where there's no noetl:// URI to embed). |
| #68 | 2026-06-08 | noetl-tools: ArtifactTool config required input but server pipeline emitted args (the post-#56 normalized name) β landed via noetl/tools#40 (v2.23.1) + worker dep bump via noetl/worker#62. One-line #[serde(alias = "args")] on ArtifactConfig.input accepts both shapes. Re-val surfaced a downstream _ref/output_select gap β filed as noetl/ai-meta#69. |
| #67 | 2026-06-08 | Rust orchestrator hangs on mode: exclusive routing β untaken sibling never emits step.skipped, R4 fan-in barrier deadlocks. Landed via noetl/server#159 (v2.57.2). Three-part fix: (1) evaluator::evaluate_next_transitions surfaces unmatched siblings as not_matched_with_target; (2) orchestrator::process_in_progress two-pass emit-skipped-then-dispatch (HashMap-order-independent); (3) +2 unit tests (lib 568/0/0). Kind-val GREEN β comprehensive_test.yaml reached playbook.completed in ~4s (was hanging forever pre-fix). |
| #66 | 2026-06-07 | Rust orchestrator: cross-step {{ step.data }} template resolves to None β landed via noetl/server#158 (v2.57.1). WorkflowState::build_context injects a self-referencing .data key on extracted user_data, guarded so the task_sequence flatten path's existing .data stays intact. 2 new unit tests + 566/0/0 lib. Surfaced by #65 kind-val; concrete repro kind execution 322087210360770560. |
| #65 | 2026-06-07 | noetl-tools: external python script loaders (file/gcs/http source types) + legacy main() function convention β landed via tools#38 (v2.22.0) + tools#39 (v2.23.0); kind-val GREEN on the live worker (execution 322087210360770560 reached playbook.completed in ~6s; loaded main(name, count) returned the expected payload). noetl/worker#61 OPEN+mergeable to bump the worker pin. Surfaced finding: noetl/ai-meta#66. |
| #43 | 2026-06-07 | R-3 Phase C-2: container tool kind β design callback pattern for K8s Job dispatch. All four Rust rounds shipped (1 k8s-watcher ops@8892043 / 2 callback endpoint server v2.48.0 / 3 Tool::Container tools v2.21.0 / 5 kind-val rig e2e@17de21d). Round 4 (Python parity) parked per Rust-only direction. Worker-side pending_callback adoption is a coordinated follow-up. |
| #64 | 2026-06-07 | noetl-tools: artifact tool kind missing from Rust registry β landed via tools#35 v2.20.0 (thin ArtifactTool adapter translating the Python-era YAML shape into a ResultFetchTool call) |
| #61 | 2026-06-07 | Secrets Wallet (Rust): envelope encryption + KMS/secret-provider plugins + sealed worker delivery + distributed multi-region resolution β all named phases + 6d.X cloud dynamic providers shipped; umbrella feature-complete |
| #54 | 2026-06-06 | Phase F R5 β regression + e2e validation under sharded topology β closed at the umbrella level (Tier 1 + Tier 3 + Tier 4 e2e all GREEN on the Rust-only stack; subsequent regression findings filed as their own ai-task issues β #62/#63/#64/#65/#66 all now also closed) |
| #62 | 2026-06-05 | noetl-server: /api/executions list query candidate-first rewrite + status-drift fix (server#99 v2.28.1) β 6.5 s β 0.015 s (~430Γ) |
| #63 | 2026-06-05 | noetl-tools: python tool accepts nested script.source.code (inline) β tools#33 v2.19.3 + worker#54 adoption, test_script_loading kind-validated; external loaders split to #65 |
| #60 | 2026-06-04 | Rust orchestrator template context doesn't expose step data for next.arcs / step.when |
| #59 | 2026-06-04 | Rust orchestrator doesn't resolve tool.kind:workbook references to inline actions |
| #58 | 2026-06-04 | Rust orchestrator doesn't emit playbook.failed on command.failed β executions stall |
| #57 | 2026-06-04 | Rust server rejects flat (name-as-field) pipeline shape in v10 playbook YAML |
| #56 | 2026-06-04 | Canonical v10 playbook workload + input alias unrecognized on Rust stack |
| #55 | 2026-06-04 | Rust server EventEmitRequest.execution_id wire-type drift blocks worker traffic |
| #53 | 2026-06-04 | Rust worker β Rust server e2e compatibility (Phase D R3b/R3c terminal completion gap) |
| #52 | 2026-06-03 | noetl-tools: add js_consume operation to nats tool kind |
| #51 | 2026-06-03 | Fix system/outbox_publisher.yaml auth block to use AuthResolver pattern |
| #50 | 2026-06-03 | Phase 2.a β system/outbox_publisher playbook + routing + auth wiring |
See the Repo Map page for the full submodule inventory.
Quick view of the production repos and their current versions
(2026-07-30 β prod runs on GKE Autopilot noetl-prod-autopilot,
us-central1, project shastaratech-noetl-prod, migrated off
noetl-demo-19700101 on 2026-07-26; since 2026-07-27 the command bus
is EHDB, not NATS β NATS remains installed only as the rollback path; since 2026-07-30 the bus is also ~2.4Γ faster than NATS on server v3.58.3 / worker v5.81.3):
| Repo | Role | Version | Recent |
|---|---|---|---|
noetl/server |
Rust control plane |
v3.123.2 in prod β verified 2026-10-07 from the pod: noetl_server_build_info{version="3.123.2"}, digest 49eb47fc, 1/1, restarts 0, env 67. β This cell said "v3.123.0 in prod, digest 091ff47f" until the 2026-10-07 sweep β a stale version assertion of exactly the class Rule 0a exists to catch. Read the version from the pod digest, never from this table. |
π’ Scoped tier repair endpoint (2026-09-04) β POST /api/ehdb/repair/executions/{id} (server#399) re-mirrors only the event IDs the cross-store comparator reports absent. Inert: no caller, no sampler, not run against prod. Both comparisons under ParityRecording::Inspect so a repair does not write the counter its own alert reads (#264). Prior: π’ T5-readiness deploy (2026-07-31) β EHDB publish retry widened to a 10 s wall-clock deadline so it spans a writer pod restart (server#291); verified live at 60/60 with 0 publish errors across a mid-burst writer restart. Prior: β‘ v3.58.3 β the EHDB command bus is now ~2.4Γ faster than NATS in PROD (2026-07-30) β server#289 drops the publish mutex held across the writer RTT (pipelined publish) and server#290 retries a publish across a writer restart. Prod: dispatch p50 138.5 / p99 181.0 ms vs the 338 ms NATS baseline (#205 CLOSED), writer-restart 38/38 with 0 loss (#208 CLOSED). Held: server#291 widens that retry to a 10 s deadline. Prior: π L1 T4 β server publishes commands to the EHDB feed in PROD (2026-07-27) β NOETL_COMMAND_BUS=ehdb live on shastaratech prod; server NATS_pub=0, 80/80 0-loss 0-dup across the flip + soak. v3.58.1 pins the #203 writer-assigned-ordering-key fix (ehdb#300). Prior: π #169 Auth0 JWT signature verification β SHADOW canary LIVE on prod (2026-07-04, server v3.50.0βv3.52.0, single replica) β handlers::auth_verify fetches the tenant JWKS + verifies the RS256 signature + iss/exp/nbf (aud when set) behind NOETL_AUTH_VERIFY_SIGNATURE (tri-state; shadow = verify + meter + always allow, cannot lock anyone out). Awaiting one real-login outcome=success metric, then audience + enforce (gated; single replica = all-or-nothing). Handover Handover-169-JWT (server#276, #169). Prior: π§ #171 orphaned-command guardrail LIVE on prod (2026-07-02) β server-side flag-gated sweep terminates dead-worker-orphaned RUNNING execs append-only (playbook.failed) once the owner is absent/stale in noetl.runtime; released ghcrβAR server-rust:v3.50.0, flag NOETL_ORPHAN_SWEEP_ENABLED=true (server#271); prod-proved a killed-worker orphan (mid-call_duffel_offers, no NATS redelivery) swept to FAILED at grace(180s)+one tick, {candidate=1,terminated=1} idempotent, 0 collateral, 0 restarts; rollback = flag off β v3.49.0. Prior: π #168 synchronous auth fast-path LIVE on prod (v3.49.0, 2026-07-01) β session-validate + login run in-process off the slow drive (NOETL_AUTH_SYNC / NOETL_AUTHZ_SYNC). Prior: β‘ #156 off-server drive tail-attach DEPLOYED on prod (2026-06-29) β the server attaches the just-published per-execution event tail (ChainTails ring β tail_events) to the __offserver_build__ dispatch so worker state rehydration is O(execution) not O(global event-log), removing the drain-lag 8s reconcile cliff; flag-gated NOETL_OFFSERVER_ATTACH_TAIL default off (server#268, v3.48.0 ddca5c4; rolled flag-off behavior-neutral then flipped on, 0 restarts, turns identical correct output, 1-step rollback). Prior: π #104 result_store dual-write RETIRED on prod (2026-06-27) β NOETL_RESULT_STORE_DUAL_WRITE=false flips PUT /api/result/{eid} to mint_ref_only() (byte-identical ref, skips the noetl.result_store INSERT; over-budget results served from the #104 tier by URN). Deployed v3.47.0 @sha256:140066d9β¦ behavior-neutral, then flipped on prod: result_store writes stopped (dual_write_skipped_total climbs, put_total/dual_write_total flat), over-budget execs COMPLETE, sole-writer balanced 142==142==142 / lag-0 / dup-0, 0 restarts; existing result_store data frozen, not deleted; revert one step = flagβtrue (server#267, v3.47.0 bd831e6; ai-meta pointer this change set). Prior: π G3 registry MERGED + RELEASED (review-only) β versioned model/dataset/eval registry on the #104 object tier, flag-gated NOETL_REGISTRY_ENABLED default-off (server#266, v3.46.0 1d86464; ai-meta pointer 2e79af6). Prior: π #104 β WI/ADC GCS auth MERGED + ROLLED TO PROD as KSA noetl-server-rust (3-mode none/static/adc auth matrix via gcp_auth; auth=autoβadc on real GCS, mints+auto-refreshes a short-lived WI/ADC OAuth token for the PAP-enforced bucket) (server#265, v3.45.0 fad5d8a; prod server @sha256:d3cbf1adβ¦ runs as the WI-bound KSA with result-tier GCS ENV applied β all tier-enable flags OFF β tier inert, off-server cutover stayed sole-writer/lag-0/never-scan, 0 restarts; ready for staged BβCβD enablement). Prior: π¦ #104 Phase F MERGED β result-tier GC sweeper (NOETL_RESULT_TIER_GC, default off): conservative dry-run-first sweep reclaiming only provably-dead tier objects, never deletes a live-referenced one (server#264, v3.44.0 341b614; default-off byte-identical, inert in prod). Prior: π¦ #104 Phase D MERGED β mint-authoritative flag + result_store dual-write counter (NOETL_RESULT_MINT_AUTHORITATIVE, default off; the result_store PUT records each write on noetl_result_store_dual_write_total as the reversible dual-write fallback leg) (server#263, v3.43.0 6f6b9ef; no Cargo.toml change, resolves noetl-locator 0.1.1 from the registry; default-off byte-identical, inert in prod). Prior: π¦ #104 Phase C β GCS object backend + cell-endpoint registry + GET /api/internal/cells (the server side of the resolve-by-URN read path; default-off, inert in prod until a future rollout) (server#262, v3.42.0 c2d5ca9; no Cargo.toml change, resolves noetl-locator 0.1.1 from the registry). Prior: π¦ #104 Phase B β ensure the sibling noetl_result_materializer durable consumer at stream-birth so the worker's result-materializer loop drains on its own ack cursor (object-store latency never back-pressures the noetl.event audit fold); no new deps, control plane stays slim (server#261, v3.41.0 4a6659e; flag-gated on the worker side, inert in prod). Prior: π¦ #104 Phase A β accept the canonical result URI behind NOETL_RESULT_URI_ACCEPT (default off), parsed via the slim dependency-free noetl-locator 0.1.0 (heavy graph stays off the control plane) (server#260, v3.40.0 c89d078): dep repointed gitβ0.1.0 pre-merge, heavy crates (duckdb/kube/arrow/tonic/rhai/gcp_auth) confirmed absent, 623 tests green; inert in prod until a future server rollout enables the flag. Prior: β
#123 β surface a non-iterable loop in: as a terminal playbook.failed (off-server drive decodes the {"error":β¦} envelope via decode_orchestrate_error; orchestrate-core prefixes the offending step name onto the existing evaluate_loop error) (server#258, v3.39.6 7f109a9, squash 275b914): a non-iterable loop.in: used to silently wedge (commands=0, RUNNING forever) under the off-server drive because apply_worker_orchestration couldn't decode the wasm plug-in's error envelope (logged WARN, recorded decode_error, returned Ok(0) β no terminal event); now it emits a terminal playbook.failed (metric noetl_orchestrate_drive_total{stage="drive_error"}, structured execution_id), matching the in-process drive. An empty iterable ([]/{}) still short-circuits to next. Code-only (worker stays v5.40.3); 600 server + 135 orchestrate-core tests + clippy clean; kind-validated prod-exact (absent workload.batch_slots β FAILED with a clear message, valid [1,2,3] still COMPLETED, #120/#124 unaffected, 0 restarts); ships to prod on a future rollout. Prior: β
#121 SECOND-HALF β gate BOTH off-server-drive sites in trigger_orchestrator_inner on should_publish so system/* execs drive server-built run_state (they INSERT to noetl.event, never enter the noetl_events WAL); regular execs keep the off-server path (server#257, v3.39.5 c421273, squash 54ac277); pure is_system_path helper + unit test; server-only (worker stays v5.40.3); 612 tests + clippy; kind full-gate system/scheduled_cleanup BEFORE 9Γ WAL chain incomplete β AFTER COMPLETED 0 loops, regular loop still off-server (#256 was only the first half; #121 reopened after the v3.39.4 prod re-cutover still wedged). Prior: β
#121 first-half β link the gate-off command.claimed through ChainHeads (server#256, v3.39.4 77aaa06, squash 28b17cb). Prior: β
#124 β distributed task_sequence forward set:/sibling bindings no longer render empty (defer sub-task templates with unresolved vars to the worker re-render) (server#255, v3.39.2 28e8950): a runtime liveness filter in the orchestrate-core reduce barrier β an open/asymmetric loop back-edge predecessor that never runs on the taken path is no longer counted as a pending fan-in dependency (build_incoming_arcs unchanged); affects in-server + off-server identically; test test_open_loop_back_edge_does_not_block_dispatch, 133/133 + clippy, kind-validated. Prior: β
#116 program-scale step 2 β execution-affinity single-owner WRITE ORDERING (multi-replica gate-ON validated) (server#252, v3.39.0 5e00d0a): closes the chain-fork race step 1 left open (the command.issued prev-read + head CAS-advance are two non-atomic steps β concurrent cross-replica emits forked the chain). Affinity routes every trigger for an execution (POST /api/events, which fires the drive) to the single replica that sharding::ShardConfig::owns(execution_id) owns (stable XxHash64); non-owner forwards a reverse-proxy POST (one-hop loop guard, degrade-to-local). On the owner the single-process drive lock + in-memory ChainHeads make the readβadvance atomic, no distributed lock; KV is the genesis/handoff vehicle (owner resolves LOCAL β kv_remote_hitβ0). New src/affinity.rs; NOETL_EXECUTION_AFFINITY/NOETL_PEER_URL_TEMPLATE/NOETL_SHARD_INDEX_FROM_HOSTNAME (all default off, prod unchanged); metric noetl_execution_affinity_total{outcome} (forwarded_ok = proof). Multi-replica gate-ON kind (2-replica StatefulSet, offserver+audit_only+nats_kv+affinity+publish_only): rig PASS β chains roots=1/dangling=0/walk==total (NO fork), forwarded_ok +9, never-scan + sole-writer across replicas, executions COMPLETE; single-replica unchanged; 595 tests + clippy green. Follow-up #117: off-server from_events spine ordered by event_id wedges fan-in under a chain-orderβ id-order inversion (high-concurrency fanout). Prior: β
#115 program-scale step 1 β multi-replica coherence DATA LAYER (NATS-KV-backed ChainHeads + ExecDescriptor); execution-affinity STAGED (server#251, v3.38.0 8f39a79): NOETL_REPLICA_COHERENCE=nats_kv (default local, prod unchanged) backs the off-server drive's watermark + descriptor with JetStream KV buckets β head advance = CAS (one chain under concurrent emits), descriptor = CAS merge; in-process maps = write-through cache / degraded fallback. src/coherence.rs; ChainHeads/ExecDescriptors async; metric noetl_replica_coherence_total{structure,op,outcome} (proof kv_remote_hit). Kind: single-replica nats_kv bit-for-bit parity with local (all topologies COMPLETE, clean chains, scans +0); 2-replica proved cross-replica resolves work. Necessary-not-sufficient: 2+ replicas still fork the chain (non-atomic issuing_event-read vs head-advance) β execution-affinity STAGED as step 2 (substrate in src/sharding.rs). Prior: β
#115 Phase 5 β atomic-working-item context (tenet 6): the drive hands a worker only its minimal declared slice (server#250, v3.37.0 a96ade8): new orchestrate-core::input_binding (analyze/project_context over minijinja undeclared_variables, conservative bail-to-full) + CommandBuilder::with_atomic_item_context; NOETL_ATOMIC_ITEM_CONTEXT (default false, prod unchanged); metric noetl_atomic_item_context_total{outcome}. Builds on #77 (Explicit Input Binding, CLOSED). Gate-ON kind-validated: flag-ON consumer ctx narrowed to the one declared key, COMPLETED; flag-OFF full ctx (back-compat). Prior: β
#115 Phase 6 β retire the hot-path noetl.event read class; the table is AUDIT-ONLY (server#249, v3.36.0 b71ca1d): NOETL_EVENT_READ_PATH=event_scan|audit_only (default event_scan, prod unchanged). Under audit_only, the remaining lifecycle readers of noetl.event (the WHERE execution_id replay class outside the drive β get_catalog_id per-ingest, inherit_parent_trace, subscription dedup-audit + container-callback catalog/existence) serve from the in-memory execute-time ExecDescriptor; a cold descriptor (post-terminal straggler after eviction / restart) resolves catalog_id from noetl.command (the synchronous queue) β never a noetl.event scan. New proof metric noetl_event_hotpath_reads_total{site,outcome}. Gate-ON kind-validated: hot-path scan Ξ0 (served_descriptor +96 + served_command +3), drive state_build_total Ξ0 + event_scans Ξ0 β ZERO noetl.event scans anywhere on the hot path, end-to-end; linear/loop/fan-out/output_select COMPLETE; sole-writer + lag-0; audit still works (direct SELECT + status + replay event_count=25); 585 tests + clippy green; default event_scan, prod unchanged. The RFC never-scan end state (tenet 3) is reached under the flag. Prior: β
#115 Phase 4 REMAINDER β stateless off-server drive edge (zero state rebuild + zero noetl.event reads) (server#248, v3.35.0 6e30fc3): removes the residual server-side chain-walk bookkeeping on the drive path. Under NOETL_STATE_BUILDER=offserver a warm execute-time ExecDescriptor (catalog_id + routing seeded at playbook_started; terminal stamped at the emit_events chokepoint) lets trigger_orchestrator_inner route the system/orchestrate command WITHOUT building WorkflowState β expected_head from the in-memory ChainHeads, trigger_event_id passed so the worker resolves the trigger type off its WAL, NO server-built state (__stateless__). apply_worker_orchestration sources catalog_id+routing from the descriptor (skips cold-rebuild) + evicts on terminal worker-built state. Cold descriptor (restart) falls through to the server-built path (re-seeds) β chain_walk + event_scan stay fallbacks. Proof: noetl_orchestrate_drive_total{dispatched_offserver_stateless,applied_stateless} advance while noetl_state_build_total stays flat. Gate-ON kind-validated: state_build_total Ξ0, event_scans Ξ0, dispatched_offserver_stateless +3 / applied_stateless +3, linear(13)/loop(62)/fan-out(25)/output_select(31) COMPLETE, offserver==server parity, sole-writer 25==25, lag-0; 583 tests + clippy green; default server, prod unchanged. Completes #107 step 2 server-side. Prior: β
#115 Phase 4 DRIVE CUTOVER β mark the off-server drive command + carry expected_head (server#247, v3.34.0 f0922bd): under NOETL_STATE_BUILDER=offserver (default server, prod unchanged) trigger_orchestrator_inner marks the system/orchestrate command __offserver_build__ + carries execution_id + expected_head (the staleness watermark) so the worker self-sources the drive state from the WAL; the server-built state rides along as the worker's incomplete-chain fallback. Gate-ON parity rig PASS (offserver==server, fan-in exactly-once, server scans 0). 580 tests + clippy green; no prod default changed. Prior: β
#115 Phase 4 β NOETL_STATE_BUILDER=offserver|server flag scaffold (default server) (server#246, v3.33.0 3e6006d): the server-side flag for the off-server state-builder drive cutover. server (default, prod unchanged) builds WorkflowState in-process; offserver routes the drive to obtain state from the pool-side off-server builder (worker state_builder, reading the noetl_events WAL). Flag only β the offserver drive-cutover wiring is staged (pool-side builder landed in worker v5.37.0). 2 config tests; no prod default changed. Prior: β
#115 Phase 3 MERGED β chain-walk state builder (flagged, default-off) (server#245, v3.32.0 8338417): behind NOETL_STATE_BUILD_MODE=chain_walk the drive reconstructs WorkflowState by following the one-level prev_event_id chain headβroot (head from the in-memory ChainHeads watermark, then (execution_id,event_id) PK point-lookups β never a WHERE execution_id scan of noetl.event) and feeds the same from_events (orchestrate-core unchanged β parity by construction). Falls back to event_scan (default) on cold-head / materializer-lag / non-genesis so correctness is never sacrificed; NOETL_STATE_BUILD_PARITY_CHECK shadow-builds both ways in one REPEATABLE READ snapshot and asserts equal. New metrics noetl_state_build_total{mode,outcome} + noetl_state_build_event_scans_total (no-scan proof, 0 under chain_walk) + noetl_state_build_chain_hops + noetl_state_build_parity_total{result}. Gate-ON kind-validated: parity 41/41 MATCH, event_scans_total=0 / 1064 PK hops / 0 fallbacks, all topologies COMPLETE, sole-writer + lag-0, 577 lib tests + clippy green. Self-merged; prod default unchanged. Phase 4 (off-server builder + WAL cache) builds on this. Prior: β
#115 Phase 2 MERGED β one-level prev_event_id event chain (server#244, v3.31.0): each noetl.event carries prev_event_id (the immediately-previous event in causal order) + each noetl.command the issuing-event link, stamped at the emit chokepoint emit_events from a per-execution chain-head watermark (ChainHeads) β covering drive events + command.issued + worker-lifecycle on both gate paths, the materializer persisting it. Additive (nothing reads the link yet β that is Phase 3, in progress). Chain-correctness kind-proven walkable/1-root/no-gap/no-scan across 6 gate-ON topologies; 573 lib tests + clippy green. ai-meta pointer afdb365. Prior: β
#115 Phase 1 β surface _ref/_store on kept refs + refs_in_state default true (server#243, v3.30.0): hydrate_result_references keep_refs branch merges ref/store/uri onto the bounded extracted summary it surfaces as context.data (so {{ step._ref }} lazy-load + {{ step._ref is defined }}/{{ step._store }} predicates resolve off the summary without bulk), and refs_in_state now defaults true β references stay out of state + commands (drive state + command.issued bounded; the worker selective consume side landed in worker#117). Closed #113 + #114; kind gate-ON all 9 stalls COMPLETE. Prior: π #114 β offload oversized command context (server#242, v3.29.5): a command.issued context over NOETL_COMMAND_CONTEXT_MAX_BYTES (512KB) is offloaded to noetl.result_store with a {__context_ref__} marker; get_command/claim_command resolve it before the worker sees the command (metrics context_offloaded/context_ref_resolved) β so the published event stays under NATS max_payload and never wedges the publish-only gate. Kind gate-ON: rig PASS, every command.issued event <1MB, 6 of #113's 9 fixtures COMPLETE; chose ref-on-oversize over refs_in_state=true (the latter breaks bulk-consuming fixtures β consume side not impl). Remaining 3 fixtures + the cutover gated on the refs_in_state consume side (#101). Prior: π #113 β off-server drive: recover offloaded drive result + stop drive on cancel (server#241, v3.29.4): apply_worker_orchestration resolves+decodes an OFFLOADED __orchestrate__ drive result (over the 100KB inline budget β durable reference.ref, no inline output_b64) instead of dropping it β non-convergent re-loop (new noetl_orchestrate_drive_total{stage=ref_resolved}); cancel now stops the drive (match underscore playbook_cancelled + ExecutionState::is_terminal terminal guard evicts the orch-cache, no restart). Kind gate-ON proven (785KB result β ref_resolvedβCOMPLETED, 0 decode WARNs; cancel froze a drive-loop instantly; sole-writer lag 0); rig e2e#63. 5/9 #113 fixtures COMPLETE; 4 hit a distinct oversized-command.issued stall β #114. No prod default flipped. Prior: π― #103 β CQRS write-path cutover COMPLETE, FLIP-READY (server#240, v3.29.3): the 2 ExecutionService cancel/finalize writers route through the emit_event chokepoint β the last synchronous server noetl.event writer under the gate is closed, so with NOETL_EVENT_INGEST_PUBLISH_ONLY=on the server writes zero event rows (materializer is the sole writer). Kind-proven both modes (gate-off byte-identical INSERT; gate-on PUBLISHED + materializer sole writer + terminal state + 0 loss/dup). All three flip blockers closed β flipping the gate on is a staged operator decision. Default-off; no prod default changed. Prior: #104 β off-server-drive Γ gate crash-recovery (v3.29.2, server#238): cold-cache apply rebuilds WorkflowState from the durable log. Prior: #108 (c) β worker-driven orchestrator drive now DEFAULT ON (server#233, v3.28.0): NOETL_ORCHESTRATE_PLUGIN_DRIVE defaults true after the scale soak proved zero noetl.event burst + full system-pool isolation; in-process drive kept as the =false revert. Prior: #101 β bounded-memory orchestrator + stall-proof reconcile (v3.9.0, server#197): projection-snapshot bounded rebuild (flat memory β 167KB snapshot at 200k events, was OOM at ~19k); throttled O(events) consistency COUNT off the hot path; background reconcile poller (force-advances every active execution every 8s β no permanent deadlock under DB backpressure); results-by-reference resolution; GET /api/executions/{id} memory-bomb fix. Validated kind 10Γ1000 (flat memory) + GKE db-g1-small/PgBouncer 10Γ200 (poller broke a stall, 0 fails/restarts, Cloud SQL ~15 backends). Prior: #90 Phase 7 β POST /api/execute/batch + opt-in exactly-once dedup window (v3.5.0, server#189, closes server#188): batch endpoint creates N executions in one round-trip with partial-failure containment, reusing the single-execute path so per-message routing/trace/dedup are intact; the opt-in dedup window (noetl.subscription_dedup, bounded-by-age, race-safe INSERT β¦ ON CONFLICT, scoped by subscription, subscription.message.deduplicated audit, default off β RFC Β§10 OQ1); validation of the new dispatch.batch_dispatch/batch_max/dedup/limits spec blocks; noetl_execute_outcomes_total + noetl_execute_batch_size. Live on kind: batch 12β12 COMPLETED; dedup duplicateβ1 execution; direct-curl within/outside-window + dedup-off all green. ai-meta β server 7b217d8. Prior (v3.4.2): #90 Phase 5 gcs/s3 spool credential optional (ADC). Prior: #90 Phase 4 β spool config validation + subscription lifecycle-status fix (v3.4.1, server#184+server#185): validate the spec.spool block at registration; lifecycle-status reconstruction now matches only the six lifecycle event types so spool/circuit events (which share the subscription's execution_id) can't 500 subscription_get/activate. Prior: #90 Phase 3 β push-ingress config endpoint + push catalog validation (v3.3.0, server#182): mode: push requires an ingress.verify block (hmac_sha256 | bearer | pubsub_oidc; none rejected) + new GET /api/internal/ingress/{listener} (service-account-gated) resolving the verify-secret alias through the Wallet + idempotent subscription registration β the gateway's DB-free config source. Live-validated on kind. Prior: #90 Phase 2 β kind: Subscription type + lifecycle + pool routing + W3C trace (v3.2.0, server#180): first-class kind: Subscription catalog type (source/mode/dispatch validation, no step-DAG) + event-sourced lifecycle endpoints /api/subscriptions (registerβactivateβpause/resumeβdrainβdeactivate, idempotent register, GET list/get) + execution_pool override on /api/execute routing the whole run to noetl.commands.<pool>.<eid> (persisted in playbook_started meta, orchestrator reads back) + W3C trace into meta.trace + command notification + child inheritance. Startup-seeds the subscription resource kind; decodes noetl.event.created_at as TIMESTAMP. Live E2E green. Prior (v3.1.0): subscription ToolKind. Prior: Round-trip JSON null in whole-object {{ step }} references (v3.0.6, server#177; closes noetl/ai-meta#89): a single-expression {{ step }} reference to a prior result envelope carrying a null field rendered that field as the JS token undefined via minijinja's map repr (json_value_to_minijinja maps JSON nullβValue::UNDEFINED); render_to_value then failed serde_json::from_str and returned the whole envelope as a raw string, so the consuming python/rhai step received an unparseable str and crashed. Fix adds a | tojson retry to render_to_value (mirrors the noetl-tools TemplateEngine::render_value the server copy had diverged from): a lone {{ expr }} whose plain render is container-shaped-but-invalid JSON re-renders with | tojson, and minijinja_to_json maps undefined/none β JSON null so the field round-trips as null. 5 new regression tests (null in nested + top-level objects, null array element, explicit | tojson no-double-pipe, scalars unchanged); 619 lib + 8 parity green; clippy clean. Kind-validated against the live paginated-api test-server: cursor fixture walks all 4 pages, terminal next_cursor: null handled, validate_results collects 35 (first_id=1, last_id=35, success) β matching offset; pre-fix the 4th check_pagination was command.completed error. ai-meta β server 8e17fbe. Prior: Workflow-arc loops advance across iterations + terminate (v3.0.5, server#176; closes noetl/ai-meta#85): two coupled fixes atop the dispatch-guard re-entry layer. (1) Durable event-sourced loop-ctx propagation β step-level set: ctx.* loop variables were recomputed per pass and reverted to the workload default (loop thrashed 0,0,1,0,1,2,β¦); root cause was start's initializer set re-firing every pass in random HashMap order against the loop's advancing set. Fix persists each completion's rendered set: as a ctx.updated event (latest-wins fold in WorkflowState.ctx + build_context overlay), emitted once per completion keyed by the stable completion event_id (the Utc::now()-fallback completed_at is unstable across reconstructions). (2) Loop-exit hang β the exit branch was marked step.skipped on a loop-body-completion pass (recency-based branch-point detector missed it), turning it terminal so the is_step_done guard suppressed the exit dispatch; fixed with a structural loop-branch-point test (any step with a back-edge arc). 614 lib tests (+6; 2 verified to fail without their guard); clippy-clean. Kind-validated: counter loop 0β1β2β3 + terminates; real-http offset pagination 4 pages collecting 35. ai-meta β server e519fdc. (Separate finding: the e2e offset/cursor fixtures read response.data.users but the Rust http tool nests it at response.body.users β filed as a follow-up.) Prior: Unblock workflow loops + loop.done-gated transitions (v3.0.4, server#175; closes noetl/ai-meta#83 + #84): the fan-in/reduce barrier counted a loop back-edge (check_pagination β fetch_page) as an upstream and deferred the loop head forever β fix excludes back-edges via a new forward_reachable helper (genuine fan-in unaffected); and event.name was never populated for arc evaluation so when: {{ event.name == "loop.done" }} never matched (10+ fixtures hung after an in-step loop:) β fix injects event.name = "loop.done" into a completed loop step's next-arc context. Found in a full e2e regression re-sweep (19β27/36 on kind); landed with e2e fixture fix e2e#39 (duckdb commandsβcommand, closes #86). Follow-ups #85/#87 filed open. 26 orchestrator tests +2 new; kind-validated. Prior: container-callback insert matches the deployed event schema (v3.0.3, server#173; tracks noetl/ai-meta#43): the container-callback handler emitted its resume call.done via a stale query targeting attempt + id columns that don't exist on the deployed noetl.event (PK (execution_id, event_id)) β every watcher POST 500'd with column "attempt" of relation "event" does not exist, blocking the #43 chain. Replaced with an inline INSERT matching the working handlers::events column set; terminal outcome rides in a chk_event_result_shape-conforming result envelope. Kind-val GREEN: kind_validate_container_callback.sh both probes pass (happy_path β succeeded, oom β failed_oom). Prior: container-tool command type contradiction fixed (v3.0.2, server#172; closes noetl/ai-meta#81): ToolSpec.command Option<String> β Option<serde_json::Value> so the container tool kind's K8s-Job-style array command decodes server-side (was a 400 data did not match any variant of untagged enum ToolDefinition) and passes through unchanged to the worker's ContainerConfig.command: Option<Vec<String>>; a scalar stays a JSON string for the shell/db consumers; ToolCall::from_spec forwards the value verbatim instead of wrapping in Value::String. 2 new regression tests (playbook::types 18/18); clippy clean. Kind-val GREEN end-to-end β server accepts command: ["/bin/sh","-c"], worker dispatches the container tool, K8s Job reaches Complete 1/1 (pre-fix kubectl get jobs empty). Prior: e2e-sweep cleanup (v3.0.1, server#171; tracks noetl/ai-meta#49): 64 MB result-store PUT body limit (DefaultBodyLimit β was rejecting 15 MB+ payloads with HTTP 413); render_pipeline_config stashes set/args/spec/command blocks before Tera rendering; iter namespace map in build_iteration_command; cmd_render_ctx uses command.context override; stripped diagnostic tracing::debug! blocks. All 7 e2e sweep playbooks PASS on Rust-only kind stack. Prior: Sequential-mode iterator dispatch (v2.62.0, server#166; closes noetl/ai-meta#76): LoopMode enum (Sequential default / Parallel); LoopSpec.mode parsed from loop.spec.mode YAML; StepInfo.iterations_dispatched tracks command.issued count for the sequential dispatch guard; sequential pattern dispatches iteration 0 at fan-out then on each command.completed dispatches next if iterations_dispatched == iterations_completed(). Default is Sequential β existing playbooks without explicit spec.mode get sequential behavior. 3 new tests; lib pass; clippy clean. Kind-val GREEN: test/loop COMPLETED 5/5 + iterator_save_test COMPLETED 4 steps. First Claude-direct Rust PR under agents/rules/handoff-routing.md. Prior: Durable result-store endpoints (v2.58.0, server#160; closes noetl/ai-meta#70): PUT + GET /api/result/<eid>/<step>/<ref>. Kind-val GREEN: output_select_test reached playbook.completed. Prior: Rust orchestrator exclusive-routing fix β step.skipped for untaken siblings (v2.57.2, server#159; closes noetl/ai-meta#67): under mode: exclusive only one arc fires; pre-fix, the static planner declared the untaken sibling's target as an upstream of any downstream merge step, then the R4 fan-in barrier waited for it forever. Three-part fix: (1) evaluator::evaluate_next_transitions stops break-ing on exclusive-mode match β surfaces unmatched siblings as not_matched_with_target results; (2) orchestrator::process_in_progress two-pass refactor β emit step.skipped for ALL unmatched arc targets first, then dispatch matched (HashMap-order-independent); (3) +2 unit tests + new defensive Jinja regression guard (server lib 568/0/0). Kind-val GREEN: e2e/fixtures/playbooks/comprehensive_test.yaml reaches playbook.completed in ~4s (was hanging forever). Single-commit patch on top of v2.57.1. Prior: Rust orchestrator step.data template accessor fix (v2.57.1, server#158; closes noetl/ai-meta#66): WorkflowState::build_context now injects a self-referencing .data key on the extracted user_data so {{ step.field }} (existing flat path) AND {{ step.data.field }} (new wrapped path) both resolve. Guarded by !map.contains_key("data") to preserve task_sequence flatten back-compat (a labeled sub-task's data field stays addressable as both <step>.<label>.data.x AND <step>.data.x). 2 new unit tests; cargo test lib 566/0/0; release build clean. Single-commit patch on top of v2.57.0. Prior: Phase D R5 R7 β cross-server parity harness; Replay engine port complete (v2.57.0, server#157; closes server#148; tracks noetl/ai-meta#49 Phase D R5). Final slice β hermetic parity rig: events.json (13 synthetic events) + expected.json (Python's pre-recorded fold output) + regenerate_expected.py (standalone Python script β verbatim extract of service.py fold + helpers, no noetl-package imports). tests/parity_harness.rs 8-test integration suite asserts structural parity field-by-field across all six projections + payload refs. Parity is structural not byte-for-byte hex (Python and Rust hash different digest inputs per R4's design). All 8 tests pass; lib 564/0/0; release build clean. No kind-val needed β test-only PR. All 7 Phase D R5 rounds shipped today (v2.51.0 β v2.57.0); the Replay engine port is complete. Phase D R5 R6 β payload resolver (v2.56.0, server#156; Refs server#148; tracks noetl/ai-meta#49 Phase D R5): every event's result.reference JSON gets parsed into a typed PayloadSummary and appended to the relevant projection's payload-refs list. PayloadSummary + PayloadRefEntry types mirror Python's dict shapes; extract_payload_ref + payload_summary mirror Python's helpers with three-tier fallback (reference.<field> β rows_ref.meta.<field> β rows_ref.ipc.<field>). ReplayExecutionState.payload_refs + ReplayFrameState.output_ref/output_ref_summary + ReplayBusinessObjectState.payload_refs/last_payload_ref all populated. 15 new unit tests; lib 564/0/0. Kind-validated against live execution showing 3 populated payload_refs with real SHA-256 digests. Only R7 remains. Phase D R5 R5 β snapshot seed + base_state + upcaster digest (v2.55.0, server#155; Refs server#148; tracks noetl/ai-meta#49 Phase D R5): the replay fold can now start from a prior fold's output and continue from there. ReplaySnapshotSeed mirrors Python's frozen dataclass; ReplaySnapshotInfo is the output subset; ReplayFoldOptions carries the optional inputs; new fold_replay_state_with_options entry point + 5-arg fold_replay_state as a back-compat shim. R5-introduced ReplayState fields (replay_snapshot, upcaster_registry_digest) both skip_serializing_if None β default folds produce R1βR4-identical JSON. Snapshot-storage backend deferred to a downstream sub-issue. 8 new unit tests; lib 549/0/0. Kind-validated wire-shape back-compat. Phase D R5 R4 β typed Checksum + projection_checksums (v2.54.0, server#154; Refs server#148; tracks noetl/ai-meta#49 Phase D R5): every replay fold now produces a typed Checksum over the full state + a 6-entry projection_checksums map. ChecksumType enum (initial variant Sha256) + Checksum { type, value } struct + stable_json_bytes deterministic JSON encoder + compute_checksums per-projection + top-level digest run at the end of fold_replay_state. Design decision: digest input is the typed Rust state directly, not Python's flat-row normalize layer; Python byte-for-byte parity deferred to R7. 9 new unit tests; lib 541/0/0. Kind-validated: top-level checksum 41265876487f...ae426; six projection_checksums entries all populated. Phase D R5 R3 β loops + business_objects projections (v2.53.0, server#153; Refs server#148; tracks noetl/ai-meta#49 Phase D R5): third slice of Phase D R5 β replay fold populates the last two per-projection maps. Two new typed state structs (ReplayLoopState, ReplayBusinessObjectState) replace R2's serde_json::Map placeholders; ReplayState.{loops,business_objects} flip to BTreeMap for deterministic key ordering. Two new ID extractors mirror Python's _loop_id / _business_object_identity resolution order; business_object_status helper mirrors Python's _business_object_status suffix-derived ACTIVE/DELETED transitions; two new populate functions with full event-shape coverage (loop counters bump on command.{completed,failed} + loop.shard.{done,failed} + loop.{done,fanin.completed}; business-object attributes REPLACE/PATCH from meta). payload_refs deferred to R6. 13 new unit tests; lib 532/0/0. Kind-validated: re-probe returns loops + business_objects empty (expected β fixture doesn't emit those events). R4 design captured (per user direction): typed ChecksumType enum + Checksum { type, value } struct; future checksum types slot in via enum. Phase D R5 R2 β stages + frames + commands projections (v2.52.0, server#152; Refs server#148; tracks noetl/ai-meta#49 Phase D R5): second slice of Phase D R5 β replay fold populates stages + frames + commands projections. Mirrors Python's state["stages"] / state["frames"] / state["commands"] per-projection dicts. ReplayEventRow extended with stage_id / frame_id / command_id / worker_id / aggregate_type / aggregate_id / meta columns (all #[sqlx(default)]); three new typed state structs (ReplayStageState / ReplayFrameState / ReplayCommandState); ReplayState.{stages,frames,commands} flip from serde_json::Map to BTreeMap for deterministic ordering; three new ID extractors mirror Python's resolution order; three new populate functions with full status transitions (stage opened/closed; frame dispatched/started/committed/failed/abandoned; command full lifecycle). 10 new unit tests; lib 518/0/0. Kind-validated: re-probe of prior fanout_reduce execution returns commands map populated with 4 entries carrying worker_id + issued_event_id + last_event_id. Phase D R5 R1 β Replay endpoint scaffold + execution projection (v2.51.0, server#149; tracks server#148): opens Phase D Round 5 (Python's noetl/server/api/replay/service.py ~1236 LoC β Rust). Sub-issue server#148 documents the 7-round decomposition (R1 scaffold + execution / R2 stages+frames+commands β
/ R3 loops+business_objects / R4 typed Checksum + projection_checksums / R5 snapshot seeds / R6 payload resolver / R7 cross-server parity harness). R1 ships new GET /api/replay/state route mirroring Python's endpoint.py byte-for-byte (query params + defaults + projection enum + mutually-exclusive cutoffs returning 400); new services::replay module with ReplayService + ReplayCutoff + ReplayProjection + ReplayState + ReplayExecutionState + pure deterministic fold_replay_state; minimal execution projection fold reuses the Phase D R4 terminal-event short-circuit. Phase D R4 follow-up: status endpoint short-circuits on terminal events (v2.50.1, server#147; closes server#146): ExecutionService::get_status now (1) looks up playbook.completed / playbook.failed FIRST and returns COMPLETED / FAILED directly, and (2) accepts 'success' lowercase in the completed_steps counter. Kind-validated: prior execution flipped RUNNINGβCOMPLETED on the same DB data. Phase D R4 slice 2 β apply_event handles step.skipped (v2.50.0, server#145; closes server#144): new step.skipped arm in state::WorkflowState::apply_event records the step with StepState::Skipped β fan-in barrier no longer defers forever when an upstream's when guard evaluates false. Container Tool Callback umbrella #43 Round 2 β POST /api/internal/container-callback/{execution_id}/{step} (v2.48.0, server#141; closes server#140; tracks noetl/ai-meta#43): external K8s watcher (Round 1, ops#166) POSTs Job terminal-state events here; handler validates path params, checks staleness via a single indexed SELECT on noetl.event, and emits a call.done event with the structured terminal state on match (or bumps noetl_container_callback_stale_total + returns 202 if no events exist for the execution). Six TerminalState variants survive in meta.terminal_state so playbooks can branch on the specific failure reason. Two new counters; 7 new unit tests; lib 487/0. Round 2 lands first per the umbrella's recommended ordering β smallest blast radius; unblocks Round 1 (watcher Deployment) + Round 3 (tools#36 β Tool::Container). Secrets Wallet #61 cloud-specific dynamic-secret providers shipped β umbrella feature-complete (v2.45.0 server#137 + v2.46.0 server#139 + v2.47.0 server#138): 6d.1 AWS STS AssumeRoleWithWebIdentity (EKS IRSA path; no SigV4 β token IS the credential); 6d.3 Azure AAD client-credentials (off-cluster non-IMDS path; sovereign-cloud overrides via env); 6d.2 GCP iamcredentials.generateAccessToken (workload-identity impersonation of a target SA). All three return SecretValue.expires_at populated β Phase 6d's cache_decision clamps cache TTL accordingly; Phase 7c.3 background refresh re-resolves inside the window. 39 new unit tests across the three providers; lib 470/0. Secrets Wallet umbrella is feature-complete: envelope encryption + KMS + 5 static-secret providers (GCP-SM, K8s, Vault, AWS-SM, Azure-KV) + 3 dynamic-secret providers (AWS-STS, GCP-IAM, Azure-AAD) + residency policy + cross-region broker + KEK rotation + audit + auto-renewal with stampede collapse. Phase 7c.3 β background-refresh wire-up + stampede collapse (v2.44.0, server#136): cache-hit path now spawns a background tokio::spawn to re-resolve via the provider + update the cache via KeychainService::set when the row is inside the refresh window. Cached value returns IMMEDIATELY to the caller β worker fetches stay on the fast path. Stampede collapse via new src/services/keychain_refresh.rs RefreshInflight (Arc<tokio::sync::Mutex<HashSet<(i64, String)>>>); concurrent refreshes for the same (catalog_id, alias) collapse to one provider call. Refactor: extracted resolve_via_provider from try_resolve_keychain so cache-miss inline + background refresh share identical code. Phase 7c series wire-complete (7c + 7c.2 + 7c.3). Phase 7a.2 / 7b.2 / 7c.2 β operator-facing rotation + audit storage + cache-refresh primitive (v2.42.0 server#127 + v2.43.0 server#129 + server#131): 7a.2 wraps the Phase-7a rewrap_storage_string primitive with POST /api/internal/wallet/rotate-kek (batched cursor scan of noetl.credential + noetl.keychain, RotateSummary { processed, rewrapped, skipped, failed, last_id } for progress checkpointing) and GET /api/internal/wallet/key-status (per-version row counts). 7b.2 adds the noetl.secret_audit table (CREATE TABLE IF NOT EXISTS at startup; server-owned), DbAuditSink impl, and GET /api/internal/secret-audit?credential=&execution_id=&from=&to=&limit= (bounded; ORDER BY occurred_at DESC; hard cap 10_000). 7c.2 adds KeychainService::should_refresh(catalog_id, keychain_name, execution_id, scope_type, now) β cache-side companion of the Phase-7c decision primitive; reads the row's expires_at, asks secrets::dynamic::should_refresh_default (honours KEYCHAIN_CACHE_REFRESH_WINDOW_SECS), bumps noetl_secret_refresh_total{outcome="triggered"} on true. Backward compatible. Next: 7c.3 stampede-collapse mutex + background re-resolve; 6d.1/.2/.3 cloud-specific dynamic providers. Phase 7c β token auto-renewal primitives (closes Phase 7) (v2.41.0, server#125): final named round of the Secrets Wallet umbrella. secrets::dynamic::should_refresh(expires_at, refresh_window, now) decision primitive (true iff expires_at set + still valid + within refresh window); KEYCHAIN_CACHE_REFRESH_WINDOW_SECS env (default 60). noetl_secret_refresh_total{outcome} counter (triggered|succeeded|failed|stampede_collapsed; failed alert-worthy) + noetl_secret_refresh_duration_seconds histogram (50msβ5s buckets). 5 new unit tests; lib 427/0. Lib-only. All named phases (1β7) of the Secrets Wallet umbrella are complete. Remaining queue is discrete follow-up sub-issues only. Phase 7b primitives β secret-resolution audit service (v2.40.0, server#123): durable audit trail of every credential resolution. AuditEvent struct (NEVER contains the secret value); bounded Operation + Outcome enums; AuditSink trait + NoopAuditSink default + SecretAuditService wrapper with record_async (fire-and-forget, never blocks resolver) + record_strict (await, used when compliance mandates the row exist before the value releases) + record (dispatches by strict-mode). NOETL_SECRET_AUDIT_REQUIRED env (default false; 1/true/TRUE/yes/YES enable strict). noetl_secret_audit_writes_total{operation, outcome, status} counter (failed_strict alert-worthy). 8 new unit tests; lib 422/0. Lib-only. Phase 7a β KEK rotation primitives (v2.39.0, server#121): starts Phase 7. KeyManager::current_key_version() trait accessor; EnvelopeCipher::rewrap_storage_string primitive (parse β if same version Skipped; else unwrap with historical version β re-wrap with current β Rewrapped { old_key_version, new_key_version, new_storage_string }). Plaintext payload NEVER reconstructed β pure DEK re-wrap, AES-GCM ciphertext bytes stay byte-identical. noetl_wallet_rotate_total{table, status} counter (skipped|rewrapped|failed_unwrap|failed_wrap|parse_error). 4 new unit tests; lib 414/0. Lib-only. Phase 6e β cross-region broker (closes Phase 6) (v2.38.0, server#119): BrokerRegistry (region β broker_url from NOETL_SECRET_BROKER_REGISTRY env; empty default = pre-6e fail-closed); POST /api/internal/cross-region/resolve peer endpoint validates expected_entry_region == server_region() (defensive against stale peer registries), resolves locally, seals via Phase-5a primitives to the requesting worker's pubkey directly; get_sealed handler falls back to broker on AppError::ResidencyViolation; KeychainDef.no_broker_fallback per-credential opt-out; AppError::CrossRegionUnreachable β HTTP 502. Two new metrics: noetl_secret_broker_call_total{broker_region, outcome} + noetl_secret_broker_call_duration_seconds{broker_region} histogram (50msβ5s). 10 new unit tests; lib 410/0. Both residency shapes operational: hard isolation (strict + no broker = HTTP 403) + soft federation (strict + broker registered = transparent cross-region routing). Phase 6 closes. Phase 6d primitives β dynamic-secret support + cache honors issuer TTL (v2.37.0, server#117): SecretValue.expires_at: Option<DateTime<Utc>> field; src/secrets/dynamic.rs cache_decision() honors min(default_ttl, expires_at - now - safety_margin) and returns SkipCacheAlreadyExpired when the deadline is already past or inside the operator's safety margin; KEYCHAIN_CACHE_DYNAMIC_SAFETY_MARGIN_SECS env (default 60); resolve_keychain_entry_with_meta returns the bundle's earliest expires_at; CredentialService::resolve_via_provider consumes the helper. Two new metrics: noetl_secret_dynamic_ttl_seconds histogram (1m/5m/15m/1h/4h/12h buckets) + noetl_secret_cache_skip_total{reason} counter. 7 new unit tests; lib 398/0. Backward compatible (providers without expires_at keep the 600 s default). Phase 6c β residency-policy gate (v2.36.0, server#115): KeychainDef.residency enum (none|advisory|strict, default none) + allowed_regions allowlist; resolver runs the gate at the top of resolve_keychain_entry BEFORE any provider call so strict-mode mismatches short-circuit with AppError::ResidencyViolation (HTTP 403, clear "credential X is region-locked to Y; this server is in Z" message that NEVER includes the value). noetl_secret_residency_check_total{policy, decision} counter β strict + violation_blocked is alert-worthy, advisory + violation_allowed is the migration-window signal. 8 new unit tests; lib 391/0. Phase 6b β ProviderRegistry + per-(provider, region) metrics (v2.35.0, server#113): server-side cache of (provider_id, region) β Arc<dyn SecretProvider> so the resolver doesn't rebuild from env on every cache-miss; RwLock + double-checked locking on the build path so concurrent get_or_build for the same key only builds once. Optional TTL via NOETL_SECRET_PROVIDER_TTL_SECONDS. New noetl_secret_provider_build_total{provider,region,status="cache_hit|ok|error"} counter + noetl_secret_resolve_duration_seconds{provider,region} histogram (5 ms β 5 s buckets). 7 new unit tests; lib 383/0. Phase 6a β region tag on keychain entries + per-region routing (v2.34.0, server#111): starts Phase 6 (residency-aware distributed resolution). KeychainDef.region optional field (no schema migration β lives in existing JSON blob); SecretRef.region provider-agnostic; AWS provider consumes it as the regional endpoint with explicit precedence (<region>: ref prefix > field > legacy project overload > AWS_REGION env). New NOETL_SERVER_REGION env + server_region() / effective_region() fallback helpers. noetl_secret_resolve_total{provider,region,status} counter per observability.md Principle 1. 5 new unit tests; lib 376/0. Lib-only β backward compatible. Phase 5b β wire format + sealing endpoint (v2.33.0, server#107): new GET /api/credentials/{id}/sealed?worker_id=<name> returns a SealedEnvelope (X25519-sealed credential JSON) addressed to the named worker; workers opt in by including worker_public_key in their register payload's runtime JSON blob β no schema migration; 400 BadRequest when the worker_pool row exists but didn't register a key; noetl_credentials_sealed_total{status} counter + credential.seal span per observability.md. Kind-validated end-to-end (Python cryptography + HKDF + ChaCha20-Poly1305 opens the envelope β recovers the bearer token + scope round-trip). Phase 5a β sealed payload crypto primitives (v2.32.0, server#107): src/crypto/sealed.rs X25519 ECDH + HKDF-SHA256 + ChaCha20-Poly1305 sealed-box (nonce derived from the shared secret, AAD pins alg+v for clean alg-mismatch rejection); 12 unit tests (round-trip, tamper, alg/version-mismatch, JSON wire stability); lib 369/0. Defense-in-depth on top of Phase-4 mTLS β cleartext never enters the response body. Lib-only; 5b adds the runtime-registry worker pubkey + sealing endpoint, 5c the worker side. Providers 3.x β AWS Secrets Manager + Azure Key Vault (v2.31.0, server#105): two new backends behind the one SecretProvider trait completing the 5-provider matrix. AWS SM uses hand-rolled AWS Signature Version 4 signing (no aws-sdk dep tree; signing key verified by a unit test against AWS's published reference vector); ref shape [<region>:]<secret-id>[#<json-key>] with JSON-key extraction for multi-field secrets; creds from env (the IRSA-injected triple). Azure KV uses IMDS Managed Identity (AKS/VMs) with TTL-cached bearer; ref shape [<vault>/]<secret-name>[#<version>]; sovereign clouds via NOETL_AZURE_KEYVAULT_DNS_SUFFIX. 21 new unit tests; lib 357/0; cloud-only backends (kind-val at unit-test layer like GCP). Phase 4a β opt-in TLS/mTLS listener (v2.30.0, server#103): the workerβserver credential channel (GET /api/credentials/<alias>) was plain HTTP; opt-in TLS via NOETL_TLS_CERT+NOETL_TLS_KEY (+NOETL_TLS_CLIENT_CA β mTLS), ring rustls + axum-server bind_rustls, kind-validated (200 w/ client cert, rejected w/o, plain HTTP refused). Providers 3.x β HashiCorp Vault provider (v2.29.0, server#101): a provider: vault keychain alias resolves from a Vault KV v2 secret (X-Vault-Token; ref [<mount>/]<path>#<key>), kind-validated end-to-end against an in-cluster Vault β second backend validatable on kind after K8s. /api/executions list perf + status fix (v2.28.1, server#99, #62): candidate-first rewrite (start-event index, not a 3.2M-row seq scan) β 6.5 s β 0.015 s (~430Γ), identical list; bool_or status-drift fix (was all-RUNNING). Secrets Wallet #61 providers 3.x β Kubernetes Secrets provider (v2.28.0, server#97): a provider: k8s keychain alias resolves from an in-cluster Secret via the API server + ServiceAccount token + cluster CA β the first secret backend kind-validated end-to-end with a real value (GCP needs GKE). Orchestrator-strand fix (v2.27.2, server#95): a deterministic evaluate failure (an invalid template in a step code body, an unknown step in a next arc, malformed routing) now emits a terminal playbook.failed instead of stranding the run in RUNNING forever β surfaced by the #54 e2e sweep (closed server#94). Parser fix (v2.27.1, server#93): NextSpec untagged-variant order β the list form next: [{step: x}] was deserialized into a struct Router positionally, silently dropping its arcs (and defeating unknown-step validation); sequence-shaped variants now precede the struct. Secrets Wallet Phase 3c β keychain cache (v2.27.0, server#91): execution-scoped, envelope-encrypted, TTL'd cache so an auth: "{{ alias }}" lookup isn't re-fetched from the secret manager per step; + fixed the keychain storage layer (queries never matched the table β also repairs the /api/keychain endpoints). Phase 3 (resolution) complete β R3b (v2.26.0) resolves a provider: gcp keychain alias from GCP Secret Manager on a credential miss; built on R3a/R2/R1 (v2.23.0βv2.25.0). Phases 1β2: Cloud KMS for the KEK (v2.22.0); envelope encryption (v2.21.0) |
noetl/worker |
Rust worker + EHDB command-bus writer host |
v5.123.0 released Β· v5.123.0 in prod (both system pools on Secret-Manager files, 2026-08-27; event-log tier primary and SERVING) |
π‘οΈ The writer seals BOTH logs on SIGTERM, and stops ingest first (2026-08-03) β worker#211 ddb4de8, v5.92.0. A fail-loud internal bus source (closes #221 β unset/blank/typo'd *_SOURCE on an enabled consumer is now a startup error, not a silent fall-through to the deleted NATS transport), the sequenced seal plus a confirmed stop-ingest (#209 graceful half), and face supervision (detection-only mitigation for ehdb#311). The seal took two rounds: the first had the right order but signalled with Notify::notify_waiters(), which stores no permit while the acceptor registers on its first poll β so a shutdown firing early lost the wakeup and the ingest listener was never closed (lsof caught the writer's own acceptor still in LISTEN after shutdown.run() returned; 24/30 full-suite runs under contention). c44ebf2 makes it a watch plus an ack after the fd is dropped. Prod gate: controlled SIGTERM sealed hosts=2 in 568 ms, every cursor resuming clamped=false / replay_records=0; a synthetic execution moved the bus +19 and all three group cursors +31 with exactly 31 durable events, lag 0. Cargo.toml back down to 5.91.3 on main (#224) β the tag, the image and the ai-meta pointer (ddb4de8) are correct. Prior: π’ T5-readiness deploy (2026-07-31) β the writer now serves the per-pool lag ehdb_feed_subject_lag{subject} (the user pool's KEDA trigger, worker#197), the #208 resume facts, and ehdb_l0_out_of_order_appends (worker#198, closes #206) β all from one scrape. Prior: π v5.81.3 β the writer survives a restart, and the bus got faster (2026-07-30) β worker#196 resumes from a durable committed cursor clamped to the reopened log's tip and seals on SIGTERM (terminationGracePeriodSeconds 30 β 90 s); claim sockets get TCP keepalive + a negotiated coordinator heartbeat, so a dead or stuck writer surfaces as an ordinary error instead of an indefinite park. worker#195 adopts the ehdb group-commit + off-lock fsync. Prod gate: writer deleted mid-load β 38/38 COMPLETED, 114 = 38Γ3, 0 dup / 0 loss, ~2.7 s redial (#208 CLOSED). Prior: π L1 T4 β the worker hosts the EHDB command-bus writer in PROD (2026-07-27) β spawn_writer_host (ingest :9100 / claim :9101 / /metrics :9102) on a premium-rwo PVC; workers claim over the network with subject filters (0 cross-pool claims proven on prod). v5.81.1 carries the #203 runtime fix (the writer lives here). v5.79.0/v5.80.0/v5.81.0 = the #199 write-behind sink slices (durable-segment GC gate / sink-confirmation signal / auto-sink observe-only), all default-off. Prior: π©Ή #166 Phase 1 β bounded off-server WAL index, system-pool OOM CURED on prod (2026-06-30) β slim chain + LRU/TTL/byte-ceiling eviction + cold-rebuild-on-miss, all flag-gated to today's unbounded behavior (worker#144, v5.49.0 4e163b9; ops env #231). Prod system-pool flags-on (SLIM/TTL_SECS=900/MAX_BYTES=256Mi ledger/REHYDRATE_ON_MISS/SWEEP_SECS=30): idle RSS 1265Mi β 67Mi holding, TTL swept 579 idle execs, drives served, /livez 200, login + "Trip to Paris" turn COMPLETED, 0 OOMKills. 2Gi limit kept as headroom (#163). Rollback = flags off β :5.48.1. Prior: π©Ή #161/#163 state_builder self-heal + /livez backstop (worker#143, v5.48.1 cc9ae22): WAL drain recreates a dead JetStream consumer + fails /livez after an unable-to-serve window so K8s restarts the wedged pod. Prior: β‘ #156 off-server drive tail-attach DEPLOYED on prod (2026-06-29) β resolve_offserver_orchestrate_input applies the server-attached tail_events to the pool-side WAL index BEFORE the build loop, so a warm-index hop serves on the first attempt regardless of drain lag β per-hop O(execution), no 8s reconcile cliff; additive + idempotent, insufficient tail falls through to today's drain/reconcile path (worker#142, v5.48.0 12710b6; prod flag-on: 90 drives attached, O(tail) mean 24 β€64, all served 0 fallback, 0 restarts, reconnect 0/30s; 1-step rollback). Prior: π #157 hotels-turn drive wedge fix (worker#141, v5.47.3 9f83ab5): __orchestrate__ drive result stays inline regardless of size. Prior: π G2 container poll-fallback MERGED + RELEASED (review-only) β watcher-free poll-to-terminal completion fallback for long-running k8s Jobs, flag-gated NOETL_CONTAINER_COMPLETION_POLL default-off (worker#139, v5.47.0 32b0e96; ai-meta pointer 2e79af6). Prior: π #130 off-server drive latency fix SHIPPED + DEPLOYED TO PROD ON OFF-SERVER (fix(offserver): event-signalled drive + per-message WAL index lock release; re-released via trivial PR worker#134 after the dropped webhook; ghcr.io/noetl/worker:5.46.1 @ sha256:97aa2690β¦; ai-meta pointer 32e95f4; warm drive β€0.1s, no offserver_retry, event_scans=0, CQRS sole-writer + result-tier intact, 0 restarts). Prior: π¦ #104 Phase E + F MERGED β side-effect durability barrier + result-tier DR re-derive (NOETL_SIDE_EFFECT_BARRIER E adopt-only exactly-once side effects, worker#130 β v5.44.0 d696f7e, depends on published noetl-tools 3.17; NOETL_RESULT_TIER_DR F materializer verify-and-repair byte-identical, worker#131 β v5.45.0 dd07016; both default-off, inert in prod). Prior: πͺΆ #104 Phase D MERGED β the minting flip (NOETL_RESULT_MINT_AUTHORITATIVE, default off: the result materializer becomes the authoritative tier writer (implies the Phase B flag) + resolve-by-URN becomes the primary consume read path (implies the Phase C flag), with the dual-written result_store as the fail-safe fallback; new noetl_worker_result_mint_authoritative_total{path} = tier |
noetl/tools |
Shared tool registry crate | v3.25.0 |
π§© provider tool round 5 β wire-shape eventlog-tier fold adapter + stack-scoped ownership MERGED + RELEASED (tools#90, v3.25.0 5a06c7e, crates.io-tarball-verified β all 5 new pub fns present; ai-meta pointer toolsβ5a06c7e; #189 OPEN): extract_facts_from_tier_records/fold_tier_records consume the EventLogRecordView rows from GET /api/ehdb/tiers/eventlog β unwrap the event body from the payload string + a bounded recursive search for the fact. Bug fixed: the round-3/4 extract_facts_from_eventlog fixed paths never matched the real call.done nesting (context.result.context.data.provider_fact), so a fold over real event data on 3.23.0/3.24.0 silently returned nothing (only ever run on synthetic records; exposure latent β the HTTP adapter didn't exist until round 5). Stack scoping: OwnershipModel::for_stack/stacks, detect_orphans_scoped (fixes detect_orphans folding all stacks into one model), detect_stack_conflicts (URN owned by >1 stack without an intervening delete; transfers not flagged). +3 tests (55 provider / 480 lib), clippy --all-features clean, no libduckdb compile. Prior: π§© provider tool round 4 β reconcile policy + confirm-gated drift adoption MERGED + RELEASED (tools#89, v3.24.0 c172efb, crates.io-verified; ai-meta pointer c6790983βtoolsβc172efb; #189 OPEN): a reconcile: policy on the provider step β report (new default, changes nothing: no cloud mutation, no ownership fact), enforce (round-2 converge), adopt (accept live actual as the new desired, read/GET only β zero cloud mutation, structural). adopt confirm-gated identically to destroy (dry-run field-by-field drift diff + plan_digest; apply needs confirm:<digest>; blind apply + stale digest REFUSED, no state written); covers update-in-place + import (UntrackedβOwned). Ships the pure query-adapter halves (normalize_live + provider_state::extract_facts_from_eventlog/fold_eventlog) + drift_between. Behavior change (user-confirmed): converging now needs reconcile: enforce. 52 provider + 477 lib tests, clippy --all-targets clean, no libduckdb compile. Prior: π provider tool round 3 β confirm-gated destroy + EHDB ownership projection + endpoint override MERGED + RELEASED (tools#88, v3.23.0 aa32938; ai-meta pointer f6eeb479; #189 OPEN): Fork 2 destroy β folders.delete/projects.delete/services.disable/{organizations,billing_accounts}.iam.remove_binding; a dry-run resolves the target against live state (id/etag/lifecycle/presence) and emits an explicit plan + SHA-256 plan_digest; apply needs confirm:<digest> β blind apply + stale confirm REFUSED, nothing deleted; no wildcard/glob/bulk; needs auth: even to plan; real destroys are a human step (wiremock only). Fork 1 ownership β secret-free provider_fact per mutating converge + pure noetl_tools::provider_state fold (fold_facts/compute_drift/detect_orphans), consuming the raw eventlog tier (/api/ehdb/tiers/eventlog) not the secret-free /api/ehdb/events read-model (#178 collision resolved, no parallel store). Endpoint override β config-level endpoint: reroutes onto a mock/emulator for offline noetl exec --runtime local validation. 39 provider tests (incl. stale-digest-refused, blind-apply-refused, drift/orphan) + 464 lib + clippy clean; wiki Provider-Tool page. Follow-up: query I/O adapter + noetl provider {plan,drift,orphans} CLI; round-4 = drift adoption. Prior: π provider tool round 2 β apply-mode converge + bounded LRO MERGED + RELEASED (tools#87, v3.22.0 eb3d8c7; ai-meta pointer 6cd1ba38): the 4 multi-step ensure/ensure_binding actions (+ services.enable/projects.link) became real, idempotent GET-first converge β read actual, write only if absent, re-run = no-op (changed:false), stateless (no state store); IAM ensure_binding read-modify-write with etag preserved. Bounded inline LRO polling for folder/project create Operations (poll.* bound; descriptive timeout naming the op + resume path; callback overflow documented, not built). wiremock tests (converge + no-op-on-rerun + LRO timeout); 24 provider tests + clippy clean; wiki Provider-Tool page. Scope held: no delete verbs, no drift layer, no live cloud. Prior: π new kind: provider cloud provider tool (Google REST MVP) MERGED + RELEASED β executes the typed Google API operation specs (Cloud Resource Manager v3 / Cloud Billing v1 / Service Usage v1 over reqwest + GcpAuth ADC; zero new heavy deps; rust-sdk deferred β REST) the gcp-org-playbooks emit instead of gcloud; plan vs apply (dry_run default true β would_call echo with no network; apply requires an explicit auth: alias, no ambient ADC); single-request actions execute, multi-step ensure/ensure_binding plan-able + apply-stubbed; secret redaction; aws/azure cross-cloud seam; no worker change; 13 unit tests (tools#86, v3.21.0 cd9d71c; ai-meta pointer e9b3e14e; umbrella #189 OPEN). Prior: π G1 container GPU placement + G2 poll helper MERGED + RELEASED β node_selector/tolerations/volumes on the container tool + a poll-to-terminal helper (tools#82, v3.19.0 f36d020; ai-meta pointer 2e79af6). Prior: π¦ #104 Phase E MERGED β registry::kind_is_side_effecting side-effect classifier the worker durability barrier consumes (conservative default true; only noop/rhai false) (tools#78, v3.17.0 1d49dd5; members noetl-directives + noetl-locator re-published first). Prior: π¦ #104 Phase B β ResultCoordinates::parse/from_locator on the slim noetl-locator (the inverse of logical_uri β recover (tenant, project, execution_id, step, frame, row, attempt) from the worker-stamped URI). Additive, so the member bumped to noetl-locator 0.1.1 (published) + noetl-tools v3.16.0 7da39d8 re-exports it (tools#77). Prior: π¦ #104 Phase A β extract the slim dependency-free noetl-locator crate (noetl-locator 0.1.0, pure std); noetl-tools re-exports it as noetl_tools::locator (tools#76, v3.15.0 dc0c5d8): the Resource Locator (ResourceLocator, ResultCoordinates, shard_key, CellPlacement, legacy-ref parse) is now a lean workspace member so the control-plane noetl-server can parse the canonical result URI without pulling noetl-tools' heavy graph (duckdb/kube/arrow/tonic/rhai). feat(locator): subject β noetl-tools minor + the release CI published the new member noetl-locator 0.1.0 to crates.io (member-publish order: locator before the root). Worker stamp path (noetl_tools::locator::ResultCoordinates) unchanged. Mirrors the lean noetl-directives member (#92). Prior: β
#95 β postgres pg_value_to_json temporal/identity serialization (tools#75, v3.14.2 6d9b674): the postgres tool probed i64/i32/f64/bool/String/json/DateTime<Utc> and fell through to Value::Null for everything else β a tz-naive timestamp (NaiveDateTime) column serialized to null even though the value was present (the auth0_login expires_at: null repro). Added arms for timestamp (ISO-8601, no offset suffix β a tz-naive value carries no zone), date/time, uuid (hyphenated lowercase), numeric/decimal (exact decimal string via a direct lossless decode of the postgres numeric binary wire format), bytea (base64). 409 lib tests + clippy + a kind-gated live-postgres before/after integration test (tests/postgres_temporal_kind.rs). Prior: β
#127 β task_sequence per-sub-task context optimization (behavior-preserving) (tools#74, v3.14.1 c8656c1): the drain rebuilt the template context per sub-task (running_ctx.clone() + 2β4Γ to_template_context() deep-clones + per-block ExecutionContext clones + a fresh context_to_value() per templated field). TemplateEngine::render_value now builds the proxied minijinja context ONCE and threads it through the recursion (render_value_with/render_with; minijinja Value is Arc-backed β reuse is a refcount bump), and new build_context_with_overlay(&variables, overlay) builds straight from &variables + a small overlay, skipping the intermediate HashMap deep-clone. Isolated micro-bench: per-sub-task context cost 2988.9Β΅sβ1147.1Β΅s (β61.6%, 2.6Γ). 407 lib tests + 2 new equivalence pins + clippy clean. Worker adopts via worker#125 (v5.40.4). Closes #127. Prior: β
#125 β task_sequence honours all control-flow directives (do: jump/break/retry) (tools#73, v3.14.0 638c3c6): the Rust runner previously matched only "fail"; do: jump/to:, do: break, do: retry (attempts/backoff + infinite-jump guard) now work. β
#126 β http tool exposes parsed body under data (Python-era contract restored) (tools#72, v3.13.1 8dd0e1f): body was under data.body; now under data with body as a back-compat alias. Together, the two fixes make the 10Γ1000 batch pft_flow_test pass end-to-end on kind. Perf follow-up #127 filed. Prior: #103 β deferred (ack-after-processing) ack (v3.13.0, tools#71): AckMode::Defer in the subscription SourceClient surfaces a durable per-message ack handle (NATS $JS.ACK reply subject) instead of acking inline; SourceClient::ack(ack_ids, AckDisposition) = Ack/Nack/Term (NATS + Pub/Sub); tool operation: ack|nack|term. Opt-in β existing callers unchanged. The capability the worker materializer (v5.34.0) drives for ack-after-materialize. Prior: #90 Phase 4 β store-and-forward spool engine + per-downstream circuit breaker (v3.4.0, tools#54): noetl_tools::spool β circuit breaker (trip/half-open/close, NATS-KV-serializable, per-downstream OQ2), SpoolItem (SHA-256 + noetl://spool ref + recv_seq-ordered keys), nats_object/local_disk backends, ordered-replay engine (global/per_key/none + idempotency + dead-letter + retention/GC). 44 unit tests + real-NATS integration test. Prior: #90 Phase 2 β header-directive engine + public build_source factory (v3.3.0, tools#52): source/directives.rs β DirectiveSpec/DispatchPlan turn allowlisted message headers into dispatch instructions (redirect dispatch.playbook, dispatch.execution_pool, priorityβpool, idempotency_key, content_type/schema_hint, W3C trace), untrusted by default (allowlist + value-allowlist enforced at parse; multi-value last-wins; applied[] audit). Public build_source(cfg, ctx) so the worker continuous runtime constructs the same SourceClient. 12 new tests. Prior (v3.2.0): bounded-drain subscription tool + SourceClient (Phase 1). Prior: Multi-tool sibling references (v3.1.1, tools#48; closes noetl/ai-meta#87): in a tool: [list] step, TaskSequenceTool stored each sub-tool's result for the aggregated output but never injected it into the running context, so a later sub-tool's {{ <label>.<field> }} rendered empty β masked in quoted positions, a syntax error at or near "," in unquoted numeric SQL (save_edge_cases test_large_payload). Fix injects each sub-tool's result under its label (with a synthetic .data self-ref matching build_context) so later siblings + a later python sub-tool's stdin variables resolve it. 2 new unit tests; lib 300/0. Kind-validated: save_edge_cases test_large_payload β record_count = 100, save_delegation_test clean. Worker adopts via worker#69 (b97f642). Prior: e2e-sweep cleanup (v3.1.0, tools#47; tracks noetl/ai-meta#49): YAML boolean when: true in policy rules now checks as_bool() before the string-template fallthrough (Value::Bool as_str() returns None); ` |
noetl/cli |
Rust CLI + local-mode runner |
v4.21.0 (noetl-executor 0.7.0) |
π§© provider round 5 β noetl provider {plan,drift,orphans,adopt} CLI verbs MERGED + RELEASED (cli#68, v4.17.0 a2e5b4d, crates.io-tarball-verified + released-binary-proven; ai-meta pointer cliβa2e5b4d; #189 OPEN): the Terraform-style CLI over the kind: provider tool, local mode β plan=dry-run, drift=reconcile:report, orphans=detect_orphans_scoped+detect_stack_conflicts, adopt=confirm-gated reconcile:adopt. Glue only β reuses ProviderTool::execute + provider_state fold + to_tools_config; URN derivation delegated to the tool; last-known-desired from --server (GET /api/ehdb/tiers/eventlog) or offline --facts-file. Re-pins noetl-tools 3.24β3.25 (HARD compile dep); noetl-executor unchanged at 0.7 (cli#68 touches zero executor/ files β no dead surface, confirmed in the published Cargo.toml). Released-binary proof: noetl-v4.17.0-darwin-arm64 from the release pipeline ran all 4 verbs GET-only zero-writes against the mock. Prior: π§© provider round 4 β local-mode drift adoption + executor 0.7.0 MERGED + RELEASED (cli#66, v4.16.0 7557dde, crates.io-verified; ai-meta pointer cliβ7557dde; #189 OPEN): local-mode reconcile/known_desired passthrough through Tool::Provider + tools_bridge + playbook_runner; re-pin noetl-tools 3.23 β 3.24; noetl-executor 0.6.0 β 0.7.0 (round-4 adds reconcile+known_desired public fields β 0.6.0 was already on crates.io without them and publish-crate skips existing versions, so the bump keeps the released bin from shipping the surface dead). libduckdb-sys unchanged (1.2.2, no fresh C++ compile). release-cli publish-crate GREEN; crates.io-verified noetl 4.16.0 + noetl-executor 0.7.0 live. Local-mode proof re-run on the published 3.24.0 (mock, no live cloud, no mutation): report β drift diff + no fact; adopt dry-run β diff + plan_digest 9bfc9023β¦; matching confirm β adopted (GET-only); STALE confirm β REFUSED "No state was written". Prior: π οΈ release-cli crate-publish FIXED (cli#67, v4.15.1 5bb1705; ai-meta pointer 7d58e7d; #190 OPEN): bumped noetl-executor 0.5.0β0.6.0 (+ bin dep "0.5"β"0.6") so the added Tool::Provider variant ships to crates.io β executor 0.6.0 + noetl 4.15.1 now live (the publish-crate job had failed 2 releases running, crate channel stuck at 4.13.0). apt/homebrew still SKIP (missing repo secrets) but now warn. Prior: π provider round-3 local-mode passthrough + re-pin to noetl-tools 3.23.0 (cli#65, v4.15.0 94cc72a; ai-meta pointer cbc7be99; #189 OPEN): extends Tool::Provider + tools_bridge + playbook_runner with endpoint/stack/confirm so the round-3 fields reach the tool in noetl exec --runtime local; re-pins noetl-tools 3.22.0 β 3.23.0 (libduckdb-sys unchanged β no fresh C++ compile). Local-mode PROVEN on 3.23.0 (mock, no live cloud): endpoint-override apply β services.enable ENABLED β changed:false; destroy dry-run β projects.delete resolved live β plan + plan_digest 6b5efdbbβ¦ + confirm_hint, no DELETE. Prior: π kind: provider dispatches through noetl exec --runtime local (cli#64, v4.14.0 2d46013; ai-meta pointer 32a8fb86): new Tool::Provider enum + tools_bridge dispatch arm + playbook_runner render arm run the same noetl-tools ProviderTool the worker runs β toward "replace Terraform, especially in local mode". Re-pins noetl-tools 3.5.0 β 3.22.0 (libduckdb-sys unchanged β no fresh C++ compile); carries + supersedes cli#63 (duckdb passthrough, closed); adds a duckdb-integration passthrough feature to noetl-executor. Local-mode PROVEN: a --runtime local run emitted the real would_call plans (CRM folders.list GET, serviceusage services.enable POST) with the 3.22.0 converge field, and apply mode entered the converge dispatch gated by the explicit-auth rule (mocks, not live cloud). Prior: noetl subscribe β local-mode subscription listener (RFC #90 Phase 6) (cli#60, ai-meta β 2fb3fb0): standalone listener + FileEventSink JSONL + local_disk spool; cli-only. Prior: --include-data flag doc fix (cli#58) |
noetl/gateway |
Gatekeeper β auth + SSE + push-ingress | v3.12.0 released Β· v3.7.1 in prod |
#90 Phase 3 β push-ingress (Mode C) + auth-gated directive trust (gateway#28): POST /ingress/{listener} verifies HMAC / bearer / Pub-Sub-OIDC β only-then directives β one POST /api/execute per delivery on the dedicated pool (verify-and-forward, no DB on the ingress path); verify_then_plan makes the auth gate a testable invariant; first /metrics surface. Live E2E green (HMAC 12/12 + bearer 12/12). Prior (v3.2.0): Phase F R3b-2 shard-info twin endpoint. |
noetl/noetl |
Python CLI wheel on PyPI (PyO3 over the Rust CLI) + legacy Python tree |
5.0.0 on PyPI (Rust CLI via PyO3, abi3-py39) / v4.12.1 legacy (ecd16a2) |
β
#115 Phase 2 DDL (noetl#667, ecd16a2): canonical schema_ddl.sql prev_event_id columns on noetl.event + noetl.command + idx_event_prev_event_id for fresh installs (the Rust server also ensures them idempotently at startup). Otherwise deprioritized per Rust-only direction; pytest debt at noetl/noetl#663 parked |
noetl/ops |
Helm + manifests | (untagged) |
πͺΆ #104 Phase B β NOETL_RESULT_MATERIALIZER_ENABLED (default false) + single-cell seed NOETL_RESULT_CELL_ENV/REGION/CELL on the kind system-pool deployment; prod manifests untouched (ops#203, c92753c). Prior: k8s-watcher durable image + pod-level OOM classification (ops@cacc513, ops#168; closes noetl/ai-meta#80, tracks noetl/ai-meta#43): retired the dead bitnami/kubectl:1.30.3 (removed from Docker Hub; cluster was on the bitnamilegacy stopgap) for alpine/k8s:1.30.3 (kubectl + jq + curl baked in) β the prior runtime install never put curl on PATH so callback POSTs returned HTTP 000. classify_pod_failure() now reads the backing Pod's status (RBAC already grants pod reads) to emit failed_oom (OOMKilled) / failed_image_pull (ImagePullBackOff); build_body's completed_at fallback uses RFC3339 `now |
noetl/docs |
Docusaurus site | (untagged) | ADR Implementation-status block |
noetl/travel |
Reference SPA (domain-fork example) | 571bf2d |
π©Ή SPA auth-timeout + slow-turn robustness LIVE on prod (2026-07-02, bundle index-CCFdWtGQ.js) β gateway-auth client timeout 15β40s + one silent retry (closes #167) plus friendlyTurnError + checkPlaybookAccess 20s-timeout/retry + a Retry button so a slow turn never surfaces a bare "Load failed" (authorization decisions unchanged) (travel#90 + #91); verified login healthy + "Trip to Paris" renders place_list on the new bundle (no regression); rollback = prior bundle index-Buo8vFKk.js. Prior: π± Mobile responsive layout shipped to prod (2026-06-28) β 3-panel shell reflows to a single column at the md breakpoint with sidebar + Trip-state as drawers (travel#80); deployed via Cloudflare Pages (https://417ab9ee.travel-bgo.pages.dev), verified live at 380px. Prior: all-5-providers-real-widgets (planner v1.5/catalog v51). |
noetl/ehdb |
Event Horizon DB β the storage substrate; both buses in prod | v0.3.0 | Resilience gap is the unsealed tail (RF=1), not replication in general |
noetl/signal-mesh |
Tiered A2A/ReAct agent mesh over the EHDB event log | v0.1.0 (untagged) | π§© split out of ehdb 2026-09-21; design + POC, nothing deployed or enabled |
noetl/catalog |
Catalog model + API over EHDB (playbooks first) | β no release yet (scaffold 089c607, P1 e1aae6d8) |
π created 2026-10-06 β spec + CI 4-of-4 RED-proven + P1 model types (#427) |
The 2026-06-04 session retired all Python deployments + their services + configmaps from the kind cluster (per the user directive "delete all legacy stuff"). Local validation runs against the Rust topology by default:
ββββββββββββββββββββ
β Gateway β noetl/gateway v3.2.0
β (Rust) β auth Β· SSE Β· subscriptions Β· shard routing
ββββββββββ¬ββββββββββ
β HTTPS
ββββββββββΌβββββββββββββββββββββββββββββββ
β noetl-server-rust (v2.19.7) β
β catalog Β· execute Β· events Β· β
β /api/internal Β· DbPoolMap sharding β
β orchestrator engine Β· SSE β
β workbook resolution Β· pipeline parse β
ββββββββββ¬βββββββββββββββββββββββββββββββ
β
ββββββββββΌββββββββββ
β NATS JetStream β NOETL_COMMANDS stream
β + Postgres β noetl.event + noetl.command
ββββββββββ¬ββββββββββ
β
ββββββββββββββββ΄βββββββββββββββ
βΌ βΌ
ββββββββββββββββββββ ββββββββββββββββββββ
β noetl-worker- β β worker-system- β
β rust v5.11.3 β β pool β
β (shared pool) β β (Rust, runs β
β β β system β
β noetl-tools β β playbooks) β
β v2.18.1: β β β
β python Β· shell Β· β β Consumer: β
β http Β· postgres β β ..._pool_system β
β duckdb Β· rhai Β· β β Filter: β
β task_sequence Β· β β noetl.commands. β
β playbook Β· noop β β system.> β
ββββββββββββββββββββ ββββββββββββββββββββ
Consumer: [Outbox publisher +
..._pool_shared projector migrated
Filter: to system playbooks
noetl.commands. via Phase 2.a]
shared.>
Retired this session (2026-06-04): noetl-server (Python
deploy), noetl-worker (Python deploy), noetl-outbox-publisher
(Python deploy), noetl-projector (Python statefulset), and
the noetl/noetl-ext/noetl-projector/noetl-worker-metrics
services + 4 legacy configmaps + noetl-worker SA. The kind
cluster is the regression-test topology for Rust-only e2e.
[Status legend: β = shipped + kind-validated]
Six interlocking gaps fixed in one iteration brought
control_flow_workbook end-to-end on the Rust-only stack β
exercising the complete v10 control-flow surface:
playbook YAML βββΊ noetl-server-rust orchestrator noetl-worker-rust
+ noetl-tools v2.18
βββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββ
workload: {...} βΊ #56 workload + input alias decode PythonTool wrapper
tool.kind: python βΊ (existing dispatch) globals().update(args)
tool.kind: βΊ #17 capture
workbook βΊ #59 parser substitutes inline action `result = {...}`
global β data
tool: [{...}] βΊ #57 ToolDefinition::Pipeline accepts both βΊ #18 TaskSequenceTool
(pipeline) flat (name-as-field) + nested (label-as-key) runtime
{{ step.field }} βΊ #60 build_context exposes step data at top
in next.arcs level (not just steps.<name>); apply_event
captures call.done before command.completed
overwrites
command.failed βΊ #58 trigger_orchestrator on command.failed
+ dedicated short-circuit in process_in_progress
emits playbook.failed terminal
worker β server βΊ #55 EventEmitRequest accepts i64 wire shape
event emission (was rejecting integer execution_id)
Validated end-to-end:
noetl exec tests/fixtures/playbooks/control_flow_workbook
β playbook_started
β start (python)
β eval_flag (workbookβpython; is_hot=true captured via marker)
β hot_path (next.arc when="{{ eval_flag.is_hot == true }}" matched)
β parallel hot_task_a + hot_task_b
β playbook.completed β
Per the Rust-only direction, Python pieces stay only as:
- Container payloads β runtime stays Rust; user code that wants Python ships in a container dispatched by the container tool kind (#43, in design).
- Back-compat GKE deployments β existing Python pods on the production cluster aren't removed (yet) since GKE traffic still uses them; no new feature work goes there.
The kind cluster is the canary for the Rust-only topology. When the operator runs the validation rigs end-to-end against their live cluster, R5 cutover decision will move the production topology to match.
Chronological notes on what each session accomplished β see Sessions Log.
Most recent (top of log):
-
2026-10-08 β β The registry became discoverable, and five measurement gaps closed. ehdb #385 (P2βP5:
discover(kind),watch_since, ephemeral units,SecretRef), #386, #387. Memory is O(parts) not O(records); β β a shared sequencer minted before the append lock reorders 34.2% of appends losing nothing (#362's mechanism in 20 ms); β β a part with 1 of 2 copies is reported durable and done; 530x for batching, and the #298 cap makes a large drain quadratic. Benches now run in CI. 1,188 tests green. β full entry -
2026-10-02 (later) β π Closing out the CI arc β the gate had to be fixed twice, once by itself. β β CHECK 3 forbade correcting a CHECK 2 violation: moving a pointer off a side branch onto the tracked branch is never a fast-forward, so the gate reported a defect and refused the fix (#379). Private submodules removed β coverage 31 of 31, zero unverifiable (#377); #372 CLOSED; clippy now gates on server + worker β measuring first explained the
|| true(#374, sweep #378); docs + doctor PR gates merged. β full entry -
2026-10-02 β π ai-meta had NO CI at all; it now has a gate proven to bite β and the gate's first sweep caught the gate. #371 + #373 merged. Two of three first-sweep findings were its OWN false alarms: it cried "dangling, unpushed, or wrong SHA" at two pointers whose repos are simply private. Coverage on main is 31 of 33, now printed. Real finding: #372. Fleet audit β β β clippy is
|| trueon 5 Rust repos, so it cannot fail (#374); 6 repos with no PR trigger and wikis that cannot host Actions (#375). β full entry -
2026-09-30 (later) β βοΈ #362: the chain is ordered by following LINKS, not by
event_idβ a snowflake minted before insert means an event can commit into the MIDDLE of an id-ordered read. Prod armed on server v3.118.0 + ehdb v0.4.5. β full entry - 2026-09-30 β β©οΈ The chain-store ramp was ROLLED BACK: a third mechanism, and my verification window was too short β size a prod ramp by DURATION, not denominator. β full entry
- 2026-09-04 β β The tier deficit is 318 events, not the ~12k being quoted. Structural mirror lag measured at 0, surplus 0, 52/100 executions affected, 303 inside the #320 window. The ~12k was failed batch deliveries, not a completeness measurement. Repair endpoint (server#399, v3.101.0) merged inert β not run against prod. β full entry
- 2026-09-03 β β The p95 tail was the event-log mirror, not the publish retry β and my published diagnosis was wrong. server v3.100.4 live; inline fallback 24.6% β 8.3%. β the tail is not collapsed and the bistability is not gone. β full entry
-
2026-09-01 β π₯ An 8-hour silent dispatch outage: the cmdbus manifest grew quadratically and filled the PVC while the writer reported
Readywith zero logs. Resolved on prod via worker v5.129.1 β snapshots 172β32, manifest 871β169 MB. β full entry - 2026-08-31 β π The Python platform is gone; #307's real blocker turns out to be a mirror gap. β full entry
-
2026-08-29 β π #307 β the projection fold stops being unmeasurable; prod live in
verify(server v3.92.0). The catalog gets a log, a relation, and full coverage. β full entry -
2026-08-27/28 β π Secret Manager is the sole source. All four
secretKeyRefs removed (server v3.90.0 / worker v5.123.0), each removal digest-checked against the file replacing it first, so the change was plumbing rather than content. Then the #267 sweep: the gateway manifest would have caused a login outage on apply,ServiceAccount/noetl-server-rustwas declared nowhere, and the watchdog script existed only in the cluster. β β #307 projection recovery has ~zero coverage; β β #306 the DR apply would escalate the worker pool. Session details -
2026-08-11 β β±οΈ A cooldown that gated belief instead of retry. EHDB tier
is_reachable()read its own cached-negative deadline as a verdict, so five seconds after a tier service died β with nothing having contacted it β the tier decided it was authoritative again. Belief now lives in a flag only a successful op sets. worker v5.115.3, inert, not deployed. - 2026-08-05 (part 9) β π Observability arc closed. P1+P2 blind spots shut across server, worker and gateway; 38 alert rules proposed (none applied). Two new defects filed: an unbacked-off claim spin (#244) and amd64-only gateway images (#245). Session details
- 2026-08-05 (part 8) β π 149 warn/error sites with no metric, triaged by consequence. P1+P2 closed; P3 (82) deliberately left as logs. The materializer's only data-losing outcome had no counter. Session details
-
2026-08-05 (part 7) β π Absence is the default state of a metric. The prod gateway's
/metricsserved zero bytes; no binary carried a version;*_build_infonow ships in all three. #242 fully dispositioned β 4 dead recorders β 1 deliberate hold. Session details -
2026-08-03 β π‘οΈ P0 prod discovery ran (read-only), and the EHDB-only IaC turned out to be wrong about prod six ways.
state_builderdefaulted toserverwhile prod runsoffserveron the server and both system pools β a converge would have turned a running subsystem off; the threeclaim_*PVC names matched no PVC in prod; a wronguser_pool_containeris rejected, not ignored, so the converge would have aborted half-applied; the gateway lives in nsgateway, notnoetl; and the writer's memory limit would have gone 4Gi β 2Gi. All six are now defaults carrying prod's measured value (ops#245 @5e28543, not merged). Two premises were inverted β prod runs all three materializer groups, so narrowingverify_groupswould have failed on a live one. A seventh defect appeared only by running the adoption path on a kind rig rebuilt into prod's shape:kubectl apply's three-way merge keeps the oldapp:Service selector, so the writer Service resolves to zero endpoints behind a 1/1 Running pod with all nine faces up β a total silent outage cutting off server, both pools, gateway and KEDA. Kind: plan β converge (VERDICT: PASS, PVCs adopted verbatim) β clean idempotent no-op.β οΈ Prod NOT converged; P1βP5 unrun. Session details -
2026-08-02 β π§ͺ First sustained-concurrency soak of the EHDB-only platform (local kind): PASS on every paired-evidence gate β 360 execs,
published 6840 == projected 6840 ==all three group cursors, lag 0, 0 dup / 0 publish errors /cursor_errors=0/out_of_order_appends=0; 3 SSE subscribers saw an identical 7864 frames each; KV 880 ops / 0 mismatches. Three fixes in worker#211 (open, no release cut): H5 fail-loud bus source (#221), the #209 graceful seal (tip 7781 / lag 570 β reopened 7781, all 570 survived), face supervision.β οΈ The hard-kill number could not be measured in kind β masked by consumption lag, so prod is more exposed, not less.β οΈ The bus was never the bottleneck (shard lag 0 while the 8-slot pool saturated) β throughput/latency remain prod-only. Defects: ehdb#311 one bad connection silently kills the WAL face;NOETL_STATE_BUILDER=shadowsilently means Off; no EHDB-only IaC exists at all. ops#241 reviewed, not merged (superseded). -
2026-07-31 β π’ The EHDB-lag autoscaler is LIVE on prod β the T5-readiness sequence executed end to end; NATS untouched, T5 still the human's call. Merged + deployed: ehdb#303 (
ehdb_feed_subject_lag{subject}, with the label set seeded at startup so a drained pool reports0rather than vanishing β a vanished series is a KEDA scaler error, not a scale-to-min) + ehdb#304 β ehdbe457249; server#291 publish retry 0.5 s β 10 s + #292 β v3.58.4; worker#197 + #198 (ehdb_l0_out_of_order_appends, closes #206) β v5.82.0 β v5.83.0; ops#242/#243/#244. Merging #303 and #304 surfaced a silent-failure seam: the pre-existingserve_metrics_with_resumerenders lag throughrender_prometheus, which predates #303 and omits the per-subject family β a writer binding it would have served the resume gauges while dropping the autoscaler's trigger series, with nothing erroring; fixed withserve_snapshot_metrics_with_resume+ a test on each side. Autoscaling proven on prod, on the per-pool metric: the user pool β which had had no autoscaling of any kind since 2026-07-26, because KEDA 2.15 deletes the HPA whilepausedis set β went 2 β 4 β 8 β 16 β 20 under 200 real executions and back to 2 on drain, every rescale attributed tos0-metric-api-ehdb_feed_subject_lag{subject="commands-shared-shard-0"}by name; 200/200 COMPLETED, 0 dup, 0 publish errors. The split earned its keep on the first scrape β shard lag 4 was entirely the system pool's (shared=0, system=2). #208 re-verified live under load: writer restarted mid-burst β 60/60 COMPLETED, 0 publish errors, claimers redialing unaided, the restart legible from one scrape (origin=persisted,clamped=false,replay_records=67= the unacked tail, not a from-zero replay β precisely the distinction #304 was built to make). Soak 6 Γ 40,out_of_order_appends=0throughout.β οΈ Two corrections the plan had wrong. (1) Thenats-jetstreamtrigger was not a harmless no-op:noetl_worker_rust_sharedstill holds the 149 messages frozen at the T4 cutover, KEDA takes MAX across triggers, and it drove the pool to 15 replicas within 40 s of unpausing (every one of those rescales attributed to the NATS metric). Removed in ops#244. At the scaler interface a stale queue is indistinguishable from a busy one β the same failure the per-subject split exists to prevent. (2) T5 would break the SPA today.NOETL_COMMANDS_RUSTis genuinely idle (last message 2d23h) butnoetl_eventshad traffic 3 minutes before the audit, with four live consumers β three worker materializers plus the gateway's SSE subscriptions. That is T3, still unchecked; the gateway fails soft but loses live SPA updates and async callbacks. Inventory:playbooks/210-t5-readiness/pre-t5-checklist.md. Images reached AR bycranecopy from GHCR β Cloud Build'spublish-arfails on every release (build SA cannot stage source in the_cloudbuildbucket) β #211, needs a human IAM grant. ai-meta gitlinksehdbβe457249,serverβ91a4a66,workerβ4c83117,opsβ6469482. - 2026-07-30 (docs sweep) β π§ Issue + wiki reconciliation after the prod deploy. #205 and #208 closed prod-validated; #210 filed for the user pool having no autoscaling since 2026-07-26; #209 annotated with what the seal race actually cost in prod (a latency artefact, not loss β the crash path is the real exposure); #194 given a single consolidated T5 status board; #204β#210 added to the roadmap board. Dashboard, ecosystem map, releases and the ehdb / ops / server / worker wikis brought to server v3.58.3 / worker v5.81.3, and the VictoriaMetrics β Google Managed Prometheus correction applied. No code, no prod action.
-
2026-07-30 (later) β π The last T5 prerequisite β an EHDB-lag KEDA autoscaler for the user pool, so deleting NATS cannot strand it; plus the two #205/#208 prod-rollout cleanups. 4 PRs held (ops#242, ehdb#303, ehdb#304, server#291). Corrected two assumptions β prod monitoring is GMP, not VictoriaMetrics (no in-cluster PromQL endpoint β KEDA
metrics-apiscraping the writer:9102directly, no new IAM), and the exposed lag is whole-shard, not per-pool (a stuck system-pool command would pin the user pool at max βehdb_feed_subject_lag). Scale-up 3 β 6 β 12 β 20 proven in kind on the real metric, then back to min on drain; prod applied paused, unpause staged. T5 HELD. -
2026-07-30 (prod deploy) β π The #301+#302 bundle lands on shastaratech prod: EHDB is now ~2.4Γ faster than NATS, and the bus survives a writer restart β and it turns out it had not been surviving one for 2.4 days. server v3.58.3 / worker v5.81.3 / ehdb
86a24f9in one rollout. Dispatch p50 138.5 / p95 156.4 / p99 181.0 ms (n=60, unsaturated) vs the 338 ms NATS baseline (#205 CLOSED β T5 latency gate GO). Writer pod deleted mid-load β 38/38 COMPLETED, 114 = 38Γ3 commands, 0 dup / 0 loss, ~2.7 s redial, cursor-resume not replay (#208 CLOSED).β οΈ Found on arrival β user-playbook dispatch had been silently dead since 2026-07-28 07:49Z (~2.4 days): the writer restarted, every user-pool claimer wedged on a half-open socket, andnoetl-worker-system-pool-shard1restarting later kept system commands flowing and masked it completely. The T4 sign-off had not survived its first writer restart; this deploy remediated it. Two rough edges left open: the resume log line cannot be told apart from a replay-from-0 (ehdb#304) and 2 Γ HTTP 500 during the writer gap (server#291). NATS untouched; T5 HELD.* -
2026-07-30 β π EHDB command bus survives a writer restart β ai-meta#208 both defects fixed + kind-validated, and a third found; 3 PRs held for review. Branched off ehdb
mainat03e94be(#301). Defect 1 (a writer-only restart wedged dispatch silently βclaim_nextparks on a half-open socket, so the redial path hangs off anErrthat never comes): TCP keepalive on everyehdb-feedsocket (~11 s detection even with no FIN/RST) + a negotiated coordinator heartbeat (one beat up front arms a 3-missed-beat read deadline for the connection; catches a writer that is alive but stuck, which keepalive cannot) β heartbeats only go to a client that asked, so the wire stays backward compatible. Defect 2 (a restarted writer replayed its whole shard from cursor 0 β kind lag 2738 draining at ~1/s):CursorStorepersistscommitted_cursor()atomically on the writer's volume +ClaimCoordinator::resumestarts there (NOETL_COMMAND_BUS_CURSOR_PERSIST_MS,NOETL_COMMAND_BUS_CURSOR_FALLBACK=tail|beginning), the cursor is clamped to the reopened log's tip (a log that lost an unsealed part reopens behind the cursor β without the clamp the bus goes permanently dark), and the writer seals its log on SIGTERM (the engine reopens from its durable manifest, so an unsealed part is invisible after a restart).ehdb_feed_shard_committed{shard}added to the writer:9102β lag reads 0 both when caught up and when a replay just finished, so the cursor is the series to watch. Third defect, found while validating: the server dropped one command per writer restart (EHDB publish failed: early eof+ HTTP 500;command.issueddurable, nothing on the bus) β redial-and-retry, 3 attempts 250 ms apart. Kind before/after, same cluster: pre-fix a writer-onlyrollout restartgave 21 issued / 0 claimed with lag frozen at 3094 for 100 s; post-fix all 3 claimers redialed in ~2 s, the writerresumed from_cursor=3246 origin="persisted"at lag 0, and a writer restarted mid-burst left 30/30 executions completed, 0 dupes, lag β 0. No-regression re-proved: pod-kill redelivery 39/39, pool isolation 0 cross-pool claims (84 + 83 across the two #166 system shards), 0 spurious redials on a 10-min idle bus. ehdb#302 + worker#196 (on #195) + server#290 (on #289) β all held for review; prod bundles #301 + #208 in one deploy. Kind restored toNOETL_COMMAND_BUS=nats; nothing in prod. Follow-up: the SIGTERM seal races in-flight ingest (one command per restart recovered by the orchestrator re-issue ~30 s later β a latency artifact, not loss). -
2026-07-28 β π EHDB is the production command bus β L1 T4 full flip SUCCEEDED (2026-07-27) Β· π #203 silent feed-delivery loss fixed Β· π Python cleanup. The staged Option-2 (single command shard) cutover completed on shastaratech prod with server v3.58.1 / worker v5.81.1: shadow 0 divergence β canary 30/30 (0 system claims, pod-kill redelivery 12/12) β full flip 30/30 + 50/50 soak = 80/80, 0 dup, 0 loss, server
NATS_pub=0, feed lagβ0, #166 state sharding intact. NATS stays installed as the one-command rollback; T5 (delete NATS) held on the human.β οΈ Dispatch latency p50 285β557 ms vs ~200 ms NATS (~1.5β2.8Γ, sub-second) is the T5 go/no-go item β #205;out_of_order_appendsnot on writer:9102β #206. The flip needed a real bug fixed first: producer-assigned sparse snowflake ids arriving out of order past the follower cursor were filtered out of the feed forever β silently,lag=0(~10% loss); fix = the writer assigns the ordering key (ehdb#300 β ehdbd4b6235), kind 17/40 β 41/41, then prod-validated β #203 CLOSED; #202 CLOSED (Option 2 executed; the per-shard user-pool split was never needed). Releases: worker v5.79.0/v5.80.0/v5.81.0/v5.81.1, server v3.58.1. Python: 5.0.0 on PyPI, shims removed (noetl#693), legacy PyPI landmine retired (noetl#694 βpypi.yml/pypi-v*is now the sole publisher); the big server/worker/outbox retirement is BLOCKED onnoetl/core's imports β #207. Infra: Cloud Build β Artifact Registry (#204, worker#194 + server#288 OPEN, amd64-only by design). ai-meta gitlink bumps + RFC write-ups left PENDING (dirty shared tree); wikis + issues updated directly.* -
2026-07-26 β π Prod migrated to the shastaratech org + cut over Β· π noetl 5.0.0 on PyPI Β· π EHDB L1 T4 executed on prod (held at canary). Prod stack moved to GKE Autopilot
noetl-prod-autopilot(us-central1,shastaratech-noetl-prod) offnoetl-demo-19700101; billing unblocked (Free-Trialβpaid +noetl-devβnoetl-prodslot swap); images/IAM-WI/10 secrets/NOETL_ENCRYPTION_KEY(19/19 decrypt)/DBs migrated; cloudflared connector flip = 25/25 external/health200 zero-blip, old prod left warm (shastaratech/gcp-org-playbooks#13, billing #9). noetl 5.0.0 LIVE on PyPI β Rust CLI via PyO3 (abi3-py39), 4 artifacts (macOS arm64 + Linux x86_64/aarch64 + sdist; Windows/Intel-Mac dropped); cli v4.20.0 (lib extraction) + v4.21.0 (DuckDB feature-gate); Stage-4 retirement evidence-only (#201). EHDB L1 T4 command-bus cutover on prod: Step 0 + Shadow PASS (0 divergence,121βNATS==121βEHDB, 2 shards), HELD at canary on the per-shard user-pool blocker β bus back on NATS, prod clean; server v3.58.0 / worker v5.81.0 + both writers deployed (inert); two paths in #202; IaCplaybooks/194-l1-t4-prod-iac/(#194). Write-behind sink slices A/B/C merged default-off (worker#190/#191/#192, #199); release-red #200 CLOSED. ai-meta gitlink pointer bumps + RFC write-ups left PENDING (busy shared tree; wikis pushed directly). -
2026-07-14 β π§© Provider tool round 5 β
noetl provider {plan,drift,orphans,adopt}CLI verbs + query-tier fetch adapter + stack-scoped ownership, merged + released. tools#90 β noetl-tools v3.25.0 (5a06c7e):extract_facts_from_tier_records/fold_tier_records(wire-shape eventlog-tier fold β unwrap thepayloadstring + recursive fact search; fixes the round-3/4 fixed-path bug βextract_facts_from_eventlognever matched the realcontext.result.context.data.provider_factnesting, so a fold over real data on 3.23/3.24 silently returned nothing) +OwnershipModel::for_stack/detect_orphans_scoped/detect_stack_conflicts. cli#68 β noetl-cli v4.17.0 (a2e5b4d): thenoetl providerverbs (Terraform-style CLI over the tool, local mode; glue overProviderTool::execute+provider_state+to_tools_config); re-pin noetl-tools 3.24β3.25; noetl-executor unchanged at 0.7 (zeroexecutor/files touched β version-bump discipline held). crates.io tarball-verified + released-binary proof (noetl-v4.17.0-darwin-arm64, all 4 verbs GET-only zero-writes against the mock; drift over HTTP--server; adopt dry-runβdigestβapplyβstale-refusal). Follow-up 3 (first-class event type) DEFERRED (projection column rewrites the secret-free invariant tests + rebuilds server/worker). ai-meta gitlinkstoolsβ5a06c7e+cliβa2e5b4d. Umbrella #189 OPEN. No prod/kind/live-cloud β mocks + unit tests only. -
2026-07-13 β π§© Provider tool round 4 β drift adoption (
reconcilepolicy report/enforce/adopt + confirm-gated adopt), merged + released. tools#89 β noetl-tools v3.24.0 (c172efb); cli#66 β noetl-cli v4.16.0 + noetl-executor 0.7.0 (7557dde).reportnew-default (converge needsreconcile:enforce);adoptaccepts live actual as new desired, read/GET-only, confirm-gated like destroy (blind+stale REFUSED). All three crates crates.io-verified. ai-meta gitlinkstoolsβc172efb+cliβ7557dde. Umbrella #189 OPEN. -
2026-07-13 β π Provider tool round 3 β confirm-gated destroy + EHDB ownership projection + endpoint override, merged + released. tools#88 β noetl-tools v3.23.0 (
aa32938): destroy verbs (*.delete/services.disable/*.iam.remove_binding) gated by a live-resolved SHA-256plan_digest(dry-run emits plan + digest; apply needsconfirm:<digest>; blind apply + stale digest REFUSED, nothing deleted; no wildcard/bulk; needsauth:even to plan; real destroys human-run); secret-freeprovider_factper converge + pureprovider_statefold (fold_facts/compute_drift/detect_orphans) over the raw eventlog tier (not the secret-free/api/ehdb/eventsread-model β #178 collision resolved); config-levelendpoint:override for offline mock/emulator runs. cli#65 β noetl-cli v4.15.0 (94cc72a): local-modeendpoint/stack/confirmpassthrough + re-pin to noetl-tools 3.23.0 (no fresh libduckdb-sys compile). Local-mode PROVEN on 3.23.0 (mock, no live cloud): endpoint-override apply β no-op; destroy dry-run β plan +plan_digest 6b5efdbbβ¦, no DELETE. 39 provider tests + 464 lib + clippy clean. ai-meta gitlinkstoolsβaa32938(f6eeb479) +cliβ94cc72a(cbc7be99). Umbrella #189 OPEN β round-4 = drift adoption. No prod/kind/live-cloud, no real destroys β mocks + unit tests only. -
2026-07-13 β π Provider tool round 2 β apply-mode converge + bounded LRO + local-mode CLI dispatch, merged + released. tools#87 β noetl-tools v3.22.0 (
eb3d8c7): idempotent GET-first converge for the 4ensure/ensure_bindingactions (+services.enable/projects.link, re-run = no-op) + bounded inline LRO polling (descriptive timeout + documented callback overflow). cli#64 β noetl-cli v4.14.0 (2d46013):kind: providerdispatches throughnoetl exec --runtime local(same tool the worker runs), re-pinned to noetl-tools 3.22.0 (libduckdb-sys unchanged β no fresh compile), supersedes cli#63 (closed). Local-mode PROVEN (dry-runwould_call+ apply-mode converge entry; mocks not live cloud). ai-meta gitlinkstoolsβeb3d8c7(6cd1ba38) +cliβ2d46013(32a8fb86). Both design forks DECIDED for round 3 (EHDB-backed state; confirm-gated destroy). Umbrella #189 OPEN. No prod/kind/live-cloud β mocks + unit tests only. -
2026-07-13 β π NoETL cloud provider tool (
kind: provider) β Google REST MVP designed β implemented β merged + released noetl-tools v3.21.0 (tools#865e427fb, releasecd9d71c); ai-meta pointere9b3e14e; umbrella #189 OPEN (round-1). No prod/kind/GCP β impl + unit tests only. -
2026-07-10 β ποΈ EHDB segment-GC limits-based retention β a shadow store can self-bound without a consumer. Merged noetl/ehdb#271 β ehdb main
49db503(engine) + noetl/worker#178 (knob) + noetl/worker#179 β worker main521fd10(re-pin ehdb to merged #271; #178 pinned the pre-merge branch commit). Keep-last-N (count-based) retention reclaims independent of consumer interest;plan_reclaimcomposes it asmin(retention_target, interest_ceiling)β never reclaims ahead of a real consumer, drives when there is none,min_retained_segmentsis the hard floor; the only engine change is insideplan_reclaim(watermark-first commit / shared-tier coherence / per-shard lock all inherit unchanged). Keep-last-N not max-age (mtime resets on hydrate/cold-load). ConfigNOETL_EHDB_EVENTLOG_GC_MAX_RETAINED_SEGMENTS+ workerNOETL_EHDB_EVENTLOG_SEGMENT_MAX_BYTES. 251 tests (+3 retention: self-bounds a consumer-less store / never reclaims ahead of a lagging consumer / respects the min floor); clippy+fmt clean; bench variant. Organic in-kind soak PROVEN: live store 19β2 segments / 2,147 B (from ~22 MB) under retention with no consumer, held flat;reclaimed=6, 0 restarts, replay intact. LOCAL kind only β no GKE/prod;noetl.eventnever purged. ai-meta gitlink bumprepos/ehdbβ49db503+repos/workerβ521fd10. -
2026-07-10 β ποΈ EHDB segment-GC worker wiring + in-kind soak β D11 FULLY CLOSED (mechanism + deployed exercise). Merged noetl/worker#177 β worker main
b25263f: a periodic per-owned-shardeventlog_gctask (local + sharedreclaim_shard) onspawn_blocking, gated off on every axis; a process-global per-shard advisory lock serializes GC against the per-op append path (resolves the two-writers-per-shard fork + closes a latent appendβappend race).noetl_ehdb_eventlog_gc_*metrics;ehdb-selfcheck durable-eventlog-gc [--drive 1]. In-kind soak (v5.72.3-ehdbgc254, user pool,/ehdb-durablePVC, GC + interval=30s): periodic task spawned + healthy (eventlog_gc_ops_total{outcome="noop"},last_ok=1β correct noop, shadow store has no consumer);--drive 1on the real PVC reclaimed shared 40β10 at watermark 30 (gc_holds, replay/cold-load/new-owner-hydrate coherent); 0 restarts; user+system pools uniform. Fork surfaced: the deployedshadowevent-log has no durable consumer β interest-based GC reclaims nothing organically (correct); a limits-based retention mode (keep-last-N/max-age) for shadow, or a primary-consumer, is the one remaining optional follow-up. LOCAL kind only β no GKE/prod;noetl.eventnever purged. ehdb-wikid68b988; ai-meta gitlink bumprepos/workerβb25263f. -
2026-07-09 β ποΈ EHDB shared-tier segment GC β coherent reclamation across the shared medium (D11 done for both topologies). Merged noetl/ehdb#270 β ehdb main
adca378: a monotonic per-shard cross-replica reclaim watermark on the shared medium;SharedTierEventLog::reclaim_shardreclaims local + shared watermark-first (reconcile β plan β commit watermark β reclaim local β delete shared), socold_load/hydrate_owned_shardskip<= watermarkand never re-pull a reclaimed segment (no un-reclaim on transfer);reconcile_owned_shardrealigns a crashed owner. Store refactored intoplan_reclaim(decision) +apply_reclaim/reclaim_to_segment(mutation). 248 tests (+5 shared-GC incl. crash-window reconcile); CLIdurable-eventlog-shared-gcpruned shared 40β10 objects at watermark 30, cold-load + new-owner hydrate coherent; bench ~25 ms@50 / ~38 ms@150. Off by default + not auto-invoked. D11: GAP β delivered + drive-tested for BOTH single-writer local (PVC) AND shared-medium β remaining is operational (worker wiring + in-cluster soak). LOCAL/engine only β no GKE/prod;noetl.eventnever purged. ehdb-wiki94afe29; ehdb#254 comment; ai-meta gitlink bumprepos/ehdbβadca378+repos/ehdb-wikiβ94afe29. -
2026-07-09 β ποΈ EHDB durable event-log segment GC β the R1 / D11 gap (Stage-C blocker) closed for single-writer local. Merged noetl/ehdb#269 β ehdb main
93f3060: interest-based (consumer-ack watermark) reclamation of consumed sealed segments behindNOETL_EHDB_EVENTLOG_GC=consumer_ack(off by default). Base-offset (retained gapless-from-reclaimed+1;reclaimed_through==0β byte-identical to pre-GC), durablefsync'dreclaim.jsonmanifest = crash-atomic commit point (crash mid-GC re-deletes below-watermark leftovers on next open), write-forward of consumerAckframes before unlink. Fixedtail()pending to the absolute tip. 242 tests (+10 GC incl. crash-mid-GC-via-manifest), clippy/fmt clean; CLI drive 41β11 segments at watermark 30. D11: GAP β delivered + proven for single-writer local (PVC), unblocking the Stage-C durable-primary consideration for that topology; shared-tier object reclamation is the one remaining follow-up (started this session). LOCAL/engine only β no GKE/prod;noetl.eventnever purged. ehdb-wikie830957; ehdb#254 comment; ai-meta gitlink bumprepos/ehdbβ93f3060+repos/ehdb-wikiβe830957. -
2026-07-04 β π #169 Auth0 JWT signature verification β SHADOW canary LIVE on prod (server v3.50.0βv3.52.0, single replica,
NOETL_AUTH_VERIFY_SIGNATURE=shadow); focused finish handover Handover-169-JWT published (docs/state-sync only β no code/prod/flag change). -
2026-06-29 β ποΈ #164 Muno ACTIVITIES render fixed on prod β planner v59 (cid
660442656010404734, rollback v58660417115383399292). The activities turn rendered blank (render: null) though the provider returned 10 real activities:render_widget_chatcrossed the ~100 KB result-tier offload threshold βnoetl://ref that 404s on prod (#154). The 10 activity cards were duplicated ~6Γ (widgets/first_widget/final_slot_state/post_docs/two post_events), each ~1.2 KB dominated by a raw[:600]HTML description (75 KB render on a fresh thread). Fix travel#88:_activity_cardstrips HTML β short plain-text (160 ch) + modalities 5β2 β card ~0.57 KB, render 75.3β39.5 KB. No widget-schema/FE change (CLOSEDactivity_list.schema.jsonstill validates). Prod-verified: realactivity_list(10 activities, no synthetic, no offload, schema PASS); places/hotels/flights/login unaffected; 0 restarts. #164 open until travel#88 merges + bump; refs #155. -
2026-06-29 β π¨ #162 Muno HOTELS bookable end-to-end on prod β planner v58 (cid
660417115383399292) + HotelBeds MCP v4 (cid660409660586853242,book_hotelcheckRateβbookings, TEST/SANDBOX-guarded). Verified: real TEST booking refs197-14375473/74/76(CONFIRMED,env=test) βhotel_confirmation; search + Trip-to-Paris + login healthy; 0 restarts. Rollback = planner v57 cid660221263901623160. Closes #162; refs #155. -
2026-06-28 β π #157 Muno hotels-turn drive wedge fixed forward + DEPLOYED β worker v5.47.3. A hotels follow-up ("What hotels are in Paris?") wedged in RUNNING because the hotelbeds render-hop
__orchestrate__drive result (~138KB) crossed the 100KB inline budget and offloaded to a result-store ref the server can't resolve underNOETL_RESULT_STORE_DUAL_WRITE=false(#104) β a #154-class wedge. worker#141 (squashf8f4d12, released v5.47.39f83ab5) keeps the__orchestrate__result inline regardless of size. Bumped ai-metarepos/worker5.47.2β5.47.3 (60938f4), rolled both pools new-pod-first (0 restarts, materializer balanced/lag-0, Fix A NATS reconnect 0/30s intact). Prod-verified: hotels exec329775581438681088COMPLETES +hotel_listwidget; places no-regression exec329775627576025088COMPLETES +place_list. Worker-behavior-only β no OQ5/result_store/IAM/shadow change; rollback:5.47.2. Closed #157, referenced #155. -
2026-06-28 β π± Muno mobile responsive layout SHIPPED to prod β the live travel SPA was still serving the broken overlapping 3-column shell on mobile because the fix had been built but never deployed. Merged travel#80 (squash β
a8e78f1): reflows the 3-panel shell to a single column at themdbreakpoint, sidebar + Trip-state become temporary MUI drawers (hamburger β° + luggage π§³ buttons). The merge triggered the repo's Cloudflare Pages workflow (run 28329391574) β prod deployhttps://417ab9ee.travel-bgo.pages.dev(bundleindex-0naZf1z6.js). Verified live signed-in at 380px: full-width single-column chat, sidebar/Trip-state as left/right drawers with backdrop, composer full-width + Send reachable; desktop still 3-column. Minor nit: 16px composer-<form>overflow (cosmetic, follow-up). ai-metarepos/travel79be476βa8e78f1; SPA + Cloudflare Pages only (no backend/OQ5/IAM/GKE). -
2026-06-27 β π #104 result_store dual-write RETIRED on prod β server v3.47.0 (server#267,
bd831e6, image@sha256:140066d9β¦) deployed behavior-neutral (flag default-on), thenNOETL_RESULT_STORE_DUAL_WRITE=falseflipped. Over-budget result_store writes stopped (dual_write_skipped_totalclimbs,put_total/dual_write_totalflat); the #104 tier serves over-budget results by URN with 0 resolve errors; execs COMPLETE; sole-writer balanced 142==142==142 / dup-0 / lag-0; server + system-pool 0 restarts. Existing result_store data frozen (not deleted); revert = flagβtrue(deeper:RESULT_MINT_AUTHORITATIVE=false; image rollback v3.46.0@sha256:6254838β¦). Probemuno/oq5-retire/test/overbudget. ai-meta server pointer β v3.47.0. -
2026-06-26 β π§± SLM platform foundation G1/G2/G3 merged + released (review-only): tools v3.19.0 (container GPU placement + poll-to-terminal helper, #82), worker v5.47.0 (watcher-free poll completion fallback, flag
NOETL_CONTAINER_COMPLETION_POLLdefault-off, #139), server v3.46.0 (versioned model/dataset/eval registry, flagNOETL_REGISTRY_ENABLEDdefault-off, #266); ai-meta pointer bump2e79af6. Plus the SLM teacher swapped OpenAIβVertex Gemini (gemini-2.5-proβflash fallback, Workload-Identity metadata token mint, no API key/secret) β ops #216 + travel #75 open on the Phase-A track. ops#214 held (stacked on the ops SLM branch). No prod deploy β both runtime flags ship default-off. -
2026-06-25 β π§ Domain-Specific SLM platform Phase A (#140) landed β
dataset_build+evalshipped as generic, config-driven NoETL playbooks (MLOps-as-playbooks). ops PR #212 (genericautomation/mlops/slmpack +lib/engines + toy 2nd domain) + travel PR noetl/travel#73 (travel instance: deterministic oracle as zero-cost label source, 45-turn seed corpus, contract schemas). Config-only proven (RFC Β§2.2 extraction test: a support-triage toy domain runs the same playbooks zero-edits). Baseline (45 turns β 29/16): 100% schema validity, eval gate passed, floor latency sub-ms, 9 intents/4 tools/10 widget types. Validated vianoetl exec -r local. OpenAI ceiling deferred (teacher budget); finetune/serve gated on G1/G2/G3; model-choice deferred. Review-only, no prod change. - 2026-06-25 β π§ Domain-Specific SLM platform RFC + umbrella (#139) + 11 children filed (design only).
-
2026-06-23 β πͺΆ #104 OQ5 Option A SHIPPED β producer-staged result tier (worker v5.46.0); decouples the over-budget tier write from
result_store(flag default-off). worker#132 + e2e#81 + docs#186 merged, ai-meta pointer #128; ops#206 soak-gate rules open.result_store-retirement soak defined but NOT started (gated on a prod producer-stage flip). -
2026-06-23 β π #104 β WI/ADC GCS auth MERGED (server#265,
fad5d8aβ noetl-server v3.45.0) + ROLLED TO PROD GKE: server runs as the WI-bound KSAnoetl-server-rust(@sha256:d3cbf1adβ¦) with the result-tier GCS ENV applied (OBJECT_STORE_BACKEND=gcs, bucketnoetl-demo-19700101-results,auth=adc, cellusc1-a/256);/api/internal/cellsreads it back; all tier-enable flags stay OFF β tier inert. Server healthyauth=adc(no token minted β lazy), off-server cutover sole-writer/lag-0/never-scan (smoke +system/scheduled_cleanupCOMPLETED, published==acked=13, chain clean), 0 restarts. Prod configured + WI-live, ready for staged BβCβD enablement. -
2026-06-23 β πͺΆ #104 Phases E + F MERGED β the FINAL build phases (side-effect durability barrier + result-tier GC + DR), repo-only, flags default-off β inert in prod. Phase E: tools#78 β noetl-tools v3.17.0 (
registry::kind_is_side_effecting, members noetl-directives + noetl-locator re-published first), worker#130 barrier β v5.44.0 (repointed onto published 3.17, no patch), e2e#79. Phase F: server#264 GC sweeper β noetl-server v3.44.0, worker#131 DR re-derive β noetl-worker v5.45.0, ops#205 + e2e#80. ai-meta pointers + deployment-spec wiki rows bumped. AβF build phases complete; #104 stays OPEN (operational items remain). -
2026-06-23 β πͺΆ #104 Phase D MERGED β the minting flip (dual-write window). All 4 PRs squash-merged in dependency order (server β worker β ops β e2e), flags default-off β inert in prod. Repo-only; prod minting cutover is a separate next task.
NOETL_RESULT_MINT_AUTHORITATIVEmakes the URN β Feather/GCS tier the authoritative result store withnoetl.result_storekept as the reversible dual-write fallback. No new crate publish β no Cargo.toml dep change in either PR; server resolvesnoetl-locator 0.1.1, worker resolves from the registry. semantic-release cut noetl-server v3.43.0 + noetl-worker v5.43.0: server#263 β v3.43.06f6b9ef, worker#129 β v5.43.0be6863a, ops#204b19b759, e2e#7807e85aa. ai-meta pointers bumped. OQ5 βresult_storeretirement β DECIDED metric-gated (drop dual-write oncemint_authoritative_total{path=legacy_fallback}holds 0 across a staging soak + retention time floor), gated on a not-yet-done byte-source re-plumbing prerequisite (materializer fetches the payload fromresult_storetoday). #104 stays OPEN (Phase E/F + minting prod-cutover remain). -
2026-06-23 β πͺΆ #104 Phase C MERGED β resolve-by-URN read path (server GCS object backend + cell registry +
GET /api/internal/cells; worker resolve-by-URN + fixes B/B1, closes OQ6). All flags default-off β inert in prod. Merged in dependency order (server β worker β e2e): server#262 β noetl-server v3.42.0c2d5ca9, worker#128 β noetl-worker v5.42.07971041(38 unit tests), e2e#7739dc880(3-pass rig + fake-gcs). No new crate publish β worker resolves noetl-tools 3.16.0 + adds publishedarrow=53; server resolves noetl-locator 0.1.1, both from the registry (no repoint). ai-meta pointers bumped; #104 stays OPEN (Phase D minting flip remains). -
2026-06-22 β πͺΆ #104 Phase B MERGED β shadow Feather result tier (5 PRs, flags default-off β inert in prod). Merged in dependency order: tools#77 (β noetl-tools v3.16.0 + noetl-locator 0.1.1 published; member bumped 0.1.0β0.1.1 pre-merge so the additive
parse/from_locatorAPI publishes) Β· server#261 (β v3.41.0, siblingnoetl_result_materializerconsumer) Β· worker#127 (β v5.41.0, the consume-loop) Β· ops#203 (flag + cell seed) Β· e2e#76 (kind rig). No downstream repoint needed (both resolve from the registry, no git/branch dep). ai-meta pointers bumped; #104 stays OPEN (CβF remain). Session details -
2026-06-22 β π¦ #104 Phase A MERGED β slim
noetl-locator 0.1.0extracted + published to crates.io; server accepts the canonical result URI behindNOETL_RESULT_URI_ACCEPT(default off). Repo-only, no prod deploy. Squash-merged tools#76 with afeat(locator):subject β noetl-tools v3.15.0 (dc0c5d8) and the release CI published the new workspace membernoetl-locator 0.1.0(member-publish order: locator before the root crate). Repointed server#260's temporary git-dep tonoetl-locator = "0.1.0", confirmedcargo build/cargo treeresolve from the registry with the heavy graph absent (duckdb/kube/arrow/tonic/rhai/gcp_auth = 0), 623 tests green; squash-merged β noetl-server v3.40.0 (c89d078) + the e2e#75 kind rig (eeca8b7). Resolves the umbrella's OQ7. Flag default-off β inert in prod until a later server rollout. ai-meta pointers bumped (repos/toolsβdc0c5d8,repos/serverβc89d078,repos/e2eβeeca8b7); #104 stays OPEN. -
2026-06-22 β π§Ή CQRS / off-server program close-out + tracking reconciliation (repo-only, no prod changes). Merged e2e#74 (squash
1deadf1, the prod-scoped Rust regression validator, 28/30 against live prod) + bumpedrepos/e2e. Closed #103 (CQRS event log β prod cutover live + validated; e2e#74 was its last piece), #102 (server#198/#199 landed; worker-side superseded by #103), #101 (acceptance met + kind-validated; consume side moved to #115 Phase 1). Closed superseded Python ops PRs #189/#190/#192 (replaced by the Rust in-process worker materializernoetl-worker/src/materializer.rs). Archived the completed2026-06-18-orchestrate-plugin-dissolution+ stale2026-06-09-rust-stack-session-snapshothandoffs. Reconciled roadmap board 3 + this Active-umbrellas table. Fixed the failingrelease-serverCI (cargo publishβ added a version to thenoetl-orchestrate-corepath dep + publish the member first, mirroring noetl/tools). -
2026-06-22 β π #95 CLOSED β postgres
pg_value_to_jsontemporal/identity serialization shipped to prod end-to-end. The Rust postgres tool returnednullfor a tz-naivetimestamp(NaiveDateTime) column plus date/time/uuid/numeric/bytea (theauth0_loginexpires_at: nullrepro). Merged tools#75 (squash06302acβ semantic-release noetl-tools v3.14.26d9b674, published to crates.io) + worker#126 pin bump3.14.1β3.14.2(squash60a849dβ noetl-worker v5.40.5da24952). Built the prod image via Cloud Build us-central1 (noetl-worker-rust:v5.40.5@sha256:45212dbeβ¦) and rolled it by digest onto prodnoetl-worker-rust+noetl-worker-system-pool(server stays v3.39.5, CPU 250m/2 kept) β rolling restart clean (2/2 + 1/1, 0 crashloop), off-server CQRS cutover stayed healthy (materializer sole-writer lag 0, command lag 0, project_errors 0, WAL index rehydrated 5 execs/198 events per #119). ai-metarepos/toolsβ6d9b674+repos/workerβda24952. -
2026-06-22 β β
#127 CLOSED β the
task_sequenceper-sub-task context optimization merged, released, and shipped to prod; the code-opt + CPU-limit bump now compound on the live off-server cutover. Merged tools#74 (squash9dd9aa6β semantic-release noetl-tools v3.14.1c8656c1, published to crates.io) βrender_valuebuilds the proxied minijinja context ONCE + threads it through the recursion (render_value_with/render_with), and newbuild_context_with_overlayskips the redundantto_template_context()HashMap clone + per-blockExecutionContextclones; micro-bench β61.6%/2.6Γ per-sub-task context cost (407 tests + 2 equivalence pins + clippy). Bumped the workernoetl-toolspin3.14β3.14.1(worker#125, squash1a10a73β noetl-worker v5.40.40afbf5c; deps-only, no worker source change, clippy clean). Built the prod image via Cloud Build (us-central1-docker.pkg.dev/noetl-demo-19700101/noetl/noetl-worker-rust:0afbf5c) and rolled it onto prodnoetl-worker-rust+noetl-worker-system-pool(server stays v3.39.5/.6, worker CPU limits kept req 250m/limit 2). Rolling restart clean (pods Ready, 0 crashloop), and the off-server CQRS cutover stayed healthy throughout β materializer sole-writer projected==acked, lag ~0, command lag 0, executions completing, system-pool rehydrated its WAL index (#119). ai-metarepos/toolsβc8656c1+repos/workerβ0afbf5cpointers bumped + wiki. #127 closed + board 3 β Done. -
2026-06-22 β β‘ #127 PROD WORKER CPU LIMIT RAISED 1β2 + APPLIED LIVE β the ~20% batch-throughput win materialized on prod. ops#202 (
85e3c23) raised request100mβ250m/ limit1β2on bothnoetl-worker-rust+noetl-worker-system-pool; ai-metarepos/opspointer bumped. Kind profiling found the step workers pegged one CPU and spent 38β47% throttled at the 1-CPU limit (per-sub-taskrunning_ctx.clone()+to_template_context()hot path); 2 CPU cut a 10k batch166β172sβ137.6s (~20%, zero patient loss). Live prod baseline confirmed 1 CPU (kind had 0.5); nodes 4Γek-standard-16(~63.5 CPU) at ~2% β headroom. Applied viakubectl set resources: rolling restart clean (all pods Ready, 0 restarts), off-server CQRS cutover stayed healthy (materializer sole-writer lag 0, WAL index rehydrated). PROD gate vars / image / DB untouched; the deeper context-clone hot path is the structural follow-up. #127 stays OPEN. -
2026-06-22 β β
#123 SHIPPED + CLOSED β a non-iterable loop
in:no longer silently wedges (commands=0, RUNNING forever); the off-server drive now decodes the{"error":β¦}envelope and emits a terminalplaybook.failed(server v3.39.6). server#258 (squash275b914β v3.39.67f109a9):apply_worker_orchestrationdecodes the drive ERROR envelope (decode_orchestrate_error) and emits the terminal failure (metricnoetl_orchestrate_drive_total{stage="drive_error"}, structuredexecution_id), matching the in-process drive; orchestrate-core prefixes the offending step name onto the existingevaluate_looperror. An empty iterable ([]/{}) still short-circuits tonext. 600 server + 135 orchestrate-core tests + clippy clean; kind-validated prod-exact (absentworkload.batch_slotsβ FAILED with a clear message, valid[1,2,3]still COMPLETED 3-way, #120/#124 unaffected, 0 restarts). Code-only fix (worker stays v5.40.3); ai-meta pointer bumped β v3.39.6; #123 closed + board 3 β Done. PROD stays healthy on v3.39.5 + the live off-server cutover (NOT redeployed β ships to prod on a future server rollout). #127 stays OPEN. -
2026-06-22 β β
#121 SECOND-HALF SHIPPED + CLOSED β off-server
system/*WAL-chain wedge fully fixed (server v3.39.5). server#257 (squash54ac277β v3.39.5c421273) gates BOTH off-server-drive sites intrigger_orchestrator_inneronshould_publish(catalog_id)sosystem/*execs drive server-builtrun_state; regular execs keep the off-server path. #256 was only the first half (#121 reopened after the v3.39.4 prod re-cutover still wedgedsystem/scheduled_cleanupon the system-pool worker). Pureis_system_pathhelper + unit test; 612 tests + clippy; kind full-gate BEFORE 9ΓWAL chain incompleteβ AFTER COMPLETED 0 loops + regular loop still off-server. Server-only (worker stays v5.40.3); ai-meta pointer bumped; #121 closed + board 3 β Done. PROD re-cutover = Phase-B follow-up this session.* -
2026-06-21 β β
#121 first-half SHIPPED (partial) β off-server WAL-chain-incomplete loop on
system/execs (link gate-offcommand.claimedthroughChainHeads+ don't routesystem/execs to the off-server WAL drive); server v3.39.4; server-only; live prod off-server cutover wedge is the real-world repro. -
2026-06-21 β β
#125 + #126 SHIPPED + CLOSED β
task_sequencedo: jump/break/retrycontrol flow +httptool body data-shape fixed (noetl-tools v3.14.0 tools#73 + v3.13.1 tools#72); worker v5.40.3 adopts the fixes (worker#124). Full 10Γ1000 batchpft_flow_testnow passes end-to-end on kind, correctness-clean (zero patient loss). Perf follow-up #127 filed. #121/#123 stay OPEN. -
2026-06-21 β β
#124 SHIPPED + CLOSED β distributed
task_sequenceforwardset:/sibling bindings no longer render empty; defer sub-task templates with unresolved vars to the worker re-render (render_value_deferring_unresolved); server v3.39.3 (server#255). #121/#123/#125/#126 stay OPEN. -
2026-06-21 β β
#120 SHIPPED + CLOSED β reduce barrier no longer deadlocks (
commands=0) on open/asymmetric loop joins (orchestrate-core); runtime liveness filter in the barrier (server v3.39.228e8950). server#254 (squashfbb855f); testtest_open_loop_back_edge_does_not_block_dispatch, 133/133 + clippy, kind-validated 2Γ2 matrix COMPLETE; PROD/defaults untouched. #121/#123 stay OPEN. -
2026-06-20 β β
#116 program-scale step 2 SHIPPED + multi-replica gate-ON validated β execution-affinity single-owner WRITE ORDERING. server#252 v3.39.0 + e2e#71. Step 1 (KV coherence) was necessary-not-sufficient β the
command.issuedprev-read + head CAS-advance are two non-atomic steps, so concurrent cross-replica emits forked the chain. Affinity routes every trigger (POST /api/events, which fires the drive) to the single replica thatShardConfig::owns(execution_id)owns; a non-owner forwards a reverse-proxy POST (one-hop loop guard, degrade-to-local). On the owner the single-process drive lock + in-memoryChainHeadsmake the readβadvance atomic, no distributed lock; KV is the genesis/handoff vehicle (owner resolves LOCAL βkv_remote_hitβ0). server#252 (5e00d0a, v3.39.0):src/affinity.rs, flagsNOETL_EXECUTION_AFFINITY/NOETL_PEER_URL_TEMPLATE/NOETL_SHARD_INDEX_FROM_HOSTNAME(all default off), metricnoetl_execution_affinity_total{outcome}. e2e#71 (66b6e1b): 2-replica StatefulSet topology + rig HARD gate. Multi-replica gate-ON kind PASS: linear/loop/fanout COMPLETE, every chain roots=1/dangling=0/walk==total (NO fork),forwarded_ok +9, never-scan (scans+0) + sole-writer across replicas; single-replica unchanged; 595 server tests + clippy green; baseline restored. Follow-up #117: off-serverfrom_eventsspine ordered byevent_idwedges fan-in under a chain-orderβ id-order inversion (affinity + high-concurrency fanout) β fix = order spine byprev_event_idwalk; linear/loop already reliable. Prod multi-replica verdict: write-ordering COMPLETE (no fork) β prod can horizontally scale the off-server stack for linear/loop; high-concurrency fan-out needs #117 first. All affinity flags default off; PROD GKE untouched. -
2026-06-20 β β
#115 program-scale step 1 SHIPPED β multi-replica coherence DATA LAYER (NATS-KV-backed
ChainHeads+ExecDescriptor); execution-affinity STAGED. server#251 v3.38.0 + e2e#70.NOETL_REPLICA_COHERENCE=nats_kv(defaultlocal, prod unchanged) backs the off-server drive's watermark + descriptor with JetStream KV buckets so 2+ replicas resolve the same value β head advance = CAS (one chain), descriptor = CAS merge; in-process maps = write-through cache / degraded fallback (localβ bit-identical). server#251 (8f39a79):src/coherence.rs(CoherenceKv+ lazy buckets),ChainHeads/ExecDescriptorsasync,ExecDescriptorserde, metricnoetl_replica_coherence_total{structure,op,outcome}(proofkv_remote_hit). e2e#70 (e222877):kind_validate_replica_coherence.sh+ un-staled the #113/#114 offload asserts behindNOETL_RIG_EXPECT_OFFLOAD(default false; underrefs_in_state=truethe offload paths legitimately stay flat). Kind-validated: single-replicanats_kvis bit-for-bit parity withlocal(linear/loop/fan-out Γ2 all COMPLETE, roots=1/dangling=0/walk==total,state_build_event_scans+0, hotpath scan +0, sole-writer intact); 2-replica proved cross-replica resolves work (kv_remote_hitadvanced for head + descriptor, nokv_unavailable). Necessary but NOT sufficient: on 2+ replicas concurrent cross-replica emits still fork the chain (theissuing_eventhead-read vs head-advance is non-atomic across replicas β observed forked chains + a cross-executionprev), so executions don't reliably COMPLETE on 2+ replicas yet β the remaining piece is execution-affinity (one replica owns an execution's drive + chain write; substrate present insrc/sharding.rsshard_for/owns), STAGED as program-scale step 2. 588 server tests + clippy green; baseline restored. ai-meta pointers β server8f39a79(v3.38.0) + e2ee222877. PROD GKE untouched; defaultlocal; no gate/mode/builder default changed. Off-server architecture is multi-replica-COHERENT (data) but not yet multi-replica-COMPLETE (write-ordering) β prod cutover stays single-replica until affinity lands. -
2026-06-20 β β
#115 Phase 5 SHIPPED + gate-ON validated β atomic-working-item context (tenet 6): the drive hands a worker only its minimal declared slice;
NOETL_ATOMIC_ITEM_CONTEXT(default off). #77 (Explicit Input Binding) resolved. server#250 v3.37.0 + worker#121 v5.40.0 + e2e#69. -
2026-06-20 β β
#115 Phase 6 SHIPPED + gate-ON literal-zero validated β hot-path
noetl.eventread class RETIRED; the table is AUDIT-ONLY (server v3.36.0).NOETL_EVENT_READ_PATH=event_scan|audit_only(defaultevent_scan, prod unchanged) retires the remaining lifecycle readers ofnoetl.event(theWHERE execution_idreplay class outside the drive). server#249 (b71ca1d): underaudit_onlyget_catalog_id(per-ingest) +inherit_parent_trace+ subscription dedup-audit + container-callback catalog/existence serve from the in-memory execute-timeExecDescriptor; a cold descriptor (post-terminal straggler after eviction / restart) resolves catalog_id fromnoetl.command(synchronous queue) β never anoetl.eventscan. Proof metricnoetl_event_hotpath_reads_total{site,outcome}. ops#199 (e5b0737) pinsevent_scanon the prod server manifest (operator-gated flip). e2e#67+#68 (0ab3c0a)kind_validate_event_read_path_phase6.sh. Gate-ON kind-validated (PUBLISH_ONLY + offserver + materializer + audit_only): hot-pathscanΞ0 (served_descriptor +96 + served_command +3), drivestate_build_totalΞ0 +event_scansΞ0 β ZEROnoetl.eventscans anywhere on the hot path, end-to-end; linear/loop/fan-out/output_select COMPLETE; sole-writer + lag-0; audit still works (direct SELECT + status COMPLETED + replayevent_count=25); committed gate rig PASS with audit_only on (no regression); 585 server tests + clippy green; baseline restored. Completes the RFC never-scan end state (tenet 3) under the flag. ai-meta pointers β serverb71ca1d(v3.36.0) + opse5b0737+ e2e0ab3c0a. PROD GKE untouched; defaultevent_scan; no gate/mode/builder default changed. Remainder = Phase 5 (atomic-item, needs #77) + program-scale (per-shard WAL, multi-replica descriptor coherence). -
2026-06-19 β β
#115 Phase 4 KERNEL + FLAG SHIPPED + shadow kind-validated β off-server state builder (worker v5.37.0 + server v3.33.0); drive cutover staged. The pool-side
state_builder(worker#118,fef961c) reconstructsWorkflowStatefrom thenoetl_eventsWAL β a per-execution chain index walksprev_event_idheadβroot, caches the spine keyed by the immutable chain head, advances only the new tail. A live WAL shadow loop (NOETL_STATE_BUILDER_SHADOW, default off) + the serverNOETL_STATE_BUILDER=offserver|serverflag (server#246,3e6006d, defaultserver). Gate-ON kind-validated on live cluster: shadow replayed the WAL β chain-walked spines whoseindexed==spinesizes match Phase-3 topologies (linear 13, loop 62, fan-out 25, output_select 31, storage_tiers 55) = parity by construction; WAL-readwal_events_total=993 withevent_scans_total=0; cachecold_rebuild=28 (replay/restart) +incremental=21 (live tail-advance β fresh fan-out indexedIncremental(5),indexed==spine==25==DB event_rows 25); fresh fan-out COMPLETED gate-ON,event_rows==distinct, 0__orchestrate__event rows, materializerpending=0/project_errors=0; 8 worker unit tests + 2 server config tests + clippy green; baseline restored. ai-meta pointers β workerfef961c+ server3e6006d. PROD untouched; no default changed. Theoffserverdrive cutover (drive consumes the builder's state) + Phase 5 (atomic-item context, #77) / Phase 6 (retire event read path) remain. -
2026-06-19 β β
#115 Phase 3 MERGED β chain-walk state builder (server v3.32.0); Phase 4 (off-server state builder + WAL cache) started. server#245 self-merged (no classifier block) β server v3.32.0 (
8338417); ai-metarepos/serverpointer bumped. BehindNOETL_STATE_BUILD_MODE=chain_walk(defaultevent_scan, prod unchanged) the drive reconstructsWorkflowStateby walkingprev_event_idheadβroot (in-memoryChainHeadshead +(execution_id,event_id)PK lookups β never aWHERE execution_idscan) β samefrom_events(orchestrate-core unchanged; parity by construction); falls back to event-scan on cold-head / lag / non-genesis. Gate-ON kind-validated (prior session): parity 41/41 MATCH,event_scans_total=0 / 1064 PK hops / 0 fallbacks, all topologies COMPLETE, sole-writer + lag-0 + gate rig PASS, 577 tests + clippy green. Phase 4 now in progress β move the chain-walk state construction OFF the server onto the system worker pool reading the WAL/NATS stream, pool-side cache keyed by the immutable chain head + incremental tail-advance (server chain-walk + event-scan remain fallbacks). PROD GKE untouched; no gate default changed. -
2026-06-19 β β
#115 Phase 2 implemented + kind-validated β one-level
prev_event_idevent chain (server#244 + noetl#667 merged). Eachnoetl.eventcarriesprev_event_id(the immediately-previous event in causal order) + eachnoetl.commandthe issuing-event link, so per-execution events form a walkable singly-linked list followable pointer-by-pointer without scanningnoetl.event(additive; no reader yet β Phase 3). The emit chokepointemit_eventsstamps the event link from a per-execution chain-head watermark (ChainHeads) β one path covers drive events +command.issued+ worker-lifecycle on both the gate-off INSERT and the gate-on publish, the materializer persisting it; the command link = the realstep.enter/unblocking completion so cursor-fan-out bodies share their branch origin (Β§4.4). Server-only (noorchestrate-corechange). Chain-correctness proven gate-ON across 6 executions (linear 13/13, loop 62/62, fan-out 25/25 with a real shared branch origin, sub-playbook 46/46, + Phase-1output_select31/31 &storage_tiers55/55 bounded): each has 1 root, 0 dangling / 0 duplicate event prev, 1 head, pointer-walk == full sequence (no gaps), real-step command dangling=0;kind_validate_orchestrate_gate.shPASS (sole-writer 25==25, 0 dup cycles, catalog0=0, lag 0); 573 lib tests + clippy green. PROD untouched; no gate default changed. Awaiting merge β ai-meta pointer bump; Phase 3 (chain-walk state builder) next. -
2026-06-19 β π #114 oversized-
command.issuedoffload shipped (server v3.29.5); refs_in_state consume side (#101) is the remaining off-server-drive cutover blocker. With #113's decode fix in place, 4 large-context fixtures wedged at a distinct second stall: underrefs_in_state=falsethe off-server drive embeds the full resolved upstream context into the next command, so itscommand.issuedevent (~1.32MB) exceeded NATSmax_payloadβ publish ack-timeout β wedge. Fix: a command context overNOETL_COMMAND_CONTEXT_MAX_BYTES(512KB) is offloaded tonoetl.result_storewith a{__context_ref__}marker;get_command/claim_commandresolve it before the worker sees it (metricscontext_offloaded/context_ref_resolved). Server #242 β v3.29.5 + rig phase 8 e2e#64. Kind gate-ON: off-server rig PASS (newtest_oversize_command_contextCOMPLETED, max command.issued ctx 585B, offload+resolve fired, 0__orchestrate__event rows, materializer lag 0); every command.issued event <1MB across all fixtures; 6 of #113's 9 now COMPLETE. Chose ref-on-oversize overrefs_in_state=true(candidate #1): a kind experiment provedrefs_in_state=truefixes the state-bloat (kind_playbook_lease_expirycompletes β drive-state 29KB vs ~1MB loop) but breaks the bulk-consuming fixtures (test_storage_tiers/test_output_selectfail at the bulk step) because the worker render-time ref-resolution isn't implemented β so the default stays false. The remaining 3 fixtures + the cutover (#107/#111) now hinge on the refs_in_state consume side (#101) β__orchestrate__drive-state bloat (17.4MB forstorage_tiers) + the_ref/bulk-resolve gap. ai-meta β server385d21f(v3.29.5) + e2e9919392. No prod default flipped; prod is pre-#108 in-server drive (unaffected). -
2026-06-19 β π #113 off-server drive β recover offloaded drive result + stop drive on cancel (server v3.29.4); #114 opened. Fixed the worker-driven drive stall when an
__orchestrate__result exceeds the 100KB inline budget (worker offloads it with only areference.refβ server now resolves+decodes it viaresult_store.resolve, metricref_resolved, instead of dropping β non-convergent re-loop) + the cancel non-stop facet (match underscoreplaybook_cancelled+ExecutionState::is_terminalterminal guard, no restart). Server #241 β v3.29.4 + rig e2e#63. Kind gate-ON proven (785KB result βref_resolvedβCOMPLETED, 0 decode WARNs; cancel froze a drive-loop instantly; sole-writer lag 0). 5/9 #113 large-context fixtures COMPLETE; the other 4 hit a distinct oversized-command.issued(full upstream context embedded β >1MB NATS payload) stall β #114 (#113 stays open until all 9 close). ai-meta β server1e844c1+ e2e12b27e9. No prod default flipped. -
2026-06-19 β π #103 GKE pre-flip PREP β prod images pushed, GMP monitoring live, manifests staged; NO traffic flip / NO
PUBLISH_ONLY. Verified prod already-Rust (the #49 cutover is done; pre-#103 live images), both flip secrets present, monitoring = Google Managed Prometheus (not VM). Pushed server v3.29.3 + worker v5.35.0 to the prod AR (amd64); applied + verified GMPPodMonitoring(worker+server/metricsβ the noetl ns had none) + materializer-lagRules(up{namespace="noetl"}=4 live); staged the roll-forward manifests (not applied β they roll live workloads); runbook gained a "Production (GKE)" section + GMP managedAlertmanager pager stub. Operator-gated: roll images β materializer shadow β pager β flip. ai-metae5b6d6cβ ops9edd9c4(ops PR #197). No prod default changed. -
2026-06-19 β π‘οΈ #103 materializer-lag GUARDRAIL shipped β the pre-flip observability gate. The server was FLIP-READY; the remaining gate was a materializer-lag metric + alert. Worker #116 β v5.35.0 extends the JetStream lag poller to track the
noetl_events/noetl_materializerconsumer on an independent task βnoetl_worker_nats_consumer_pending{consumer="noetl_materializer"}climbs even when the materializer loop is dead. Ops #195+#196:VMRule(backlog warning>200/critical>2000/growing + stall-under-gate + project-errors + absent-under-gate, stall guarded on backlog>0), worker/metricsVMServiceScrape(was unscraped), VMAlert enabled, Grafana dashboard, flip runbooknoetl-cqrs-publish-only-flip.md. Kind-proven full cycle on the VM stack: green baseline (backlog 0) β induced lag (materializer fault-injected, events publishing under the gate) β gauge 0β684, alerts fire (backlog warning+critical + stall) β recover β drainsβ0 idempotently (0 dup/loss), alerts clear. ai-meta β workerb910341(v5.35.0) + ops2fcfa59+ worker-wiki0030f30.PUBLISH_ONLYstays default-off. -
2026-06-19 β π― #103 server cutover COMPLETE β FLIP-READY. The 2 ExecutionService cancel/finalize sites now route through the
emit_eventchokepoint (server #240 β v3.29.3 + e2e rig #62); kind-proven both modes; no remaining synchronous server event writers under the gate. FlippingPUBLISH_ONLYon is now a staged operator decision. -
2026-06-19 β π― #104 off-server-drive Γ gate reconciliation PROVEN β the last real blocker before the
PUBLISH_ONLYflip is operator-safe. The combination #103 left unproven (gate-on was only ever validated with the in-process drive) is green on kind: gate-ON (PUBLISH_ONLY=true) with the off-server drive (PLUGIN_DRIVE=true) + materializer sole writer β fresh exec + cursor fan-out β COMPLETED; server wrote 0noetl.eventrows (all 25 PUBLISHED βevent_ingest_published_total=25), materializer materialized all 25 exactly once (25 rows == 25 distinct ids, 0catalog_id=0, 0 dup cycles), drivedispatched=applied, read-your-writes held (the relocated trigger fires post-materialize β server rebuilds state from committed log before bounding the off-server drive input). Server #238 β v3.29.2 (76d29bb): cold-cache apply now rebuildsWorkflowStatefrom the durable log (the #104 WAL-rebuild principle) instead of dropping the in-flight result on a server restart mid-drive β kind crash-recovery proof: hard-kill mid-drive βcold_rebuildmetric+log fires β that exec COMPLETES with full event integrity. Committed e2e rigkind_validate_orchestrate_gate.sh(e2e#61). Regression green: gate-off + in-process (prod default) and gate-off + off-server. ai-meta β server76d29bb+ e2e61f7a5c. Remaining before a safe flip: only the 2 ExecutionService cancel/finalize sites. No prod default changed. -
2026-06-18 β π― #108 (c) β the worker-driven orchestrator drive is now the DEFAULT; #108 CLOSED.
Flipped
NOETL_ORCHESTRATE_PLUGIN_DRIVEto default true (server#233, v3.28.0 β server@80cc0e6). Gated on a scale soak on kind (images built from the released tips, server v3.27.0 / worker v5.33.0): a single 694-drive cursor+fan-out run (test_pft_flow_v23Γ40) COMPLETED with__orchestrate__rows innoetl.event= 0 (event_suppressed +2082) and all 694 drives claimed on the system pool (shared pool got only the 671 real-step commands), 0 errors; 5Γ concurrent self-contained cursor = 5/5 COMPLETED. Then deployed the flipped image with no env var β the default-on path reproduced the identical shape (361 drives, system-isolated, 0 burst); 15/15 regression fixtures green; the revert (=false) verified to fall back to the in-process drive (system delta 0). In-processtrigger_orchestrator_innerkept as the fallback. ai-meta β server80cc0e6+ worker437b0be+ server-wiki0210012. -
2026-06-18 β orchestrate drive isolated on the SYSTEM pool via pool affinity (#108 follow-up b, kind-validated).
Server stamps
execution_poolon the command notification (server#232 β server@846166b); worker declines (ACK+skip) notifications not for its pool segment (worker#114 β worker@e2162b7), so the drive runs on the dedicated system pool even under JetStream consumer-filter drift. No worker HTTP pending-poll β the NATS consumer is the only claim vector. Validated:__orchestrate__claimed+executed on the system pool (3), zero on the default pool, simple_python COMPLETED; 553+196 tests green. Only (c) the deliberate default-flip remains. -
2026-06-18 β the orchestrate meta-command touches
noetl.eventZERO times (#108 follow-up a, kind-validated).dispatch_orchestrate_commandstops writingcommand.issuedtonoetl.event; the command lives only innoetl.command, andclaim_command/get_commandfall back to it on a miss (noetl.eventstays authoritative for normal commands) (server#231 β server@9438f3b). So__orchestrate__writes 0 of its former 5 rows per drive β the directive that system playbooks keep only their own state is met. Validated: cursor+fan-out COMPLETED via thenoetl.commandfallback, 0 event rows, 20 real steps normal, 0 errors. Remaining: NATS affinity (ops) + default-flip. -
2026-06-17 β system playbook events no longer burst Postgres + system-pool routing (#108 slice 4b, kind-validated).
The
__orchestrate__meta-command is infrastructure, not a workflow step, so the server now skips persisting its lifecycle events tonoetl.event(handle_event_inner+claim_command) (server#230 β server@6aef3a6). At scale they'd burstnoetl.event/Postgres for no benefit. Validated:__orchestrate__now writes only the lonecommand.issued(1 of 5 β 80% fewer rows); cursor+fan-out flow still COMPLETED. Drive routes to thesystemsegment (true isolation pending a NATS-affinity ops fix; resilient via the pending-poll meanwhile). Follow-ups: eliminate the lastcommand.issued(claim fromnoetl.command) + NATS affinity. -
2026-06-17 β π― the orchestrator drive runs OFF-SERVER on the worker pool (#108 slice 3, kind-validated).
With
NOETL_ORCHESTRATE_PLUGIN_DRIVE=onthe server issuessystem/orchestrate(entry: run_state, args = the boundedWorkflowState) to the worker pool instead of evaluating in-process; the worker runs the drive, the server applies the result on the command'scall.done(server#229 β server@465cdbb v3.23.0). Kind:test/simple_pythondrove startβendβCOMPLETED through the round-trip (dispatched=2, applied=2, 0 decode_error,__orchestrate__didn't leak as a step,playbook.completed). Default off, in-process fallback. Bug caught+fixed:output_b64ridescall.donenotcommand.completed. Next: shadowβflip at scale, make drive the default, route to the system pool. -
2026-06-17 β worker-driven cutover slice 2:
apply_orchestration_resultextracted + slice 3 designed (#108). The post-evaluate emission (events β commands β terminal) is extracted verbatim fromtrigger_orchestrator_innerinto a reusable fn (server#228 β server@586aeae) so the worker-driven drive applies a worker-computed result identically. Behavior-preserving (553 tests green, clippy clean). Slice 3 (dispatch) designed + grounded:apply_eventwould phantom-step a meta-command, so the design uses a reserved__orchestrate__step ignored in state, a flag-gated scheduler, apply-on-callback, and loop-prevention. Lands behindNOETL_ORCHESTRATE_PLUGIN_DRIVE(default off), kind-validated before the flip. -
2026-06-17 β worker-driven cutover slice 1: configurable wasm guest entry (#108).
The worker can now dispatch a named plug-in export (worker#113 β worker@04420d0):
tool: {kind: wasm, plugin: {path, version, entry}}names the export (defaultrun); the worker-driven orchestrator will useentry: run_state.invoke_bytes_with_entry+run_by_ref_entry/run_and_apply_by_ref_entry(originals delegate withrun); test proves runβ0xAA vs run_stateβ0xBB + missing-export error. Purely additive, no live change. Server scheduler+apply (the hot-path round-trip, default-off flag) next. -
2026-06-17 β orchestrate plug-in drives the real workload identically, live (#108 slice 4).
The orchestrator runs the plug-in alongside the in-process drive on every evaluation + diffs commands (server#227 β server@bd652ab) β a process-global wasmtime host (feature
orchestrate-shadow) loaded fromnoetl.plugin_moduleat boot, gatedNOETL_ORCHESTRATE_PLUGIN_SHADOW; in-process result authoritative. Kind-validated over the live 10Γ1000 PFT:noetl_orchestrate_shadow_total{result="match"} 529, ZERO mismatch/error, workers stable. Plug-in gains a state-input path (run_state); both build configs green (default nowasmtime). Slices 1-4 prove orchestrator-as-plug-in end to end; next is the worker-driven cutover. -
2026-06-17 β
system/orchestrate@1registered + servable in a deployed server (#108 slice 3). The server bakes the orchestrate wasm into its image and seeds built-in system plug-ins intonoetl.plugin_moduleon boot (server#226 β server@b21b589, kind-validated). Newsrc/system_plugins.rs(pure dir-scan + sha256, unit-tested) + awasmbuilderDocker stage +NOETL_SYSTEM_PLUGIN_DIR; in-process upsert (not the token-gated HTTP surface); digest-keyed hot-reload. Validated:GET /api/internal/plugins/system/orchestrate?version=1β 200application/wasm1559093 bytes, ETag=digest, staleβ409, baked sha256 == served digest. Next: kernel scheduler dispatches the now-registered plug-in. -
2026-06-17 β
system/orchestrateplug-in runs identically to native in wasmtime (#108 slice 2). A wasmtime shadow-diff (server#225 β server@ccec104) loads the built.wasmthrough a harness mirroring the worker host'sinvoke_bytesABI byte-for-byte and asserts the wasm output equals the native drive over auth0 multi-arcwhen:routing (minijinja in wasm) + cold-start. Finding: command-set identity (parsedValueeq), not raw bytes β thecontextmap serializes in insertion order (serde_jsonpreserve_orderβ upstream HashMap iteration, differs wasm32 vs host arch); the scheduler deserializes toVec<Command>, so the command set is the bar. 2 unit + shadow-diff green; plug-inexcluded; test-only. Next: catalog register/serve β kernel scheduler. -
2026-06-17 β
system/orchestrateWASM plug-in exists β drive core runs as a 0-import module (#108 slice 1). New standaloneplugins/orchestrate/crate (server#224 β server@10a629b) wraps the drive behind the worker plug-in ABI (input = JSON event-slice + playbook; output = JSONOrchestrationResult; data-plane =memory/alloc/run) and compiles towasm32-unknown-unknownβ the first non-trivial compiled system playbook. Feasibility risk retired: the.wasmhas 0 imports (no WASI, no hostrender) β the whole drive incl. minijinja runs in-guest. Native parity test reproduces nativeevaluatebyte-for-byte; 551 server tests green, crateexcluded from the workspace. Next: worker-host shadow-diff β catalog register/serve β kernel scheduler (NOETL_ORCHESTRATE_PLUGIN, default off). -
2026-06-17 β Orchestrator drive core fully wasm-resident β Event-ABI round #109 CLOSED.
Slice 3 (server#223) moved
orchestrator/evaluatefromsrc/engine/intonoetl-orchestrate-core. All 6 drive modules (renderer, playbook model, commands, evaluator, state, orchestrator switch) now compile native +wasm32-unknown-unknownβ thesystem/orchestrateplug-in seed (#108).evaluatereads the purecore::event::Event; server convertsdb::Eventat thetrigger_orchestratorboundary (slice-1From). 122 core + 565 server tests green, 0 WASI imports on wasm32, clippy clean; cargo-chef image (v3.20.0) kind-deployed, PFT 10Γ1000 β full command lifecycle, 0 errors, 0 restarts. ai-meta β serverbfd3f77(internal refactor, stays v3.20.0). -
2026-06-14 β Transfer tool: SnowflakeβPostgres both directions β #99 CLOSED.
Both transfer arms implemented with full credential-alias resolution. tools v3.10.0 (tools#65) + worker v5.22.0 (worker#87) + e2e#58. SFβPG:
$n::text::<udt>coercion + RFC3339 timestamp reformat; PGβSF: generated INSERTs. Full bidirectionaldata_transfer/snowflake_postgresfixture COMPLETED on kind against livesf_testaccount. tools β 4127b4b Β· worker β 6d97e7c Β· e2e β 94aa7f1. -
2026-06-14 β Snowflake key-pair JWT validated end-to-end β #98 last external-tool gap closed; transfer step β #99.
noetl-tools v3.9.0 / v3.9.1 / v3.9.2 (tools#62/#63/#64) β key-pair JWT auth (bypasses MFA) + User-Agent fix (code
391903) + SQL-API context-in-body + multi-statement split (codes391911+000008). Worker bumped to v3.9.2 (worker#83β#86) + e2e#57 fixture cleanup.create_sf_database(CREATE DATABASE) +setup_sf_table(CREATE TABLE + INSERT) both COMPLETED via JWT on kind against the livesf_testaccount (NDCFGPC-MI21697). Transfer step fails (inline creds, no key-pair fields) β filed #99. ai-meta pointers: tools a216ab2 Β· worker 9d6b127 Β· e2e e191231. -
2026-06-12 β #90 Phase 7 shipped β scale hardening; #90 CLOSED (all 7 phases complete), live proof green.
Final phase: server v3.5.0 (server#189)
POST /api/execute/batch(NβN, partial-failure contained) + opt-in exactly-once dedup window (noetl.subscription_dedup, bounded-by-age, race-safe, default off); worker v5.19.0 (worker#79) batch dispatch + dedup opt-in + per-subscription rate limits (deterministic token-bucketRateGovernor, fetch-side backpressure β source keeps backlog, no loss,subscription.rate_limitedevent); ops (ops#176) + e2e (e2e#48); no tools change β no crate cascade. Live on kind: batch 12β12 COMPLETED on the subscription pool + per-message traceparent; dedup duplicateβ1 execution +subscription.message.deduplicated; rate-limit engaged + 10/10 β executions (no loss); direct-curl within/outside-window + dedup-off + batch partial-failure all green. ai-meta β server7b217d8+ worker7531f4a+ ops6db69b9+ e2e203593b. #90 closed; follow-ups tracked: #91β#94 + tools#57. -
2026-06-12 β #90 Phase 6 shipped β CLI local
noetl subscribe+FileEventSink+local_diskspool (live local proof green). Addednoetl subscribe <spec.yaml>(cli v4.11.0, cli#60, closes cli#59): akind: Subscriptionlistener run standalone in local mode β no k8s, no NATS-dispatch server for the listening itself β reusing the samenoetl_toolssource clients + directive engine + spool engine the in-cluster worker uses, emitting the sameExecutorEventenvelope to a localFileEventSink(one event/line JSONL β replayable trail). Local dispatch (RFC Β§5.3): in-process viaPlaybookRunner(pure-local default) orPOST /api/execute.local_diskspool (Β§8.6): circuit-breaker + buffer + ordered replay + idempotency + dead-letter against a local dir, circuit state in a local file. Newsrc/subscribe/{mod,spec,sink,dispatch,runtime,spool}.rs+examples/subscribe/.cli-only β no tools change / crate cascade (the source+spool surface ships innoetl-toolsv3.5.0; bumps the lock 3.0.0 β 3.5.0 via the executor's"3"). Tests: 12 subscribe + full bin suite (53) green, incl. a deterministic outageβspoolβordered-replayβidempotency proof on the real engine. Live (in-cluster NATS on kind): 5 msgs βreceived=5 dispatched=5 failed=0(19-event JSONL trail); local_disk spool outage β 6message.spooled(0 dispatched, no loss) β recovery β 6message.replayedin order β drained to 0. Finding: the NATS source ignores URL-embeddeduser:pass(async-natsConnectOptions) β specs use explicituser/password. ai-meta β cli2fb3fb0(v4.11.0); wiki clisubscribe. #90 stays open for Phase 7 (scale hardening, volume-gated). -
2026-06-11 β #90 Pub/Sub + Kafka brought to live-E2E parity with NATS (validation gap closed).
Stood up the two remaining subscription brokers in kind β Pub/Sub emulator (gcloud SDK image) + single-broker KRaft
apache/kafka:3.9.1β undernoetl/ops(ops#170), and added bounded-drain fixtures + kind-validate runners undernoetl/e2e(e2e#41). Both backends passed the same live bar as NATS: publish/produce 5 β bounded drain count=5 acked=true β execution COMPLETED βcall.done/command.completed/playbook.completedevent trail. No adapter code change needed β the pure-Rustkafkacrate talks to Kafka 3.9 KRaft and the Pub/Sub REST backend works against the emulator as-is. The one fix: the<step>.output.<field>accessor never resolved (bothwhen:arcs skipped β drain stalled); corrected to<step>.<field>in the fixtures + the latent opssubscription_drain.yamlexample. Validated on server v3.1.0 + worker v5.15.2 + tools v3.2.0; cluster left on that clean released stack. ai-meta β ops568a4ac+ e2e8d21e7a. #90 stays open (Phases 2β7 design-only). -
2026-06-11 β #89 shipped β JSON
nullround-trips through{{ step }}(server fix, v3.0.6). #89 βnullβundefinedserialization β CLOSED. The #88 cursor fixture walked all 4 pages but its 4thcheck_paginationcrashed: the terminal page'snext_cursor: null, re-injected via the whole{{ fetch_page }}envelope, rendered as the JS tokenundefined(invalid JSON), so the consuming Python step receivedresponseas astr. Traced the corruptcommand.issuedargs.responseto the renderer that builds next-step inputs β the server orchestrator (src/template/jinja.rs::render_to_value), not the worker the issue blamed.json_value_to_minijinjamaps JSONnullβValue::UNDEFINED; minijinja's map repr emitsundefined;render_to_valuefailedfrom_strand fell through to a raw string. The noetl-tools engine already had a| tojsonretry for exactly this; the server's copy had diverged without it. Fix (server#177, v3.0.6) ports the retry. 5 new tests; 619 lib + 8 parity green; clippy clean. Kind-validated end to end on the live test-server (baseline 4thcheck_paginationerrorβ fixedsuccess; cursor collects 35, matching offset). ai-meta β server8e17fbe. Standing direction honored β Claude wrote the Rust directly, no Codex. -
2026-06-10 β #88 shipped β pagination fixtures read
response.body.*; #89 filed. #88 β offset/cursor pagination fixture path β CLOSED. The Rusthttptool nests the parsed JSON payload underbody({{ fetch_page }}β{body, headers, status_code}); the fixtures readresponse.get('data', {}), which resolved to{}, sohas_more/next_cursordefaulted falsy and the loop exited after page 1 despite the correct post-#85 machinery. Confirmed the shape against a live http-tool result, then switched bothcheck_paginationsteps toresponse.get('body', {})(e2e#40). Kind-validated: offset walks0β10β20β30, users 10/10/10/5,validate_resultssuccess 35,playbook.completed COMPLETED; cursor path-fixed + walks all 4 pages (Mg==βMw==βNA==βnull, 35 events fetched) but the terminal page surfaced a distinct worker bug β #89 (worker serializesnext_cursor: nullas JSundefinedwhen re-injecting{{ fetch_page }}, so the consuming Python step gets an unparseable str). Other pagination fixtures (retry/max_iterations/pipeline*/loop_with_pagination) share the same envelope-key assumption over/api/v1/assessments|flaky({data, paging}) β flagged, left for follow-up. ai-meta β e2e72a7525. -
2026-06-10 β #87 shipped, #85 deferred (e2e sweep follow-ups #85/#87).
#87 β multi-tool sibling references β CLOSED.
task_sequence(thetool: [list]pipeline runtime) stored each sub-tool's result for the aggregated output but never injected it into the running context, so a later sub-tool's{{ <label>.<field> }}rendered empty β masked in quoted positions, asyntax error at or near ","in unquoted numeric SQL (save_edge_casestest_large_payload). Fix (tools#48, v3.1.1) injects each sub-tool's result under its label (synthetic.dataself-ref); worker adopts via worker#69. Kind-validated on a worker built from the fix:save_edge_casestest_large_payloadβrecord_count = 100(no syntax error),save_delegation_testclean. ai-meta β tools76f942a+ tools-wiki4962f8b+ workerb97f642. #85 β workflow-arc loop re-entry β DEFERRED (kept open). Implemented the dispatch-guard layer (draft server#176): a back-edge detector (cycle + recency) re-enters a completed loop head, so the loop no longer hangs (608 lib tests + 5 new pass). But kind validation surfaced a second blocker βset: ctx.Xloop variables are recomputed per orchestrator pass and revert to the workload default when the producing step is re-dispatched (a minimal counter-loop thrashes0,0,1,0,1,2,β¦). Full multi-page pagination needs durable event-sourced ctx propagation across iterations β larger than is safe to land well-tested in one session; held as a draft, not merged. Standing direction honored: Claude wrote all Rust directly (no Codex). -
2026-06-10 β #80 closed β container_callback chain green end to end.
Fixing the watcher's missing
curl(the literal #80 goal) surfaced two more layered bugs beneath it. Watcher image (ops#168): the manifest used the retiredbitnami/kubectl:1.30.3(removed from Docker Hub; the live cluster was patched to thebitnamilegacyarchive) with a runtimeapt/apk installstep that never putcurlon PATH β callback POST returned HTTP 000. Switched toalpine/k8s:1.30.3(kubectl + jq + curl baked in), dropped the install hack. Server insert (server#173, v3.0.3): once curl worked the POST reached the server and 500'd β the container-callback handler insertedcall.donevia a stale query targeting anattemptcolumn that doesn't exist on the deployednoetl.event; fixed to the workinghandlers::eventscolumn set. OOM path: the watcher only read Job-level conditions sofailed_oomcould never fire β added pod-levelOOMKilledβfailed_oomclassification (ops#168); thecompleted_atfallback for failed Jobs used bare jqnow(numeric epoch β HTTP 422), fixed to RFC3339now | todate; and the e2e fixture'sbytes(40MiB)was calloc-lazy (mapped to the zero page, never faulted in) so the container exited 0 β switched to a written-intobytearraythat dirties pages and reliably OOM-kills (e2e#38). Verified the kind cluster actually enforces memory limits (120 MiB in a 32Mi pod βOOMKilledexit 137). Rebuilt the server image + reloaded into kind;kind_validate_container_callback.shboth probes GREEN β happy_path β succeeded (delta 1), oom β failed_oom (delta 1). This is the last blocker on the #43 container-callback chain. ai-meta β opscacc513+ server5d2cf58(v3.0.3) + e2e6aaf06e. -
2026-06-10 β #79 closed β e2e kind-val runners back on the current
noetlCLI surface. Bothscripts/kind_validate_*.shrunners aborted immediately onerror: unrecognized subcommand 'playbook'β they targeted the retirednoetl playbook register/execute+noetl execution status/eventsverbs. The validation logic and the event taxonomy (step.enter/command.completed/node_name/ the fan-in barrier) were intact; only the invocation layer had drifted. Fix (e2e#37):noetl register playbook --file,noetl exec <catalog-path> --runtime distributed --json(exec bymetadata.path, not the bare name),noetl status <id> --json, and the event log overnoetl query(noeventsverb today β rows wrap under.result, order byevent_idsincenoetl.eventhas no timestamp column). Added a fail-fast CLI-surface guard to each runner. Validated on kind (server-rust v3.0.1 + worker-rust, :8082): fanout_reduce PASS start-to-finish with no manual workaround; container_callback drives registerβexecβCOMPLETED cleanly and stops at the metric-delta assertion because the deployednoetl-k8s-watcherimage lackscurl(watcher.sh: curl: not foundβ HTTP 000) β a cluster-side watcher gap tracked on #80. Version-skew note: PATH binary isnoetl 2.17.0,repos/clisubmodule is v4.10.0; the targeted surface is identical across both, so the runners work on either (the binary lags the submodule by a major line β worth refreshing for parity, not required here). Pointer: e2e βa3594b3; e2e wiki: new Kind-Val Runners page. -
2026-06-10 β #82 closed β GUI credential View/Edit recovered for pre-wallet records.
The Secrets Wallet (#61) moved credential storage to
forward-only envelope encryption; pre-wallet records now 500 on
GET /api/credentials/{id}?include_data=true(Decryption failed: aead::Error), so the GUI View/Edit flow dead-ended on a generic toast (response shape unchanged). Fix (gui#36): View surfaces the real reason + points to Edit; Edit still opens with the list-row metadata (name/type/description/tags) + a warning banner and an empty-but-required data field, so re-entering the secret and saving re-seals the record under the current wallet β recovering it. Validated live against kind + thedev:kindUI on :3001. Also landed e2e#36 (duplicateworkloadprobe-flag keys removed fromtooling_non_blocking) and gui#35 (dev:kindconvenience script). Pointers: gui β8cacc9e(v1.11.1), e2e β4a9ffbc. -
2026-06-10 β #81 closed β noetl-server v3.0.2 fixes the container-tool
commandtype contradiction.ToolSpec.commandwasOption<String>(scalar) but thecontainertool kind writes a K8s-Job-style array β an array failed the server'sToolDefinitionuntagged-enum match (400), a scalar was rejected by the worker'sContainerConfig.command: Option<Vec<String>>. TypedcommandasOption<serde_json::Value>(same asargs);ToolCall::from_specforwards it verbatim. 2 regression tests; clippy clean (server#172, v3.0.2). Kind-val GREEN end-to-end: server accepts the arraycommand, worker creates the K8s Job, Job reachesComplete 1/1. Server pointer bumped (ai-meta β serverbd36672). Chain counter-bump validation stays gated on #79 (runner CLI) / #43. -
2026-06-09 β E2E sweep cleanup β noetl-tools v3.1.0 + noetl-server v3.0.1.
Stripped the diagnostic
tracing::debug!scaffolding added during the e2e triage, kept the production fixes: YAMLwhen: trueboolean +|tojsonobject-template fallback (tools#47), 64 MB result-store body limit + pipeline command/spec stash (server#171). Pointers bumped (ai-meta@316048c tools, @6590bd6 server); tracks #49. All 7 sweep playbooks PASS on Rust-only kind. Worker crates.io dep-revert deferred β v3.1.0 not yet on crates.io ([skip ci]release commit). -
2026-06-08 β noetl-tools v2.24.2 clippy cleanup + noetl/server#22 closed.
Cleared the clippy
-D warningsCI gate on noetl-tools (15 warnings across 7 files; all mechanical lint fixes). Closed stale noetl/server#22 (Phase D orchestrator engine port β complete). noetl/server PR #167 (same clippy shape) opened, awaiting merge. -
2026-06-05 β Rust-only regression rig β canonical v10 SQL + http config shapes.
Swept ~30 self-contained e2e fixtures against the Rust-only kind stack
and fixed three config-shape classes in noetl-tools: postgres
command:alias + multi-statement SQL (tools#24, v2.18.3), a task_sequenceβduckdb regression test (tools#25), and the duckdbcommand:alias + httpparams/headers/formnon-string coercion (tools#26, v2.18.4). Worker adopted both (worker#50, worker#51). Newly GREEN:duckdb_test,json_serialization_save,duckdb_retry_query,pagination/{offset,cursor,max_iterations,pipeline},retry_simple_config. Recovered the cluster first (server had latched intoNATS not configuredafter a podman restart). Server-side follow-up noted:loop_with_paginationrenders{{ execution_id }}empty in a multi-statement postgrescommand. -
2026-06-05 β postgres-tool observability β real SQLSTATE errors.
noetl-tools 2.18.2 (tools#21) + worker dep bump (worker#49): the
postgres tool surfaces the real SQLSTATE + message instead of the
opaque
db error. Validated end-to-end β a bad query reportsERROR: relation "..." does not exist (SQLSTATE 42P01)in thecall.errorevent. Closes the last follow-up from the credential/iterator saga. -
2026-06-05 β
iterator_save_testGREEN β full v10 + credential + iterator-pipeline surface validated. server#73 (v2.19.7) defers task_sequence_prev/_resultsrefs at command-build so nested-pipeline templates render at runtime.iterator_save_testreachesplaybook.completedand writes 3 rows to the realdemo_noetlDB β the deepest v10 path (iterator β pipeline β_prevchaining β nested credential β postgres write). Closes the credential + iterator + pipeline chain (server#71, worker#46, worker#48, server#73). -
2026-06-05 β Nested-pipeline credentials + template-timing finding.
worker#48 (v5.11.3) β the worker now pre-resolves keychain aliases
on task_sequence SUB-tasks;
iterator_save_test's nestedsave_itempostgres step connects todemo_noetl. Closes the credential-path chain (store β alias-key β nested resolution, all validated). Lastiterator_save_testblocker found + filed: server#72 β the server pre-renders task_sequence{{ _prev.* }}refs (runtime-only) to empty β malformed SQL (a symptom the v2.19.5 Chainable change surfaced). -
2026-06-05 β Keychain-credential path validated on Rust-only.
Continuing R5 Tier 4, registered the
pg_k8spostgres credential and probed the DB-backed fixtures. Surfaced + fixed a 3-bug chain in the keychain subsystem: credential store bound AES-GCMVec<u8>to a TEXT column (server#71, v2.19.6); alias resolution read only theauth:key not v10'scredential:(worker#46, v5.11.2). Proven:iterator_save_test'screate_tableconnects + runs DDL against the realdemo_noetlDB. Third bug β nested-pipeline credentials (task_sequence sub-tasks bypass worker resolution) β filed as worker#47 for a follow-up round. Session details -
2026-06-05 β v10 control-flow runs end-to-end on Rust-only.
Phase F R5 Tier 4 re-probe found + fixed 7 more bugs across the
Rust stack (server v2.19.5 server#69 6 commits, worker v5.11.1
worker#44, tools v2.18.1). Four v10 fixtures now reach
playbook.completedβstart_with_action,end_with_action,loop_test,control_flow_workbook;actions_testcorrect-fails on a missingTEST_SECRETenv. Root-cause chain: catalog SQL type drift β ToolSpec null-serialization β worker array-config drop β orchestrator end-step skip + task_sequence label-wrap β minijinjaLenient-vs-Chainableundefined β end-step trigger gate. Also: rust-analyzer workspace setup + rule (ai-meta@38287b7). Session details -
2026-06-04 (late evening) β Rust-only e2e complete + legacy cleanup.
Six interlocking server gaps closed in one iteration (#55β#60)
- two noetl-tools fixes (#15, #16); worker dep bump; kind cluster legacy Python deployments retired. control_flow_workbook runs fully end-to-end on the Rust-only stack. Standing direction pinned: Rust-only focus, ignore Python tasks. Session details
- 2026-06-04 (afternoon) β Pipeline + failure termination + workbook resolution. Three server PRs landed together as v2.19.3 (#61, #63, #65).
- 2026-06-04 (morning) β EE-5 lax decode + workload + input alias. v2.19.1 + v2.19.2 β unblocked Rust worker β Rust server emission + canonical v10 playbook compatibility.
- 2026-06-04 (early morning) β Phase F R4-5 + R4 complete. N=2 shard kind validation script + ExecutionService refactor.
- 2026-06-03 β Phase F R4 series β DbPoolMap N+1 pool layer, AppState wiring, per-execution handler cutover, cluster-wide list fan-out.
- 2026-06-02 (afternoon) β Architecture pivot: rest of migration moves to system playbooks. Closed #30, #45; promoted #46.
See Releases for the per-repo release log with links to GitHub Releases pages.
Recent:
-
2026-10-04 β
noetl/noetlpypi-v5.1.1 β π the shipped wheel's RustSec count goes 5 β 3.h20.4.19 andrustls0.23.45 clear two advisories Dependabot reported as zero;event-listener5.4.2 andrustls-webpki0.103.15 ride along. Verified on the published artifact (4 files sha256-matched, sdistCargo.lockread, 5.1.0's lock scanned first as a positive control). Remaining 3 are documented deferrals (#402). -
2026-10-04 β
noetl/noetlpypi-v5.1.0 β π¦ the artifact finally matches the lock β the 7 cleared Rust advisories reach the wheel users install (#395). β β Verified by upload counts, becausetwine --skip-existingmakes a publish-nothing run go green. -
2026-10-04 β
noetl/cliv5.0.3 β π¦ arm64 debs are built and indexed; the aptReleasefile gains its 8 identifying fields and stops listing itself (#390). -
2026-09-04 β
noetl/serverv3.101.0 β π§ scoped, inspect-only tier repair endpoint (server#399). Re-mirrors only the event IDs the comparator reports absent; both comparisons underParityRecording::Inspectso a repair does not inflate the counter its own alert reads. Inert, not run against prod (prod stays v3.100.4). -
2026-09-01 β
noetl/workerv5.129.1 β π₯ bounded manifest retention + ingest failure counters, the two fixes for the 8-hour silent dispatch outage. Deployed: manifest 871 β 169 MB. -
2026-08-29 β
noetl/serverv3.92.0 β v3.94.0,noetl/workerv5.124.0,noetl/cliv4.22.0 β π the catalog gets its own log tier, relation and bulk loader; #307's projection fold becomes measurable. -
2026-08-11 β
noetl/workerv5.115.3 β β±οΈ the cooldown gates RETRY, never BELIEF.is_reachable()consulted the cached-negative deadline, so a lapsed window re-promoted a dead tier with no evidence; belief moved to a flag only a real successful op sets. Mutation-checked twice (worker#262, #257). Inert, not deployed. -
2026-08-05 β
noetl/serverv3.64.0 β v3.69.0,noetl/workerv5.98.0 β v5.99.1,noetl/gatewayv3.8.0 β π the metrics surface becomes readable when nothing has happened:*_build_info{version}in all three binaries, publish-skip reasons and all seven sweep outcomes pinned at 0, and the dead NATS + JetStream metrics deleted. Prod untouched. -
2026-08-03 β
noetl/workerv5.92.0 β π‘οΈ the writer's SIGTERM seal now covers the events log, and ingest is actually stopped first. worker#211 (ddb4de8): fail-loud internal bus source (closes #221), sequenced seal + confirmed stop-ingest (#209 graceful half), face supervision. The release gate caught that the first seal attempt never actually closed the listener βNotify::notify_waiters()stores no permit and the acceptor registers on its first poll, so an early shutdown lost the signal (24/30 runs under contention;lsofshowed the writer's own acceptor stillLISTENing). Fixed with a watch + an ack after the fd is dropped. Deployed to shastaratech prod by digest@sha256:ad1e96063bf5β¦81725; a controlled SIGTERM sealed both hosts in 568 ms with every cursor resumingclamped=false.β οΈ semantic-release then pushed5.91.3over it onmain(#224). -
2026-07-30 β
noetl/serverv3.58.3 +noetl/workerv5.81.3 β π the EHDB command bus goes faster than NATS, and learns to survive a writer restart. #205: group commit + off-lock fsync in the writer (ehdb#30103e94be) + pipelined publish (server#289, worker#195) β prod dispatch p50 138.5 / p99 181.0 ms vs the 338 ms NATS baseline (~2.4Γ). #208: keepalive + negotiated coordinator heartbeat, durable committed cursor clamped to the reopened tip, seal on SIGTERM (ehdb#30286a24f9, worker#196, server#290) β writer deleted mid-load β 38/38, 0 dup / 0 loss, ~2.7 s redial. These are the images now running the production command bus. -
2026-07-28 β
noetl/workerv5.81.1 +noetl/serverv3.58.1 β π the #203 feed-delivery fix: the command-bus writer now assigns the feed ordering key (append_writer_assigned) instead of trusting the producer's sparse snowflake id, so an out-of-order append can no longer land below the follower cursor and be silently dropped (ehdb#300 β ehdbd4b6235; worker#193 + server#287). Kind before/after on an identical 40-concurrent burst: 17/40 β 41/41. These are the images now running the EHDB command bus in production. -
2026-07-26 β
noetl/workerv5.81.0 β πͺ #199 Slice C auto-sink observe-only (worker#192): the auto-sink loop observes and writes nothing; the write slice is held for design sign-off. Default-off. -
2026-07-26 β
noetl/workerv5.80.0 β πͺ #199 Slice A sink-confirmation signal (worker#190): an author-declaredsink: trueconnector step marks its execution pending-sink on dispatch andconfirm_sunks on success. Author-declared, never inferred (apostgres/http/transferstep can be a read β classifying by kind would confirm-sink on reads and release un-sunk context); fail-safe is to under-confirm. Default-off (NOETL_SINK_GATE_EVICTION). -
2026-07-25 β
noetl/workerv5.79.0 β πͺ #199 Slice B durable-segment GC gate (worker#191): the reclaim pass consultsSharedWalIndex::any_sink_pending()and defers while any execution is pending-sink. Behavior-neutral, default-off. -
2026-07-24 β
noetl/cliv4.21.0 β π Stage 2 of the PyPI wheel: theduckdbcrate (features = ["bundled"]) gated behind theduckdb-integrationfeature so the wheel matrix builds without compiling the DuckDB C++ amalgamation (cli#74). Distributed artifacts (tarballs, deb, Homebrew, Dockerfile) keep the engine explicitly. This is the CLI crate compiled into the publishednoetl5.0.0 wheel (noetl --versionβ 4.21.0). -
2026-07-24 β
noetl/cliv4.20.0 β π Stage 1 library extraction (cli#73):src/main.rsbecomes a shim oversrc/lib.rsexposingcli_main(argv) -> i32(the full 24-subcommand tree) + re-exportedPlaybookRunner/RunOutcome. Verified byte-identical to 4.19.0. This is what the wheel links, not a reimplementation. -
2026-07-26 β
noetl/noetl5.0.0 on PyPI β π the NoETL CLI shipped as a Python wheel, the Rust CLI bound via PyO3 (abi3-py39):noetlconsole script +import noetlover the realPlaybookRunner. 4 artifacts (macOS arm64, Linux x86_64/aarch64, sdist); Windows dropped (the CLI depends onnix/POSIX), macOS-Intel covered by the sdist. https://pypi.org/project/noetl/5.0.0/ (noetl#692, tagpypi-v5.0.0). -
2026-07-23 β
noetl/serverv3.58.0 + v3.57.0 β π§± EHDB write-behind-cache boundary: command-input tiering (server#284) and permanent-log-lean (server#283) β an over-floor inline step result / command input is stripped out of the permanent log at theevents_projectmaterializer into a byte-source reference. Both default-off (NOETL_PERMANENT_LOG_LEAN). -
2026-07-14 β
noetl/toolsv3.25.0 +noetl/cliv4.17.0 β π§© provider tool round 5: thenoetl provider {plan,drift,orphans,adopt}CLI verbs (Terraform-style CLI over the tool, local mode; glue overProviderTool::execute+provider_state+to_tools_config) (cli#68,a2e5b4d) + the query-tier fetch adapterextract_facts_from_tier_records(unwrap thepayloadstring + recursive fact search β fixes the round-3/4 fixed-path bug where the fold silently returned nothing against real event data on 3.23/3.24) + stack-scopeddetect_orphans_scoped/detect_stack_conflicts(tools#90,5a06c7e). noetl-executor unchanged at 0.7 (zeroexecutor/files touched). crates.io tarball-verified + released-binary proof (noetl-v4.17.0-darwin-arm64, all 4 verbs GET-only zero-writes). Follow-up 3 (first-class event type) DEFERRED. ai-metatoolsβ5a06c7e+cliβa2e5b4d; #189 OPEN. Mocks only β no prod/kind/live-cloud. -
2026-07-13 β
noetl/toolsv3.24.0 +noetl/cliv4.16.0 (+noetl-executor0.7.0) β π§© provider tool round 4:reconcilepolicy (reportnew-default /enforce/adopt) + confirm-gated drift adoption (adopt= accept live actual as new desired, read/GET-only, blind+stale REFUSED; covers update-in-place + import) (tools#89,c172efb), AND local-modereconcile/known_desiredpassthrough + executor 0.6.0β0.7.0 (cli#66,7557dde). All three crates crates.io-verified. Behavior change (user-confirmed): converging needsreconcile:enforce. ai-metatoolsβc172efb(c6790983) +cliβ7557dde(e4d3a32c); #189 OPEN. -
2026-07-13 β
noetl/cliv4.15.1 β π οΈ release-cli crate-publish FIXED: bumpednoetl-executor0.5.0β0.6.0 (+ bin dep"0.5"β"0.6") so theTool::Providervariant ships to crates.io. Thepublish-cratejob had failed on v4.14.0 + v4.15.0 (crate channel stuck at 4.13.0); full release run 29290603328 GREEN β executor 0.6.0 + noetl 4.15.1 now live on crates.io, binaries +.debon the v4.15.1 release. apt + homebrew still SKIP (missingAPT_REPO_PUSH_TOKEN/HOMEBREW_TAP_PUSH_TOKEN) but now emit a::warning::. cli#67, ai-metacliβ5bb1705(7d58e7d); #190 OPEN pending the two repo secrets. -
2026-07-13 β
noetl/toolsv3.23.0 +noetl/cliv4.15.0 β π provider tool round 3: confirm-gated destroy (*.delete/services.disable/*.iam.remove_bindingβ live-resolved plan + SHA-256plan_digest; blind apply + stale digest REFUSED; no wildcard) + EHDB ownership projection (secret-freeprovider_fact+ pureprovider_statefold over the raw eventlog tier, not the secret-free/api/ehdb/eventsread-model) + config-level endpoint override (tools#88,aa32938), AND local-modeendpoint/stack/confirmpassthrough + re-pin to noetl-tools 3.23.0 (cli#65,94cc72a; local-mode proof green on 3.23.0). ai-metatoolsβaa32938(f6eeb479) +cliβ94cc72a(cbc7be99); #189 OPEN, round-4 = drift adoption. Mocks/wiremock only β no prod/kind/live-cloud, no real destroys. -
2026-07-13 β
noetl/toolsv3.22.0 +noetl/cliv4.14.0 β π provider tool round 2: apply-mode idempotent GET-first converge for the 4ensure/ensure_bindingactions (+services.enable/projects.link, re-run = no-op) + bounded inline LRO polling (tools#87,eb3d8c7), ANDkind: providerdispatches throughnoetl exec --runtime localβ same tool the worker runs, re-pinned to noetl-tools 3.22.0 (cli#64,2d46013; supersedes cli#63, closed). ai-metatoolsβeb3d8c7(6cd1ba38) +cliβ2d46013(32a8fb86); #189 OPEN, both design forks DECIDED for round 3. -
2026-07-13 β
noetl/toolsv3.21.0 β π newkind: providercloud provider tool (Google REST MVP; Cloud Resource Manager v3 / Cloud Billing v1 / Service Usage v1 over REST +GcpAuthADC, zero new heavy deps; plan/apply + explicit-auth-for-mutations) (tools#86,cd9d71c; ai-metae9b3e14e; #189 OPEN) -
2026-07-04 β
noetl/serverv3.52.0 β π #169 Auth0 JWT RS256/JWKS signature verification (server#276) shipped + deployed to prod in shadow (NOETL_AUTH_VERIFY_SIGNATURE, default off; also carries #166 Phase 5 dark). Prod v3.50.0βv3.52.0. -
2026-06-29 β
noetl/serverv3.48.0 +noetl/workerv5.48.0 β β‘ #156 off-server drive tail-attach (server#268ddca5c4+ worker#14212710b6): server attaches the per-execution event tail to the__offserver_build__dispatch, worker applies it to its WAL index before building β per-hop O(execution) not O(global log), removing the drain-lag 8s reconcile cliff. FlagNOETL_OFFSERVER_ATTACH_TAIL(default off). DEPLOYED on prod flag-off (behavior-neutral) then flipped on; 5 Muno turns COMPLETED identical correct widgets, 90 drives attached O(tail), 0 restarts, reconnect 0/30s; 1-step rollback. ai-meta pointerf2b5666 -
2026-06-28 β
noetl/workerv5.47.3 β π #157 hotels-turn drive wedge fix (worker#141):__orchestrate__drive result stays inline regardless of size, so a render-hop result over the 100KB budget no longer offloads to a result-store ref the server can't resolve underNOETL_RESULT_STORE_DUAL_WRITE=false(#104) β Muno hotels turn now COMPLETES + returns ahotel_listwidget; deployed both pools (0 restarts), ai-meta pointer60938f4 -
2026-06-27 β
noetl/serverv3.47.0 β π #104 result_store dual-write retirement gate (NOETL_RESULT_STORE_DUAL_WRITE, default on = behavior-neutral) (server#267); flipped OFF on prod β result_store writes retired, #104 tier authoritative, data frozen -
2026-06-26 β
noetl/toolsv3.19.0 β π G1 container GPU placement (node_selector/tolerations/volumes) + G2 poll-to-terminal helper (tools#82); SLM platform foundation #144/#145, review-only -
2026-06-26 β
noetl/workerv5.47.0 β π G2 watcher-free poll completion fallback (NOETL_CONTAINER_COMPLETION_POLL, default off) (worker#139); #145, review-only -
2026-06-26 β
noetl/serverv3.46.0 β π G3 versioned model/dataset/eval registry (NOETL_REGISTRY_ENABLED, default off) (server#266); #146, review-only -
2026-06-23 β
noetl/workerv5.46.0 β πͺΆ #104 OQ5 Option A: producer-staged result tier (NOETL_RESULT_PRODUCER_STAGE, default off) β decouples the over-budget tier write fromresult_store; materializer skip-on-exists. -
2026-06-23 β
noetl/serverv3.45.0 β π #104: WI/ADC GCS auth (3-modenone/static/adc) MERGED + ROLLED TO PROD as WI KSAnoetl-server-rust(@sha256:d3cbf1adβ¦); result-tier GCS ENV applied, all tier-enable flags OFF β tier inert; off-server cutover stayed healthy, 0 restarts (server#265,fad5d8a). -
2026-06-23 β
noetl/serverv3.44.0 β πͺΆ #104 Phase F: result-tier GC sweeper (NOETL_RESULT_TIER_GC, default off) β conservative dry-run-first, never deletes a live-referenced object (server#264,341b614). -
2026-06-23 β
noetl/workerv5.45.0 β πͺΆ #104 Phase F: result-tier DR re-derive (NOETL_RESULT_TIER_DR, default off) β materializer verify-and-repair, byte-identical rebuild of a missing/corrupt tier object (worker#131,dd07016). -
2026-06-23 β
noetl/workerv5.44.0 β πͺΆ #104 Phase E: side-effect durability barrier (NOETL_SIDE_EFFECT_BARRIER, default off) β adopt-only, side effects fire exactly once across re-drive; depends on published noetl-tools 3.17 (worker#130,d696f7e). -
2026-06-23 β
noetl/toolsv3.17.0 β πͺΆ #104 Phase E:registry::kind_is_side_effectingside-effect classifier (conservative defaulttrue; onlynoop/rhaifalse) (tools#78,1d49dd5; members noetl-directives + noetl-locator re-published first). -
2026-06-23 β
noetl/serverv3.43.0 β πͺΆ #104 Phase D: mint-authoritative flag (NOETL_RESULT_MINT_AUTHORITATIVE, default off) +result_storedual-write counter (noetl_result_store_dual_write_total) β the reversible dual-write fallback leg of the minting flip; no Cargo.toml change (resolvesnoetl-locator 0.1.1from the registry) (server#263,6f6b9ef). -
2026-06-23 β
noetl/workerv5.43.0 β πͺΆ #104 Phase D: the minting flip βNOETL_RESULT_MINT_AUTHORITATIVE(default off) makes the materializer the authoritative tier writer + resolve-by-URN the primary consume path, with the dual-writtenresult_storeas the fail-safe fallback; newnoetl_worker_result_mint_authoritative_total{path}(tier | legacy_fallback) (worker#129,be6863a). -
2026-06-23 β
noetl/serverv3.42.0 β πͺΆ #104 Phase C: GCS object backend + cell-endpoint registry +GET /api/internal/cells(resolve-by-URN read side; default-off) (server#262,c2d5ca9). -
2026-06-23 β
noetl/workerv5.42.0 β πͺΆ #104 Phase C: resolve-by-URN read path + fixes B/B1 (references-in-state behavior,flatten_single_tool_result, closes OQ6; adds publishedarrow=53, resolves noetl-tools 3.16.0; 38 unit tests) (worker#128,7971041). -
2026-06-22 β
noetl/toolsv3.16.0 +noetl-locatorv0.1.1 β πͺΆ #104 Phase B:ResultCoordinates::parse/from_locator(the inverse oflogical_uri); additive β member bumped 0.1.0β0.1.1, published (tools#77). -
2026-06-22 β
noetl/serverv3.41.0 β πͺΆ #104 Phase B: ensure the siblingnoetl_result_materializerdurable consumer at stream-birth (own ack cursor) (server#261). -
2026-06-22 β
noetl/workerv5.41.0 β πͺΆ #104 Phase B: shadow Feather result tier β separatenoetl_eventsconsume-loop writes over-budget results (tabular β Feather, non-tabular β JSON) at the derived Β§7 key; gatedNOETL_RESULT_MATERIALIZER_ENABLED(default off) (worker#127). -
2026-06-22 β
noetl/serverv3.40.0 β π¦ #104 Phase A: accept the canonical result URI behindNOETL_RESULT_URI_ACCEPT(default off), parsed via the slimnoetl-locator 0.1.0(heavy graph absent from the control plane) (server#260,c89d078). -
2026-06-22 β
noetl/toolsv3.15.0 + new cratenoetl-locatorv0.1.0 β π¦ #104 Phase A: extract the slim dependency-freenoetl-locatorcrate (purestd; ResourceLocator/ResultCoordinates/shard_key/CellPlacement/legacy parse) so the control-plane server parses the result URI without noetl-tools' heavy graph; noetl-tools re-exports it asnoetl_tools::locator(tools#76,dc0c5d8;noetl-locator 0.1.0published to crates.io). -
2026-06-22 β
noetl/workerv5.40.5 β β #95: adopt noetl-tools 3.14.2 (postgres temporal/identity serialization); Cargo.lock pin3.14.1β3.14.2, no worker source change; builtnoetl-worker-rust:v5.40.5@sha256:45212dbeβ¦+ rolled by digest to prod under the live off-server cutover (worker#126,da24952). -
2026-06-22 β
noetl/workerv5.40.4 β β #127: adopt noetl-tools 3.14.1 (task_sequenceper-sub-task context optimization); Cargo.lock pin3.14β3.14.1, no worker source change; builtnoetl-worker-rust:0afbf5c+ rolled to prod under the live off-server cutover (worker#125,0afbf5c). -
2026-06-22 β
noetl/toolsv3.14.2 β β #95: postgrespg_value_to_jsonnow serializes tz-naivetimestamp(NaiveDateTime) + date/time/uuid/numeric (lossless binary-wire decode)/bytea instead of returningnull; theauth0_loginexpires_at: nullrepro (tools#75,6d9b674). -
2026-06-22 β
noetl/toolsv3.14.1 β β #127: behavior-preservingtask_sequenceper-sub-task context reuse βrender_valuebuilds the proxied minijinja context once +build_context_with_overlayskips the redundantto_template_context()clone; micro-bench β61.6%/2.6Γ per-sub-task context cost (tools#74,c8656c1). -
2026-06-22 β
noetl/serverv3.39.6 β β #123: surface a non-iterable loopin:as a terminalplaybook.failedβ the off-server drive decodes the{"error":β¦}envelope (decode_orchestrate_error, metricnoetl_orchestrate_drive_total{stage="drive_error"}) instead of silently wedging (commands=0); orchestrate-core prefixes the offending step name onto theevaluate_looperror (server#258,7f109a9, squash275b914). Code-only (worker stays v5.40.3); 600 server + 135 orchestrate-core tests + clippy; kind-validated prod-exact (absent iterable β FAILED with a clear message, valid[1,2,3]still COMPLETED). -
2026-06-22 β
noetl/serverv3.39.5 β β #121 second-half: gate BOTH off-server-drive sites intrigger_orchestrator_inneronshould_publishsosystem/*execs drive server-builtrun_state(they INSERT tonoetl.event, never enter the WAL); regular execs keep the off-server path (server#257,c421273, squash54ac277). Server-only (worker stays v5.40.3); 612 tests + clippy; kind full-gatesystem/scheduled_cleanupBEFORE 9ΓWAL chain incompleteβ AFTER COMPLETED 0 loops. -
2026-06-21 β
noetl/serverv3.39.4 β β #121 first-half (partial): link gate-offcommand.claimedthroughChainHeads+ don't routesystem/execs to the off-server WAL drive (server#256,77aaa06) -
2026-06-21 β
noetl/workerv5.40.3 β β #125 + #126 adopt noetl-tools 3.14.0 (task_sequencecontrol flow +httpbody data-shape fixes); 10Γ1000 batchpft_flow_testclean (worker#124,6dd3449). -
2026-06-21 β
noetl/toolsv3.14.0 β β #125task_sequencehonoursdo: jump/break/retry(tools#73,638c3c6). -
2026-06-21 β
noetl/toolsv3.13.1 β β #126httptool body underdata(Python-era contract restored; back-compatbodyalias) (tools#72,8dd0e1f). -
2026-06-21 β
noetl/serverv3.39.3 β β #124 distributedtask_sequenceforwardset:/sibling bindings no longer render empty; defer sub-task templates with unresolved vars to the worker re-render (server#255,365d3be). -
2026-06-21 β
noetl/serverv3.39.2 β β #120 reduce barrier no longer deadlocks on open/asymmetric loop joins; runtime liveness filter in the orchestrate-core barrier (server#25428e8950) -
2026-06-20 β
noetl/serverv3.39.0 β β #116 program-scale step 2: execution-affinity single-owner WRITE ORDERING (multi-replica gate-ON validated) β closes the chain-fork race step 1 left open. Affinity routes every trigger for an execution to the replica thatShardConfig::ownsit (non-owner forwards a reverse-proxy POST); owner's single-process drive lock + in-memoryChainHeadsmake the readβadvance atomic, no distributed lock; KV = genesis/handoff vehicle (kv_remote_hitβ0).src/affinity.rs;NOETL_EXECUTION_AFFINITY/NOETL_PEER_URL_TEMPLATE/NOETL_SHARD_INDEX_FROM_HOSTNAME(all default off); metricnoetl_execution_affinity_total{outcome}(server#2525e00d0a). 2-replica gate-ON kind PASS: chains roots=1/dangling=0/walk==total (NO fork),forwarded_ok +9, never-scan + sole-writer across replicas. Follow-up #117 (off-serverfrom_eventsspineevent_id-order wedges fan-in under inversion). -
2026-06-20 β
noetl/serverv3.38.0 β β #115 program-scale step 1: multi-replica coherence DATA LAYER βNOETL_REPLICA_COHERENCE=nats_kv(defaultlocal, prod unchanged) backsChainHeads+ExecDescriptorwith JetStream KV buckets (head CAS + descriptor CAS merge);src/coherence.rs; metricnoetl_replica_coherence_total{structure,op,outcome}(server#2518f39a79). Kind: single-replica parity withlocal; 2-replica cross-replica resolves proven. Necessary-not-sufficient β execution-affinity STAGED (2+ replicas still fork the chain). -
2026-06-20 β
noetl/serverv3.37.0 β β #115 Phase 5 atomic-working-item context (tenet 6):input_binding+NOETL_ATOMIC_ITEM_CONTEXT(default off) (server#250a96ade8) -
2026-06-20 β
noetl/workerv5.40.0 β β #115 Phase 5 forward the atomic-item-context flag onto the off-serverfrom_eventsdrive (worker#1212484d17) -
2026-06-20 β
noetl/serverv3.36.0 β β #115 Phase 6 retire the hot-pathnoetl.eventread class; the table is AUDIT-ONLY (server#249,b71ca1d):NOETL_EVENT_READ_PATH=event_scan|audit_only(defaultevent_scan, prod unchanged). Underaudit_onlythe remaining lifecycle readers (get_catalog_id,inherit_parent_trace, dedup-audit + container-callback catalog/existence) serve from the in-memoryExecDescriptor; cold βnoetl.command(synchronous queue) β never anoetl.eventscan. Proof metricnoetl_event_hotpath_reads_total{site,outcome}. Gate-ON kind-validated: hot-path scan Ξ0 + drivestate_build_total/event_scansΞ0 β ZEROnoetl.eventscans on the hot path, end-to-end; audit/replay still work; 585 tests + clippy green. RFC never-scan end state (tenet 3) reached under the flag. -
2026-06-19 β
noetl/workerv5.37.0 β β #115 Phase 4 off-server state-builder kernel + WAL shadow loop (worker#118,fef961c): pool-side per-execution chain index sourced from thenoetl_eventsWAL;chain_walk()headβroot spine inevent_idorder (parity by construction); cache keyed by the immutable chain head (CacheHit / Incremental tail-advance / ColdRebuild). Live WAL shadow loop (NOETL_STATE_BUILDER_SHADOW, default off) + metrics. Gate-ON kind-validated (993 WAL events, 0 noetl.event scans, 28 cold + 21 incremental); 8 unit tests + clippy green; default off. Drive cutover staged. -
2026-06-19 β
noetl/serverv3.33.0 β β #115 Phase 4NOETL_STATE_BUILDER=offserver|serverflag scaffold (server#246,3e6006d, defaultserver): the server-side flag for the off-server state-builder drive cutover (staged). 2 config tests; no prod default changed. -
2026-06-19 β
noetl/serverv3.32.0 β β #115 Phase 3 chain-walk state builder (flagged, default-off) (server#245, ai-meta pointer bumped): behindNOETL_STATE_BUILD_MODE=chain_walkthe drive reconstructsWorkflowStateby following the one-levelprev_event_idchain headβroot (in-memoryChainHeadshead +(execution_id,event_id)PK lookups β never aWHERE execution_idscan) β samefrom_events(parity by construction); event-scan kept as the default + fallback (cold-head / lag / non-genesis).NOETL_STATE_BUILD_PARITY_CHECKshadow-builds both ways in oneREPEATABLE READsnapshot. New metricsnoetl_state_build_total{mode,outcome}/_event_scans_total(no-scan proof) /_chain_hops/_parity_total{result}. Gate-ON kind-validated: parity 41/41 MATCH, scans=0 / 1064 hops / 0 fallbacks, all topologies COMPLETE, sole-writer + lag-0, 577 tests + clippy green. No prod default changed. -
2026-06-19 β
noetl/serverv3.31.0 β β #115 Phase 2 one-levelprev_event_idevent chain (server#244, ai-metaafdb365): eachnoetl.event/noetl.commandcarries the chain link, stamped at the emit chokepoint from a per-execution chain-head watermark (ChainHeads), covering both gate paths + the materializer. Additive; nothing reads it yet (Phase 3). Kind-proven walkable/1-root/no-gap/no-scan across 6 gate-ON topologies; 573 tests + clippy green. Companion DDLnoetl/noetlecd16a2(noetl#667). No prod default changed. -
2026-06-19 β
noetl/serverv3.30.0 β β #115 Phase 1 surface_ref/_storeon kept refs +refs_in_statedefault true (server#243): consume-side accessors for{{ step._ref }}lazy-load + storage-tier predicates; references stay out of state/commands by default (worker selective-resolve landed in worker#117 v5.36.0). Closed #113 + #114; kind gate-ON all 9 stalls COMPLETE. -
2026-06-19 β
noetl/serverv3.29.4 β π #113 off-server drive: recover offloaded drive result + stop drive on cancel (server#241, rig e2e#63).apply_worker_orchestrationresolves+decodes an offloaded__orchestrate__result (over the 100KB inline budget β durablereference.ref) instead of dropping it β non-convergent re-loop (metricref_resolved); cancel now matches underscoreplaybook_cancelled+ a terminal guard evicts the orch-cache (no restart). Kind gate-ON proven; 5/9 #113 fixtures COMPLETE, the other 4 hit a distinct oversized-command.issuedstall β #114. No prod default flipped. -
2026-06-19 β
noetl/serverv3.29.5 β π #114 offload oversized command context: acommand.issuedcontext overNOETL_COMMAND_CONTEXT_MAX_BYTES(512KB) is offloaded tonoetl.result_storewith a{__context_ref__}marker, resolved inget_command/claim_command(server#242, rig e2e#64) β published events stay under NATSmax_payload, no publish-wall wedge. Kind gate-ON: rig PASS, all command.issued <1MB, 6 of #113's 9 fixtures COMPLETE; remaining 3 + cutover gated on the refs_in_state consume side (#101). No prod default changed. -
2026-06-19 β
noetl/serverv3.29.4 β π #113 recover offloaded drive result + stop drive on cancel:apply_worker_orchestrationresolves+decodes an over-budget__orchestrate__drive result viaresult_store.resolve(metricref_resolved) instead of dropping it β non-convergent re-loop; cancel matches underscoreplaybook_cancelled+ a terminal guard evicts the orch-cache (server#241, rig e2e#63). No prod default changed. -
2026-06-19 β
noetl/serverv3.29.3 β π― #103 cutover COMPLETE, FLIP-READY: the 2ExecutionServicecancel/finalize writers route through theemit_eventchokepoint (server#240, e2e rig e2e#62) β the last synchronous servernoetl.eventwriter under the gate is closed. Kind-proven both modes (gate-off byte-identical INSERT; gate-on PUBLISHED + materializer sole writer + terminal state + 0 loss/dup). All three flip blockers closed βPUBLISH_ONLYflip is a staged operator decision. Default-off; no prod default changed. -
2026-06-19 β
noetl/serverv3.29.2 β off-server-drive Γ gate crash-recovery: cold-cache apply rebuildsWorkflowStatefrom the durable log instead of dropping the in-flight drive result (server#238, refs #104/#103). Unblocks thePUBLISH_ONLYflip (off-server drive Γ gate now kind-proven). Confined to the cold branch; no prod default changed. -
2026-06-19 β
noetl/toolsv3.13.0 +noetl/workerv5.34.0 β #103 ack-after-materialize durability: deferred ack-after-processing capability (tools#71:AckMode::Defer+$JS.ACKdurable handles +ack/nack/term) + in-process CQRS materializer consume-loop (worker#115: drainβprojectβack-only-on-success, redeliver on failure) + system-pool wiring (ops#194). Kind fault-injection: gate-on sole-writer loss=0 across a mid-drain failure. Default-off. -
2026-06-18 β
noetl/serverv3.28.0 β worker-driven orchestrator drive now default ON (NOETL_ORCHESTRATE_PLUGIN_DRIVEdefaults true) (server#233, closes #108; scale-soak-gated, revert ==false). -
2026-06-18 β
noetl/workerv5.33.0 β pool-affinity decline (drive isolated on the system pool) (worker#114, refs #108 (b)). -
2026-06-14 β
noetl/workerv5.22.0 β transfer endpoint credential-alias resolution, both SnowflakeβPostgres directions (worker#87, closes #99). -
2026-06-14 β
noetl/toolsv3.10.0 β SnowflakeβPostgres transfer arms + flatten credential config (tools#65, closes #99). -
2026-06-14 β
noetl/toolsv3.9.2 β Snowflake SQL-API context in request body + multi-statement split (tools#64, refs #98). -
2026-06-14 β
noetl/toolsv3.9.1 β set User-Agent on the Snowflake HTTP client (tools#63). -
2026-06-14 β
noetl/toolsv3.9.0 β Snowflake key-pair JWT authentication (tools#62; kind-validated on livesf_testaccount). -
2026-06-12 β
noetl/serverv3.5.0 βPOST /api/execute/batch- opt-in exactly-once dedup window (server#189, RFC #90 Phase 7 β scale hardening).
-
2026-06-12 β
noetl/workerv5.19.0 β batch dispatch + dedup opt-in + per-subscription rate limits (worker#79, RFC #90 Phase 7 β scale hardening, closes #90). -
2026-06-12 β
noetl/cliv4.11.0 βnoetl subscribe, local-mode subscription listener (cli#60, closes cli#59, RFC #90 Phase 6). Standalonekind: Subscriptionlistener +FileEventSinkJSONL trail +local_diskstore-and-forward spool;cli-only (reusesnoetl-toolsv3.5.0 source+spool). ai-meta β cli2fb3fb0. -
2026-06-11 β
noetl/serverv3.0.6 β round-trip JSONnullin whole-object{{ step }}references (server#177, closes noetl/ai-meta#89). Anullfield in a{{ step }}envelope rendered as the JS tokenundefined(invalid JSON), so the consuming step received an unparseablestr;render_to_valuenow retries with| tojson(undefined/none β JSONnull) β the server renderer had diverged from the noetl-tools engine that already did this. Kind-validated: cursor pagination collects all 35 events through the terminalnext_cursor: nullpage. ai-meta pointer β server8e17fbe. -
2026-06-10 β
noetl/toolsv3.1.1 β multi-tool sibling references (tools#48, closes noetl/ai-meta#87).TaskSequenceToolnow injects each sub-tool's result under its label so a later sub-tool resolves{{ <label>.<field> }}(was rendering empty β asyntax error at or near ","in unquoted numeric SQL positions). Worker adopts via worker#69. Kind-validated (save_edge_casestest_large_payloadβrecord_count = 100). ai-meta pointer β tools76f942a+ workerb97f642. -
2026-06-10 β
noetl/serverv3.0.3 β container-callback insert matches the deployednoetl.eventschema (server#173, tracks noetl/ai-meta#43). The handler'scall.doneinsert targeted a non-existentattemptcolumn β HTTP 500 on every watcher callback; replaced with an inline INSERT matching the working ingestion path. Unblocked the container-callback chain (kind-val GREEN both probes). ai-meta pointer β5d2cf58. -
2026-06-10 β
noetl/guiv1.11.0 + v1.11.1 β credential View/Edit recovery for pre-wallet records (gui#36, closes noetl/ai-meta#82) +dev:kindconvenience script (gui#35). ai-meta pointer β8cacc9e. -
2026-06-10 β
noetl/serverv3.0.2 β container-toolcommandtype contradiction fix (server#172, closes noetl/ai-meta#81).ToolSpec.commandOption<String>βOption<serde_json::Value>: the container tool's arraycommandnow decodes server-side + passes through to the worker'sVec<String>; scalars stay JSON strings for shell/db tools. Kind-val GREEN (K8s Job reaches Complete 1/1). -
2026-06-08 β
noetl/toolsv2.24.2 β clippy cleanup: 15 warnings resolved across 7 files (tools#44, closes tools#42). Mechanical lint fixes, zero behavioral changes. -
2026-06-05 β
noetl/toolsv2.18.4 β duckdbcommand:alias (parity with postgres) + httpparams/headers/formnon-string coercion (tools#26); worker adopts it (worker#51). Unblocks the pagination + http + duckdb-commandfixtures. -
2026-06-05 β
noetl/toolsv2.18.3 β postgrescommand:alias- multi-statement SQL on postgres + duckdb (tools#24, closes tools#23);
worker adopts it (worker#50).
duckdb_test+json_serialization_saveGREEN.
- multi-statement SQL on postgres + duckdb (tools#24, closes tools#23);
worker adopts it (worker#50).
-
2026-06-05 β
noetl/toolsv2.18.2 β postgres tool surfaces the real SQLSTATE + message instead ofdb error(tools#21); worker bumped to it (worker#49). -
2026-06-05 β
noetl/serverv2.22.0 β Secrets Wallet Phase 2: GCP Cloud KMSKeyManager(Cloud KMS:encrypt/:decrypt+ Workload Identity); runtimeNOETL_KMS_PROVIDER(local/gcp-kms); KEK can leave the process (server#81, tracks #61). Kind-validated on local. -
2026-06-05 β
noetl/serverv2.21.0 β Secrets Wallet Phase 1c/1d: credentials + keychain store envelope-encrypted (per-record DEK wrapped by the KEK); self-describing{"v":1,β¦}blob, forward-only (server#79, tracks #61). Kind-validated end-to-end. -
2026-06-05 β
noetl/serverv2.20.0 β Secrets Wallet Phase 1b: envelope-encryption core βKeyManager/LocalDevKms/EnvelopeCipher(server#77). -
2026-06-05 β
noetl/serverv2.19.8 β Secrets Wallet Phase 1a: remove the all-zeros default encryption key, fail closed (server#75, tracks #61). Kind-validated. -
2026-06-05 β
noetl/toolsv2.18.5 β dollar-quote-aware statement splitter; the 2.18.3 splitter shredded plpgsql$$ β¦ $$blocks (tools#27). -
2026-06-05 β
noetl/serverv2.19.7 β defer task_sequence_prev/_resultsrefs at command-build (server#73); nested-pipeline templates render at runtime βiterator_save_testGREEN. -
2026-06-05 β
noetl/workerv5.11.3 β resolve keychain aliases on task_sequence sub-tasks (worker#48); nested postgres-in-pipeline steps connect. -
2026-06-05 β
noetl/serverv2.19.6 β credential store base64-armors the AES-GCM blob for the TEXTdata_encryptedcolumn (server#71); keychain creds register + round-trip. -
2026-06-05 β
noetl/workerv5.11.2 β resolves keychain alias under the v10credential:key (worker#46). -
2026-06-05 β
noetl/serverv2.19.5 β v10 control-flow end-to-end (server#69, 6 commits): catalog INT4 + catalog_id alias, ToolSpec skip-null, orchestrator end-step-with-action + task_sequence flatten + intra-pass dedup, template Chainable- undefined, end-step trigger gate. -
2026-06-05 β
noetl/workerv5.11.1 β preserve arraytool_configfor task_sequence (worker#44). -
2026-06-05 β
noetl/toolsv2.18.1 β task_sequenceparse_tasksaccepts worker-envelope shape. -
2026-06-04 β
noetl/serverv2.19.4 β orchestrator template context: step data at top level + call.done capture. -
2026-06-04 β
noetl/toolsv2.18.0 β TaskSequenceTool. -
2026-06-04 β
noetl/toolsv2.17.1 β PythonTool result- global capture. -
2026-06-04 β
noetl/serverv2.19.3 β three fixes shipped together: pipeline flat shape decode (#61), failure termination (#63), workbook resolution (#65). -
2026-06-04 β
noetl/serverv2.19.2 β v10 workload + input alias (#59). -
2026-06-04 β
noetl/serverv2.19.1 β EE-5 lax decode for integer execution_id (#57). -
2026-06-04 β
noetl/serverv2.19.0 β Phase F R4-4b (ExecutionService refactor + cluster-wide list fan-out). -
2026-06-04 β
noetl/serverv2.13.0 β v2.19.0 β Phase F R4 series (DbPoolMap N+1 pool layer through R4-5 kind validation). -
2026-06-04 β
noetl/gatewayv3.2.0 β Phase F R3b-2 shard-info twin endpoint.
How agents (Claude / Codex / Cursor) operate across this
ecosystem β pointers into the rule files in
agents/rules/:
- Agent coordination β Claude β Codex lane ownership + shared-surface deconfliction (repos/server, pointer commits, prod image, kind) + append-only session board.
- Issue tracking β when + how to open an ai-task issue (rule).
- Continuous Integration β what the ai-meta CI gate checks (submodule pointers + docs), how to read a red pointer check and fix a bad bump, and the per-repo PR-gate status table. This repo had zero workflows until 2026-10-02, which is how #370 merged five pointer bumps unchecked.
- Specs Index β the browsable map of general + cross-repo specs. Pointer list only: file = truth, issue = tracking, wiki = reading room, so a spec's acceptance criteria live in its file and are never copied here.
- Wiki convention β ai-meta wiki vs per-repo wikis (rule). Rule 0a enumerates the four wiki pages that drift together; spot-check Home against the open-issue list at session start.
- Handoffs β file-based cross-agent dispatch (rule).
- Deployment validation β kind-first before GKE (rule).
- Execution model β the foundational shape (rule).
-
Data access boundary β NoETL data via
server API only; workers never touch
noetl.*direct (rule). - Observability β metrics + traceability + snowflake IDs (rule).
- Just landed in this codebase? Read Repo Map, then Execution Model, then the umbrella for whatever you're working on.
- Picking up an in-flight task? Find the matching umbrella page above; it has the full state of the work + the next concrete step.
-
Need to file new work? Follow the
issue tracking convention β open the ai-task
issue on
noetl/ai-meta, then add the umbrella to the table above and create the corresponding wiki page. -
Maintenance pass? Refresh this Home + the Sessions Log
- the Releases page + the matching
Umbrella-*.mdpage when you bump a submodule pointer. All four pages drift together β see Rule 0a's checklist.
- the Releases page + the matching