ares-server 0.10.0

ARES agent server with multi-provider LLM support, tool calling, RAG, and MCP integration
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# Upstream Parity Ledger

## What this document is

This document records every deliberate divergence between our kernel and the upstream model.
The paper states the reference model.
Our kernel sometimes chooses a different behavior on purpose.
This file is the audit ledger for those decisions.

- Date: 2026-08-24
- Scope: `crates/cordis` versus the model in the paper
- Method: every divergence lists the claim, the rationale, and the enforcement or test point

## Divergence table

| # | Divergence | Decision |
|---|---|---|
| 1 | Failed registrations stay visible | `Failed{error}` is a terminal rest state with reflective wiring |
| 2 | Peer-dependency compatibility | Majors-only version buckets, no structural checks |
| 3 | Late inject declarations | Eager reconciliation on `Active` fibers |
| 4 | Hot swap mechanics | Out-of-band trial then promote, honest `swap_mode` reporting |
| 5 | Dynamic library loading | Strictly opt-in behind `hmr`, exact fingerprint handshake |
| 6 | Factory collection | Inventory primary, manual chains as fallback |
| 7 | Serial dispatch | Direct alias of `Bail`, waterfall uses real `next` continuations |
| 8 | Worker supervision | Reserved exit codes plus stdin-EOF death detection |
| 9 | Log routing | One exporter router fans records to every gated sink |
| 10 | Dependency withdrawal | Genuine loss rests working fibers `Pending` (reversible); apply errors stay terminal `Failed` |
| 11 | Dispatch participation knobs | `EventOptions{prepend,global}` + `emit_filtered`; filters never exclude global listeners |
| 12 | Reads during transitions | Strict `get` refuses transitioning owners; `get_relaxed` is the explicit opt-in |

| 14 | Kernel operations are interceptable | Five meta-events veto or rewrite kernel ops; unregistered path stays zero-cost; sync bridges fall open |
| 15 | Readiness gates wait quietly | Closed `ready_when` barriers rest fibers `Pending` (never `Failed`); availability predicates remain the loud path |
| 16 | Config cascades batch | Concurrent provider updates collapse to one dependent convergence wave |
| 17 | Validation errors carry paths | Pre-flight failures surface `message` + `path` issues beside the legacy string |
| 18 | Logger adopted natively | Ring buffer, effect-owned exporters, per-name routing live in the kernel crate |
| 19 | Timers adopted natively | Six fiber-scoped primitives share one std-only wheel thread |
| 20 | Accessor traffic bypasses interception | Name-keyed computed properties resolve outside the `internal/get` / `internal/set` waterfalls entirely |
| 21 | Intercept layers are ordered and inspectable | Append-on-set keeps the innermost layer effective; `intercept_chain` returns outermost..innermost |
| 22 | Restart errors keep the old application | `Fiber::update` propagates errors with the fiber still `Active`; a veto parks `vetoed_config` and returns `Ok` |
| 23 | Config interception covers activation | The `internal/config` waterfall consults on first activation too, not only re-applies |
| 24 | Module changes fan out through one graph | `change_many` computes the affected set read-only first, reloads each plugin once per transaction, and rollback keeps siblings `Active` (EXCEEDS upstream: no module-graph concept) |
| 25 | Entries relocate without losing identity | PATCH move-then-update answers 409 on conflict; `/move` renames the `{id}:*` subtree; in-place refresh preserves the fiber (EXCEEDS upstream: flat key list only) |
| 26 | Subtask cancellation is wired end to end | Sticky cancel tokens honored at step boundaries plus an external trigger (EXCEEDS upstream: reference defines the hook but never wires it) |
| 27 | Deterministic micro calls are cached | LRU+TTL keyed `(model, system, input)` with `cache_hit` telemetry; salvage/retry results never cached (EXCEEDS upstream: roadmap prose only) |
| 28 | Guided grammars ride typed hints | Schema-shaped values become `json_schema` everywhere; raw GBNF stays a provider extension; absent hint = byte-identical wire |
| 29 | Duplicate embeddings cost one backend call | Content-hash dedup on local and HTTP paths fans vectors back to duplicate slots (EXCEEDS upstream: roadmap prose only) |


Each row expands below with the claim, the rationale, and the evidence.

### 1. Failed registrations stay visible

- Upstream expectation: a factory error leaves the fiber permanently `Inactive` and unreachable.
- Claim: `Failed{error}` is a terminal VISIBLE rest state.
- Detail: `RegistryService::register` returns `Err`.
- Detail: the fiber enters the bookkeeping graph through `RegistryService::wire_failed_registration`.
- Detail: `ReflectService` wiring registers the fiber against the attempted provider key.
- Detail: notify fans out, so dependents observe the provider loss reactively and rest `Inactive`.
- Detail: a later successful registration allocates a fresh fiber id and supersedes the failed fiber.
- Rationale: operators inspect failures directly.
- Rationale: dependents get a real notification instead of silence.
- Rationale: fresh-id supersession keeps the provided slot free for retry.
- Source: `crates/cordis/src/registry.rs`, `wire_failed_registration`
- Test: metatheory property 3 legs D-H, `metatheory_dependent_never_active_without_provider`

### 2. Peer dependencies use majors-only buckets

- Upstream expectation: compatibility needs full structural interface checks.
- Claim: compatibility compares majors only.
- Detail: versions are plain `u64` values.
- Detail: `major(v) = v / VERSION_MAJOR_SCALE` and `floor(v) = v % VERSION_MAJOR_SCALE`.
- Detail: `VERSION_MAJOR_SCALE` equals `100_000`.
- Detail: a requirement binds when the major matches AND the provider reaches the floor.
- Detail: any mismatch leaves the dependent fiber `Inactive`.
- Detail: structural interface compatibility is deliberately NOT attempted.
- Rationale: majors-only buckets cover practical drift between builds.
- Rationale: full structural compatibility remains the open problem the paper defers.
- Source: `Context::provide_versioned` and `VERSION_MAJOR_SCALE` in `crates/cordis/src/context.rs`
- Source: `Fiber::declare_inject_versioned` in `crates/cordis/src/fiber.rs`
- Tests: the `version_conformance` module in `crates/cordis/src/metatheory.rs`

### 3. Late inject declarations reconcile eagerly

- Upstream expectation: a late declaration waits for an external refresh trigger.
- Claim: a declaration landing on a fiber resting `Active` reconciles eagerly.
- Detail: `Fiber::reconcile_after_declare` runs with the same transition shape as `refresh`.
- Detail: satisfied declarations update the epoch in place.
- Detail: unsatisfied declarations undo effects and rest the fiber `Inactive`.
- Detail: a declaration that races an in-flight refresh folds into that refresh through a pending flag.
- Detail: declarations on `Inactive` and `Failed` fibers wait for the next transition.
- Rationale: eager recompute loses no racing declaration.
- Rationale: the quiescence invariant survives every declaration path.
- Source: `Fiber::declare_inject` and `Fiber::reconcile_after_declare` in `crates/cordis/src/fiber.rs`
- Source: register paths in `crates/cordis/src/registry.rs`
- Test: reactive invariant leg of `dependent_never_active_without_provider`

### 4. Hot swap drains then shifts

- Upstream expectation: swap mutates providers in place.
- Claim: swap builds out-of-band, then promotes.
- Detail: new instances build inside a scratch context.
- Detail: `SwapPromotion` bridges the new values through intercept bindings.
- Detail: the old fiber disposes while the bridge keeps serving consumers.
- Detail: promotion moves bridge values into the store before intercept removal.
- Detail: consumers never observe an absence window.
- Detail: an unverifiable swap reports `swap_mode = "unverified"` instead of a fake success state.
- Rationale: drain-and-shift proves a zero absence window under concurrency.
- Rationale: honest reporting lets operators tell verified swaps from unverified ones.
- Source: `Loader::replace_provider` and `SwapPromotion` in `crates/cordis/src/loader.rs`
- Tests: `replace_provider_zero_absence_window`
- Tests: `rebuild_same_type_verified_swap` probes resolution from a concurrent task during the swap

### 5. Dylib loading is strictly opt-in with a fingerprint handshake

- Upstream expectation: dynamic library loading runs as a first-class default.
- Claim: dylib loading requires the `hmr` cargo feature.
- Detail: the default production path is file-watch plus `Fiber::reload` through `watcher::watch_many`.
- Detail: as of this change, every dylib load performs an exact ABI fingerprint handshake.
- Detail: the plugin must export `cordis_plugin_fingerprint`.
- Detail: the returned string must equal the host fingerprint exactly.
- Detail: a missing symbol refuses the load.
- Detail: a mismatched string refuses the load and names both fingerprints.
- Rationale: stale libraries fail fast at load time.
- Rationale: unchecked dylibs corrupt the process across the FFI boundary.
- Source: `load_plugin_so` and `FINGERPRINT_SYMBOL` in `crates/cordis/src/hmr.rs`
- Tests: `load_plugin_so_rejects_missing_fingerprint`
- Tests: `load_plugin_so_rejects_mismatched_fingerprint`

### 6. Inventory collection is the primary registration path

- Upstream expectation: registration walks an explicit hand-written factory list.
- Claim: inventory collection gathers factories automatically as the primary path.
- Detail: hand-written `register_plugins` chains remain as the fallback without the `inventory` feature.
- Detail: the linker drops inventory nodes from crates that nothing references.
- Detail: parity tests force-link every contributing crate before collection.
- Rationale: collection deletes a hand-maintained list and its drift bugs.
- Rationale: the linker failure is silent, so it needs a written warning.
- Source: `register_inventory_factories` in `crates/cordis/src/lib.rs`
- Test: `inventory_registry_matches_expected_factory_set` in `tests/inventory_parity.rs`

### 7. Serial dispatch aliases Bail, waterfall composes handlers

- Upstream expectation: serial dispatch differs from bail semantics.
- Claim: `Dispatch::Serial` is a direct alias of `Dispatch::Bail`.
- Detail: both variants run the same `run_bail_handlers` code path.
- Detail: waterfall is around-middleware.
- Detail: every waterfall handler receives a real `next` continuation.
- Detail: the terminal `next` runs the core operation.
- Detail: no sentinel value ever stops a chain.
- Rationale: one shared bail mode removes a near-duplicate implementation.
- Rationale: around-middleware composition matches the paper shape directly.
- Source: `EventsService::dispatch` in `crates/cordis/src/events.rs`
- Tests: `serial_stops_at_first_non_null_result`
- Tests: `waterfall_around_short_circuit_skips_core`

### 8. Worker supervision uses reserved exit codes plus stdin EOF

- Upstream expectation: the paper defines no process supervision model.
- Claim: supervised workers terminate through a fixed exit-code protocol.
- Detail: `EXIT_RESTART` (51) asks the daemon for a fresh worker.
- Detail: `EXIT_QUIT` (52) stops without a restart.
- Detail: `EXIT_BOOT` (53) surfaces boot failure non-zero to the manager.
- Detail: codes sit in the 51-53 band, clear of shell (1-2) and panic (101) codes.
- Detail: workers set `CORDIS_SUPERVISED` watch stdin; EOF means the daemon died.
- Rationale: pipe EOF is the only loss-free signal that survives daemon SIGKILL.
- Source: `crates/cordis/src/worker.rs`, `src/supervisor.rs`
- Tests: `exit_codes_are_distinct`
- Tests: `child_exit_codes_drive_loop`

### 9. Log routing fans out through one exporter router

- Upstream expectation: the paper defines no observability surface.
- Claim: one router fans call records out to every registered exporter.
- Detail: per-exporter level gates filter records before delivery.
- Detail: exporter failures stay contained; inference never fails on them.
- Detail: registration validates once; duplicate registrations are skipped.
- Rationale: a single fan-out point replaces ad hoc sink plumbing per consumer.
- Source: `ExporterRouter` in `crates/ares-llm/src/exporter.rs`
- Tests: `router_fans_out_to_all_exporters`
- Tests: `accepts_gate_filters_records`

### 10. Dependency withdrawal is reversible for working fibers

- Upstream expectation: a fiber whose provider disappears rests `Inactive` (or is disposed) and never comes back on its own.
- Claim: a previously-working runner fiber whose dependency genuinely vanished disposes its effects LIFO under `Unloading` and rests a new `Pending` state; when the provider returns it reactivates through `Loading`.
- Detail: `Pending` is reserved for reactive waiting only — an apply error still rests terminal `Failed{error}` (row 1), and a peer-version constraint refusal over an existing-but-incompatible provider still rests `Inactive` because the provider remains available.
- Detail: eligibility requires one fully-satisfied refresh pass first; registration cannot mark a fiber eligible.
- Detail: `Pending` fibers reserve their registry key and survive `prune_disposed`, so reactivation needs no re-registration.
- Rationale: the paper's permanently-Inactive outcome discards a healthy instance that only waits for its dependency; keeping it reversible preserves work.
- Source: `FiberState::Pending`, the reactive-loss branch of `Fiber::refresh` in `crates/cordis/src/fiber.rs`
- Tests: `dependent_reactivates_when_provider_returns`, `failed_stays_failed_on_dep_return`, `pending_fiber_survives_prune_disposed`

### 11. Dispatch participation knobs and filtered emits

- Upstream expectation: listener registration has fixed semantics with no ordering or participation control.
- Claim: flat listeners register through `on_with` / `once_with` with `EventOptions { prepend, global }`; `emit_filtered` runs a per-dispatch predicate over non-global listeners.
- Detail: `prepend: true` inserts at the front of the dispatch-order list; `global: true` marks the listener realm-agnostic, and filters never exclude it.
- Detail: a filter exclusion skips one dispatch without unregistering the listener.
- Detail: the historical `on` / `once` / `emit` signatures delegate unchanged, and the broadcast bus fan-out is not filtered.
- Rationale: per-realm policies need ordered, selectively-participating listeners without duplicating the bus.
- Source: `EventOptions`, `EventsService::on_with` / `once_with` / `emit_filtered` in `crates/cordis/src/events.rs`
- Tests: `prepend_ordering_observed`, `filter_excludes_nonmatching_contexts`, `global_bypasses_filter`

### 12. Reads during transitions are explicit and relaxed

- Upstream expectation: every read either resolves an Active value or fails; mid-transition values are unreachable by construction.
- Claim: strict `Context::get` keeps refusing providers resting in transitional states; `Context::get_relaxed` serves locally-owned values while their owner sits in `Loading` / `Reloading` / `Unloading` / reactive `Pending`.
- Detail: terminal rest states stay refused even relaxed — disposed owners (undos already ran) and `Failed{error}` owners return nothing.
- Rationale: lifecycle and observer code must inspect the value that is about to serve or was just retracted; making that a distinct method keeps the default read conservative.
- Source: `Context::get_relaxed` in `crates/cordis/src/context.rs`
- Test: `relaxed_read_succeeds_while_provider_transitioning`

### 13. Fiber state observers

- Upstream expectation: the model defines no notification surface for individual fiber state changes.
- Claim: `Fiber::subscribe_state` fans every lifecycle transition out to synchronous observers.
- Detail: observers run inline under the short state-lock critical section and MUST NOT call back into the fiber.
- Detail: observer panics are caught, so one broken observer cannot corrupt a transition; cancelled subscriptions are pruned on the next event.
- Rationale: tooling (admin surfaces, tests, supervision) needs transitions as they happen, not just polling after quiescence.

### 14. Kernel operations are interceptable through meta-events

- Upstream expectation: reads, writes, config resolution, restart schedules, and listener registration are fixed kernel behavior with no override points.
- Claim: five veto meta-events (`internal/get`, `internal/set`, `internal/config`, `internal/update`, `internal/listener`) wrap those operations and an `internal/dispatch` observer reports every non-internal dispatch with `(mode, name, args)`; the un-intercepted path is a zero-cost gate, and synchronous bridges FALL OPEN on runtimes that cannot park the worker.
- Detail: `internal/get` — a non-null terminal replaces the value a strict read returns, `{"refuse": true}` fails the lookup outright, null passes, a chain error refuses the read; a redirect verdict continues the lookup at the parent frame.
- Detail: `internal/set` — a chain error vetoes THIS write; the previous binding stays fully intact (no store/owners/version mutation).
- Detail: `internal/config` — the chain's non-null terminal IS the effective config staged for that apply pass; a chain error rests the fiber terminal `Failed{error}` (row 1 semantics unchanged).
- Detail: `internal/update` — a bail or explicit JSON false skips the restart; the fiber keeps serving its current application and the deferred config stays visible via `vetoed_config`.
- Detail: `internal/listener` — a bail or chain error cancels the registration and the caller receives an INERT handle; neither registry ever sees the listener (fail-closed).
- Detail: every consult checks `listener_count == 0` first (map-lookup cost); a thread-local fence keeps operations made inside a chain un-intercepted; single-thread tokio flavors log a warning and fall open, matching historical behavior.
- Detail: `bail_from` / `waterfall_from` / `waterfall_async_from` carry the operating context through an optional per-dispatch `ListenerFilter`; exclusions skip one dispatch without unregistering.
- Rationale: policy layers need to observe and veto kernel operations without duplicating them; the zero-cost gate keeps the default path byte-identical for every existing caller.
- Source: `INTERNAL_*_EVENT` constants, `intercept_get/set/config/update/listener`, `bail_from` / `waterfall_from` / `waterfall_async_from`, and the synchronous bridges in `crates/cordis/src/events.rs`
- Source: consult points in `Context::get` / the provider-write path (`crates/cordis/src/context.rs`); config staging and the update veto in `crates/cordis/src/fiber.rs`
- Tests: `get_interceptor_rewrites_read`, `set_interceptor_vetoes_write_leaves_old_value`, `config_interceptor_rewrites_effective_config`, `update_interceptor_veto_skips_restart_keeps_config`, `listener_interceptor_bail_cancels_registration_inert_handle`, `internal_dispatch_observes_non_internal_only`, `interceptor_error_fails_fiber_activation`, `target_carrying_dispatches_filter_per_dispatch`

### 15. Readiness gates wait quietly; availability predicates fail loudly

- Upstream expectation: the model defines no way to hold a produced service out of rotation while its environment warms up (and nothing distinguishes that from failure).
- Claim: `register_with_readiness` installs a composable `ReadinessBarrier` consulted before every activation pass; while it reports not-ready the fiber rests inspectable `Pending` — quiet waiting that NEVER becomes `Failed` — while availability predicates (`Service::check`) remain the loud complement resting `Failed{error: "availability predicate rejected service"}`.
- Detail: `ReadinessBarrier::new(pred)` wraps one `Fn(&Arc<Context>) -> bool`; `.and(other)` AND-composes; `with_readiness([a, b, c])` folds any number of barriers (an empty list is vacuously ready).
- Detail: `.watching([TypeId])` unions the provider keys whose settlements re-kick the gated fiber through the `ReflectService` fan-out — an external provide or withdrawal re-evaluates the gate without touching the fiber.
- Detail: the factory runs once at registration (config errors still surface immediately); a closed gate only keeps the produced service OUT of consumer reach because strict `get` refuses non-`Active` owners; opening the gate activates without re-running the factory.
- Rationale: not-ready-yet (warming caches, absent external system) differs fundamentally from broken; conflating them buries healthy waiting fibers under failure noise, and separating them lets operators read intent from state alone.
- Source: `ReadinessBarrier`, `with_readiness`, `register_with_readiness`, and the re-kick wiring in `crates/cordis/src/registry.rs`
- Source: the readiness consult in `Fiber::refresh` (`crates/cordis/src/fiber.rs`)
- Tests: `ready_when_holds_pending_until_true_then_activates`, `readiness_composes_and_semantics`, `external_rekick_reactivates_waiting_fiber`

### 16. Concurrent config updates collapse to one cascade wave

- Upstream expectation: every provider settle triggers its own full dependent refresh wave.
- Claim: an in-flight ledger marks providers mid-reapply; dependents defer during the window and converge EXACTLY ONCE per settled batch.
- Detail: `CASCADE_INFLIGHT` maps fiber id to open-window count (reentrant-safe); `Loader::drive_fiber_update` opens and closes windows around one live re-apply.
- Detail: the kernel refresh path consults `cascade_any_inflight`, so a storm of racing patches costs one dependent apply pass and ends Active with the final config.
- Rationale: N concurrent patches against one provider must not cost N dependent convergence waves.
- Source: `CASCADE_INFLIGHT`, `cascade_begin` / `cascade_end` / `cascade_any_inflight` in `crates/cordis/src/loader.rs`
- Test: `concurrent_config_updates_collapse_to_single_cascade`

### 17. Config pre-flight failures carry structured issues

- Upstream expectation: configuration errors are lossy prose strings.
- Claim: plugins reject configs with `ValidationIssue { message, path }` items aggregated in a `ValidationError`; `CordisError::validation` lifts the aggregate into the existing `invalid config:` class, and the loader trial stashes per-entry failures so the admin PATCH answers 4xx with a machine-readable `issues` array beside the legacy `error` string.
- Detail: stash slots mirror the LATEST trial outcome; recording a non-validation error clears the entry and consumption removes it, so a later successful patch carries no `issues`.
- Rationale: API consumers need to render field-level feedback, not parse sentences.
- Source: `ValidationIssue` / `ValidationError` / trial stash in `crates/cordis/src/error.rs`; `CordisError::validation` in `crates/cordis/src/service.rs`; issue attachment in `crates/ares-http/src/api/handlers/admin/cordis.rs`
- Test: `patch_endpoint_returns_structured_issues_on_bad_config`

### 18. The logger lives in the kernel crate

- Upstream expectation: logging ships as a satellite console package beside the kernel.
- Claim: the logger is adopted NATIVELY (`cordis::logger`) with upstream-style semantics: bounded ring, effect-owned exporter sinks, per-name level routing, printf rendering.
- Detail: `LoggerService` keeps the last 1000 `Message`s (monotonic sequence, timestamp, name, kind, numeric level, args, fiber label) and snapshots without copying payloads.
- Detail: exporters are effect-owned — `register` returns a `Disposable` whose disposal removes the sink; `ExporterConfig` gates per name and truncates rendered text (default cap 4096 chars, char-boundary safe).
- Detail: thresholds resolve per-name pin, then the `LoggerIntercept` override (read through the relaxed channel, so per-fiber overrides apply on child contexts), then the default level (`Debug`); `enabled` bails BEFORE argument assembly.
- Detail: rendering supports `%s %d %i %f %o %O %c %C %%`; unknown specifiers and exhausted arguments stay literal; `%c` picks a stable ANSI16 slot by FNV-1a hash of the logger name, `%C` adds bold; `hyphenate` / `derived_name` yield kebab-case logger names.
- Detail: the `Context` facade (`ctx.log/info/warn/debug/error/log_with`) is a no-op when no logger is provided.
- Rationale: observability belongs where fibers dispose, so sink lifetimes tie to effects instead of a satellite package boundary; the multi-package layering ceremony is deliberately not replicated.
- Source: `LoggerService`, `Exporter`, `ExporterConfig`, `LoggerIntercept`, `Message::render`, `hyphenate` in `crates/cordis/src/logger.rs`
- Tests: `buffer_bounded_at_capacity_snapshot_reads`, `level_routing_per_name_with_default_fallback`, `printf_placeholders_format_correctly`, `logger_intercept_overrides_level`, `hyphenate_and_derived_names`, `exporter_disposal_removes_sink`

### 19. Timer primitives are fiber-scoped and std-only

- Upstream expectation: timing ships as a dedicated satellite package with its own runtime assumptions.
- Claim: six primitives (`timeout`, `sleep`, `interval`, `interval_stream`, `debounce`, `throttle`) live natively in `cordis::timer`, run on ONE shared wheel thread, and attach to the owning fiber through labeled undos.
- Detail: the wheel is a min-heap on a dedicated `cordis-timer` thread; due entries drain under one short critical section and callbacks run outside the lock; panics are caught and the thread survives.
- Detail: registrations made under `with_current_fiber` push `timer:`-labeled undos, so `Fiber::dispose` (or a reactive unload) cancels them; dropping a handle does NOT cancel; out-of-scope registrations degrade to warned orphan handles that stay explicitly disposable.
- Detail: a disposed `Interval` stream yields exactly ONE final `Err(InactiveEffect)` then closes; queued live ticks are discarded so teardown is the final observation.
- Detail: `debounce` collapses a burst into one trailing delivery after the last call; `throttle` delivers leading-edge plus optional trailing in a fixed window.
- Rationale: timers must die with the fiber that owns them or they leak firings past teardown; a shared thread keeps thousands of registrations at one thread's cost with no async runtime dependency.
- Source: `timeout` / `sleep` / `interval` / `interval_stream` / `debounce` / `throttle`, `with_current_fiber`, `Scheduled`, `Interval` in `crates/cordis/src/timer.rs`
- Tests: `timeout_fires_once_and_disposes_with_fiber`, `timeout_dispose_before_deadline_prevents_fire`, `interval_ticks_repeatedly_and_stops_on_dispose`, `interval_stream_final_err_on_dispose`, `debounce_collapses_bursts`, `throttle_trailing_edge_respected`

### 20. Accessor traffic bypasses interception

- Upstream expectation: every value read or write consults the `internal/get` / `internal/set` veto waterfalls.
- Claim: name-keyed computed properties (`register_accessor`) resolve OUTSIDE both waterfalls — resolving an accessor never consults or re-enters a veto chain.
- Detail: `Accessor::{read_only, read_write, setter_only}` installs a getter/setter pair beside the TypeId service store; registration returns an `EffectHandle` whose disposal removes the declaration and every alias.
- Detail: `Context::alias` binds an alternate name through the SAME registration; duplicate declarations (including alias collisions) are rejected with `DuplicateProvider`.
- Detail: typed reads surface `CordisError::PropertyTypeMismatch` instead of a silent `None`; writes to a read-only property are refused with `CordisError::ReadOnlyProperty`.
- Rationale: computed properties are policy plumbing, not provider state — vetoing them would let an interceptor break accessor invariants it cannot see.
- Source: `Context::register_accessor`, `Accessor`, `Context::alias`, `EffectHandle` in `crates/cordis/src/context.rs`
- Tests: `accessor_read_write_roundtrip`, `duplicate_accessor_declaration_rejected`, `readonly_property_rejects_set`, `dispose_accessor_resolves_none`, `alias_resolves_same_value`, `accessor_bypasses_intercept_waterfalls`

### 21. Intercept layers are ordered, append-on-set, and inspectable

- Upstream expectation: one intercept binding per key; later registrations replace earlier ones (last-write-wins).
- Claim: intercept layers per TypeId form an ordered outermost..innermost sequence; NEW registrations APPEND, so the innermost layer stays effective for all existing getters.
- Detail: append-on-set means no existing caller observes a behavior change when another layer joins.
- Detail: `Context::intercept_chain(tid)` returns every layer outermost..innermost for inspection and restart-decision logic.
- Detail: `Context::chains_structurally_equal` compares two chains by shared-instance identity (`Arc::ptr_eq`) per layer pair; erased values carry no comparable contract, so freshly-built values compare unequal by design.
- Rationale: layered policies need composition without clobbering, and restart decisions need an honest equality test over opaque layers.
- Source: `Context::intercept_chain`, `Context::chains_structurally_equal`, layer storage in `crates/cordis/src/context.rs`
- Tests: `chained_layers_append_innermost_effective`, `intercept_chain_returns_all_layers_in_order`

### 22. Restart errors keep the old application; vetoes defer loudly

- Upstream expectation: an update-pass failure leaves the fiber in an unspecified mid-transition state.
- Claim: `Fiber::update` returns `Result<(), CordisError>` — a restart-path error propagates to the caller and the fiber stays `Active` serving its OLD configuration; an `internal/update` veto parks the deferred config in `Fiber::vetoed_config` and returns `Ok`.
- Detail: error propagation never disposes effects of the still-running application.
- Detail: the vetoed config remains inspectable through `vetoed_config()` so operators can see what was declined and why.
- Rationale: a failed restart must not destroy the working instance, and a silent skip must still be observable.
- Source: `Fiber::update`, `vetoed_config` in `crates/cordis/src/fiber.rs`
- Tests: `update_error_stays_active_old_config`, `update_veto_defers_config_and_returns_ok`

### 23. Config interception covers the activation path

- Upstream expectation: config rewriting applies only on later re-applies; first activation runs the raw config.
- Claim: the `internal/config` waterfall is consulted on the ACTIVATION path too, so rewrites apply on first activation identically to re-applies.
- Detail: the same non-null-terminal-becomes-effective-config semantics hold on both paths (row 14).
- Rationale: activation-time-only rewrites would make a policy's effect depend on whether the fiber happened to start fresh.
- Source: config-waterfall consult in the register/activation path in `crates/cordis/src/registry.rs`
- Test: `config_waterfall_covers_activation_path`

### 24. Module changes fan out through one dependency graph

- Upstream expectation: no module-level change propagation exists; each watcher event maps to at most one reload target.
- Claim: `ModuleGraph` maps module keys to dependencies; `change_many` computes the TRANSITIVE affected plugin set read-only FIRST, then reloads each affected plugin EXACTLY ONCE per transaction; a failing reload rolls back that plugin while successfully reloaded siblings stay `Active`.
- Detail: `ModuleReload` implementations perform the reloads; `ChangeOutcome` classifies the transaction result.
- Detail: the file watcher's debounced batch fans through a registered `ModuleGraph` when one is provided on the context; WITHOUT registration the watcher path is unchanged (opt-in).
- Rationale: batched filesystem events must not reload shared dependents N times or leave siblings dead because one peer failed.
- Source: `ModuleGraph`, `ModuleEntry`, `ModuleReload`, `ChangeOutcome`, `change_many` in `crates/cordis/src/module_graph.rs`; fan-out wiring in `crates/cordis/src/watcher.rs`
- Tests: `dependency_change_reloads_dependents_transitively`, `batched_changes_reload_each_plugin_once`, `rollback_keeps_successful_siblings_active`, `watcher_module_graph_fan_out_reloads_dependents`, `module_graph_without_registration_is_ignored`

### 25. Entries relocate without losing fiber identity (we exceed upstream)

- Upstream expectation: entries form a flat id-keyed list; relocation means delete-plus-recreate with a fresh fiber.
- Claim: PATCH accepts optional `parent` / `position` (`EntryPosition`) applied move-THEN-update (invalid placements answer 409 before any mutation), `POST /admin/cordis/entries/{id}/move` relocates an entry with its whole `{id}:*` subtree in one rename cascade, and a valid move preserves fiber identity via in-place refresh.
- Detail: moving into a descendant is refused; disabled groups move suppressed-then-restored.
- Detail: invalid moves touch neither the entries file nor the live tree; unknown ids answer 404.
- Rationale: hierarchical entry organization must not cost consumer-visible dispose/recreate windows.
- Source: `EntryPosition`, `EntryTree::move_entry`, `subtree_ids`, `Loader::move_entry` in `crates/cordis/src/loader.rs`; `patch` / `move_cordis_entry` handlers in `crates/ares-http/src/api/handlers/admin/cordis.rs`
- Tests: `move_preserves_fiber_identity_and_lands_update_in_new_parent`, `descendant_move_refused`, `subtree_rename_cascades_descendants`, `disabled_group_move_suppresses_start_then_restores`, `patch_endpoint_moves_entry`, `patch_endpoint_invalid_move_conflicts_without_mutating`

### 26. Subtask cancellation is wired end to end (we exceed upstream)

- Upstream expectation: the reference client defines cancellation hooks but never wires them into its delegation loop.
- Claim: delegated subtasks register sticky cancel tokens keyed by run/skill id; `SkillEngine::cancel_subtask()` flips a token exactly once and is honored at step boundaries alongside the `EmergencyStop` hook; an aborted subtask integrates nothing into the parent.
- Detail: quote-aware delegation argument parsing: double-quoted segments are single tokens (backslash escapes inside quotes); `--parallel` latches split-per-token mode with `|` separators ignored, `--model` consumes exactly one token, `--tools` enables the inner tool loop; precedence is flags > profile > global.
- Rationale: long-running delegated work needs an external off switch that lands between model rounds, not only at process exit.
- Source: `SubtaskCancelToken`, `SkillEngine::cancel_subtask`, `registered_cancel_token`, step-boundary checks in `crates/ares-agent/src/skills/engine.rs`; `parse_flags` tokenizer in `crates/ares-agent/src/skills/mod.rs`
- Tests: `cancel_token_aborts_subtask_between_rounds`, `parse_flags_quote_aware_tokens`

### 27. Deterministic micro calls are cached; repaired answers are not (we exceed upstream)

- Upstream expectation: the reference roadmap describes response caching but ships none; every identical micro call re-hits the network.
- Claim: deterministic-class micro outcomes serve from a bounded LRU map keyed by a content hash over `(model, system template, input)`; answers reached through retries or salvage fallback are NEVER cached.
- Detail: default 256 entries, 15-minute TTL, master switch via `MicroCacheConfig`.
- Detail: hits skip the network entirely, report `latency_ms: 0`, and carry the `cache_hit` telemetry flag.
- Rationale: classify/tag-style enrichment calls dominate micro traffic and their answers are stable; a repeated or repaired request proves the call was NOT deterministic-class, so caching it would pin a bad answer.
- Source: `MicroCacheConfig`, `MicroOutcome::cache_hit`, `cache_key`, `LruOutcomeCache` in `crates/ares-llm/src/micro.rs`
- Tests: `identical_inputs_serve_cached_outcome`, `retries_exhausted_falls_back_to_salvage` (salvage path stays uncached by construction)

### 28. Guided grammars ride typed hints with byte-identical absence

- Upstream expectation: constrained output requires per-provider request surgery with no portable hint channel.
- Claim: `GenerationHints::guided_grammar` carries a schema-shaped JSON value as `response_format` `json_schema` on every OpenAI-compatible path; raw GBNF/EBNF text rides the provider-specific `guided_grammar` extension field on NON-streaming OpenAI-compatible requests; providers without a channel silently ignore the hint; an ABSENT hint leaves the wire byte-identical.
- Detail: classification is structural — a JSON object with an object root is a schema; anything else is raw grammar text.
- Rationale: one opt-in hint field covers structured outputs where supported and vendor grammar extensions where they are not, without changing default requests.
- Source: `GenerationHints::guided_grammar` in `crates/ares-llm/src/client.rs`; classification and `GUIDED_GRAMMAR_EXTENSION` in `crates/ares-llm/src/openai.rs`
- Test: `grammar_hint_present_reaches_request_builder`

### 29. Duplicate embeddings cost exactly one backend call (we exceed upstream)

- Upstream expectation: the reference roadmap mentions embedding dedup but implements none; every input in a batch hits the backend.
- Claim: per-request content-hash dedup collapses duplicate inputs (whitespace-normalized SHA-256) BEFORE the backend call on BOTH local and HTTP embedding paths; computed vectors fan back to every duplicate slot.
- Detail: `DedupPlan` maps duplicates to the first occurrence's slot; callers receive full-length results.
- Rationale: identical texts in one batch are common (templates, retries) and each costs a paid embedding call.
- Source: `DedupPlan`, `content_hash_hex`, `normalize_for_dedup` in `crates/ares-rag/src/embeddings.rs`
- Tests: `dedup_plan_maps_duplicates_to_first_occurrence_slot`, `duplicate_texts_single_backend_call_vectors_fanned_back`, `content_hash_hex_ignores_whitespace_differences_only`

## Properties we prove beyond the paper

`crates/cordis/src/metatheory.rs` proves five properties as executable checks.
These hold regardless of the divergence choices above.

1. **Quiescence after every operation** (`quiescence_after_every_op`). Every fiber rests in a well-defined state between operations. Transitional states appear only mid-await, never at rest. Allowed rest states include the reversible `Pending` (row 10) and terminal `Failed{error}` (row 1); only `Active` fibers must hold all declared injects available.
2. **Registration confluence** (`order_confluence_of_registrations`). Registration order does not change the final graph.
3. **Reactive spatial invariant** (`dependent_never_active_without_provider`). A dependent never activates while its provider is absent. It activates reactively when the provider appears.
4. **LIFO dispose restores the store** (`lifo_dispose_restores_store`). Disposal unwinds effects in strict LIFO order. The store returns to its pre-registration contents.
5. **Version-conformance flips** (`version_conformance` module). A compatible upgrade flips the dependent back to `Active`. A mismatch holds it at `Inactive`.

## Maintenance note

Any pull request that changes kernel semantics MUST add a row here or update an existing row.
State the claim, the rationale, and the enforcement point in the entry.
Reviewers reject semantic kernel changes without a ledger entry.
Before merge, re-run the cited tests.