Commit Graph

1150 Commits

Author SHA1 Message Date
Oliver Kieselbach 4300a01769 refactor(v2core): M4.4.5.d mTLS HttpClient factory + certificate resolver seam
MtlsHttpClientFactory baut HttpClient/HttpClientHandler mit Client-Cert
attached fuer den BackendTelemetryUploader. Delegiert an
ICertificateResolver-Seam (DefaultCertificateResolver in Prod nutzt
CertificateHelper, Tests liefern Fake mit self-signed Cert).

Factory wirft InvalidOperationException wenn kein Cert gefunden — mTLS
ist Pflicht, Agent darf ohne Cert nicht starten. Decompression (GZip +
Deflate) + 30s Default-Timeout analog Legacy BackendApiClient.

CreateHandler separat exponiert, damit Tests direkt auf dem Handler
asserten koennen (HttpClient._handler ist internal + reflection-fragil
auf net48).

+5 Agent.V2.Core Tests (260 → 265 gruen; self-signed-Cert via
System.Security.Cryptography.CertificateRequest, kein Cert-Store-Touch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:46:48 +02:00
Oliver Kieselbach a72e716862 refactor(v2core): M4.4.5.c collector-host seam + event-bridge wiring
ICollectorHost + IComponentFactory als Seam fuer Collector-Produktion:
Factory liefert fertig-konstruierte Hosts mit der telemetryEmitter.Emit-
Callback bereits als onEnrollmentEvent-Sink gebunden. Orchestrator
ruft Start/Stop/Dispose in korrekter Reihenfolge auf.

Die 5 Collectors (HelloTracker, ShellCoreTracker,
ProvisioningStatusTracker, StallProbeCollector, ModernDeploymentTracker)
haben heavy Production-Deps (Event-Log-Watcher, WMI, Registry) — die
Factory-Seam erlaubt Prod-Impl in Program.cs / Tests nutzen Fakes.

Exception-Swallow-Wrapper im Orchestrator: ein Emitter-Fail (z.B.
Transport disposed) darf den Collector-Thread nicht killen.

ModernDeploymentTracker explizit als bridge-only dokumentiert: kein
SignalAdapter, nur Event-Bridge — validiert durch dedizierten Test,
der prueft dass Events im Transport landen aber SignalLog leer bleibt.

Factory optional (default null): Orchestrator ohne Factory laeuft
weiter ohne Collectors (backwards-compatible zu M4.4.5.b).

+7 Agent.V2.Core Tests (253 → 260 gruen).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:41:59 +02:00
Oliver Kieselbach 51f9db6429 refactor(v2core): M4.4.5.b enrollment orchestrator skeleton + lifecycle
Top-Level-Lifecycle-Owner. Start() verdrahtet die Kern-Pipeline
(SignalLog/Journal/Snapshot, TelemetrySpool+Uploader, EventSequence+
Emitter+Timeline+BackPressure, DeadlineScheduler+ClassifierRegistry,
EffectRunner, DecisionStepProcessor, SessionTraceOrdinalProvider,
SignalIngress) und verbindet Scheduler.Fired → ingress.Post-Bridge.

Zirkel EffectRunner-ISignalIngressSink ↔ SignalIngress-Processor via
LazyIngressSinkRelay gebrochen. Snapshot-Recovery-Vorstufe bei Start;
volle Checksum/Replay-Logik kommt in M4.4.5.f.

Shutdown idempotent + reverse-order: Scheduler-Unsubscribe → Scheduler
dispose → Ingress.Stop → Drain-Loop cancel → Terminal-Drain → Final-
Snapshot → Dispose. IQuarantineSink-Impl setzt Flag (Bewegung erst beim
naechsten Start, nicht mid-run).

Drain-Loop nutzt Task.Delay (Wall-Clock) statt IClock.Delay, damit
VirtualClock-Tests nicht in Tight-Loop laufen.

+14 Agent.V2.Core Tests (239 → 253 gruen). Collectors + SignalAdapters
folgen in M4.4.5.c, mTLS-HttpClient-Factory in .d, Pattern-Injection in
.e, Recovery-Pfade in .f.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:37:22 +02:00
Oliver Kieselbach ce62a6527d refactor(v2core): M4.4.5.a decision-step processor + quarantine escalation
Produktions-IDecisionStepProcessor als Applicator-Schicht zwischen
SignalIngress und Journal/Effects/Snapshot. Sequenz: Journal-Append →
EffectRunner (sync-over-async) → Snapshot (best-effort) → State-Forward.

Quarantine-Eskalation: zaehlt aufeinanderfolgende Journal-Failures; ab
Threshold (default 3) Fire-once Call an IQuarantineSink. SignalIngress
schluckt das Throw weiter (Worker-Thread bleibt am Leben), aber der
Orchestrator (M4.4.5.b/f) kennt den Alarm fuer die Quarantine-Bewegung
beim naechsten Start.

+11 Agent.V2.Core Tests (228 → 239 gruen).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:28:09 +02:00
Oliver Kieselbach 8294f7a8a2 refactor(v2): M4.4.4 classifier-tick from session start + hello-outcome forward + pattern-match hook
Three related fixes Plan §4.x M4.4.4 calls out:

1. ClassifierTick armed at SessionStarted (DecisionCore.Shared)
   Previously the WhiteGlove classifier deadline was armed reactively on the
   first WG-relevant signal (AttachWhiteGloveClassifierEffects, WhiteGlove.cs).
   Non-WG sessions and late-WG sessions never got a periodic re-evaluation,
   which is the M3 re-trigger-lücke. Fix: HandleSessionStartedV1 now schedules
   the ClassifierTick deadline up-front and emits the matching
   ScheduleDeadline effect. The existing reactive `hasTick` dedup makes the
   WG-path arm a no-op once the tick is already present. Classifier-runner
   anti-loop (snapshot-hash) keeps the tick cheap for non-WG paths until the
   session reaches a terminal stage and HandleClassifierTickDeadlineFired
   stops re-arming. Dead-end branch (SessionStarted on active state) is
   unchanged — no deadline, no effect, state preserved.

2. HelloOutcome now forwards from coordinator (EspAndHelloTrackerAdapter)
   ReadHelloOutcome() previously returned hardcoded "unknown", so
   HelloResolved signals always carried that value regardless of what the
   inner HelloTracker actually observed. The coordinator already exposes a
   forwarded HelloOutcome property (line 78, since M2) — only the adapter
   was missing the wiring. One-line fix: `_coordinator.HelloOutcome ?? "unknown"`.

3. ImeLogTracker.OnPatternMatched hook + WhiteGloveSealingPatternDetected
   emission (ImeLogTracker + ImeLogTrackerAdapter, closing the M4.3.4 tech debt)
   Added `Action<string> OnPatternMatched` to ImeLogTracker, invoked by
   HandlePatternMatch after LastMatchedPatternId is set (subscribers can read
   the ID synchronously). Adapter gains an optional ctor arg
   `IReadOnlyCollection<string> whiteGloveSealingPatternIds` (default empty,
   backwards-compatible with pre-M4.4.4 M3 behavior — no emission). When
   configured and a matching pattern fires, adapter posts
   WhiteGloveSealingPatternDetected once per session (fire-once). Polling-on-
   LastMatchedPatternId (alternative A from plan) was rejected; the hook is
   a real event surface. Orchestrator in M4.4.5 will inject the tenant-
   configured sealing-pattern-ID list.

Bonus: reduced EventSequenceCounterTests thread-safety stress from 6×200 to
4×50 to avoid parallel xUnit test contention on the Windows atomic-rename
path (Next() persists to disk per call). Test still catches any counter race
via the uniqueness assertion.

SmokeTests.DecisionEngine_Reduce_SessionStarted_advancesStep updated to assert
the new ClassifierTick effect/deadline instead of `Assert.Empty(step.Effects)`.

+18 tests (1350 -> 1368 green, 57 DecisionCore + 218 V2.Core before vs. 73 + 228
after): ClassifierTickFromSessionStartTests (5) · EspAndHelloTrackerAdapter-
HelloOutcomeTests (3) · ImeLogTrackerAdapterSealingPatternTests (7) · smoke +
harness updates. Bestehende Scenario-Tests grün (Classic/WG1/WG2/
SelfDeploying/Edge) — ClassifierTick-at-start ist additiv, kein Scoring-Impact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 19:23:56 +02:00
Oliver Kieselbach fb98d43ae5 refactor(decisioncore): M4.4.3 reducer handlers for diagnostic signals
DeviceInfoCollected and AutopilotProfileRead previously fell through to
HandleUnhandledSignal, writing unhandled_signal_kind:* DeadEnd rows into the
journal on every emission — pure noise, no decision value, but distorting the
Inspector timeline. Plan §4.x M4.4.3 fix.

Both are diagnostic signals: they carry hardware inventory / Autopilot profile
payload used only by the Inspector and backend analyzers. They do NOT drive
stage or hypotheses. Fix is minimal: neutral Taken transitions via a new
shared helper RecordDiagnosticObservation — bookkeeping advances,
stage/hypotheses untouched, no effects, no DeadEndReason. Full payload stays
in the SignalLog for Inspector rendering (Plan §2.7c — Snapshot/Journal are
Cache, SignalLog is Master; no reason to duplicate data into DecisionState).

Version-gap protection: dispatcher entries are keyed on (Kind, v1). A future
v2 payload still dead-ends with unhandled_signal_kind:*:v2 — the L.6
forward-compat contract requires explicit handler upgrades, not silent
recording under the old handler.

AppInstallCompleted/AppInstallFailed intentionally stay unwired — no decision
consumer in v11 scope, and the collector isn't emitting them yet.

+11 tests (1339 -> 1350 green): Taken-without-DeadEnd, stage+hypotheses
unchanged, bookkeeping advancement, no effects, existing deadlines preserved,
v2-schema-version falls through to unhandled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 19:03:08 +02:00
Oliver Kieselbach bca53bd43d refactor(v2core): M4.4.2 backend telemetry uploader HTTP skeleton
Concrete IBackendTelemetryUploader implementation for the Plan §2.7a
POST /api/telemetry/batch endpoint. Skeleton only — the live backend
endpoint lands in M5; this milestone only needs the client-side contract
testable against HttpMessageHandler fakes.

Design:
- New class BackendTelemetryUploader in Transport/Telemetry/ (NOT added as a
  method on the M2-copied Legacy BackendApiClient, which is slated for
  removal in M4.5 / v11.1+). Lives next to IBackendTelemetryUploader;
  clean layer, no legacy cross-dependency.
- HttpClient injected (not self-constructed). mTLS client-cert wiring stays
  with the orchestrator-builder in M4.4.5 via
  HttpClientHandler.ClientCertificates; the uploader itself is cert-agnostic,
  which makes it trivially testable with a custom handler.
- Ctor deps are flat: baseUrl, tenantId, optional hardware/bootstrap/version
  headers. No AgentConfiguration coupling — orchestrator extracts values.
- No retry/back-off here; TelemetryUploadOrchestrator (M4.1) owns that.
- Body: JSON array of TelemetryItem via Newtonsoft (PascalCase; Kind as
  string via existing StringEnumConverter annotation). No gzip — evaluate
  in M5 once bandwidth profile is known.
- HTTP status -> UploadResult mapping:
    2xx                 -> Ok
    401 / 403           -> Permanent ("unauthorized: ...")
    408 / 429           -> Transient (rate limit / request timeout)
    other 4xx           -> Permanent (client bug; retry pointless)
    5xx                 -> Transient
    HttpRequestException-> Transient ("network: ...")
    TaskCanceledException (HttpClient internal timeout, not caller CTS)
                        -> Transient ("timeout: ...")
    OperationCanceledException with caller-CTS signaled -> rethrow
  Empty batch short-circuits with Ok (no HTTP call) to avoid wasted
  round-trips when a concurrent drain emptied the spool mid-upload.
- Security headers: X-Tenant-Id (pre-body auth check), hardware headers
  (X-Device-Manufacturer/Model/SerialNumber for whitelist + Autopilot
  validation), optional X-Bootstrap-Token (pre-MDM OOBE), X-Agent-Version
  for kill-switch + diagnostics.

New Constants.ApiEndpoints.TelemetryBatch = "/api/telemetry/batch" — shared
with M5 backend-side routing.

+26 tests (1313 -> 1339 green): URL + header shape, JSON body wire-format
(Kind as string, TelemetryItemId/PK/RK/PayloadJson round-trip), empty-batch
short-circuit, full HTTP status-code -> UploadResult matrix (2xx/401/403/
4xx/408/429/5xx), HttpRequestException + timeout mapping, caller
cancellation propagation, null-arg rejection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:54:07 +02:00
Oliver Kieselbach 5e28142642 refactor(v2core): M4.4.1 event timeline emitter + sequence counter + back-pressure event bridge
Infrastructure for Kind=Event telemetry in V2.Core. Three consumers share one sink:

- IEventTimelineEmitter impl (EventTimelineEmitter) — expands reducer
  EmitEventTimelineEntry effect parameters {eventType, reason, ...} into a fully-
  populated EnrollmentEvent (Severity auto-derived from eventType suffix:
  _failed->Error, _aborted->Warning, else Info; Source=decision_engine;
  Phase=Unknown per feedback_phase_strategy; ImmediateUpload=true for terminal
  events). Timestamp comes from the signal's OccurredAtUtc, not wall clock,
  keeping replay deterministic.
- IBackPressureObserver impl (BackPressureEventObserver) — surfaces signal-
  ingress back-pressure as agent_trace "ingress_backpressure" events with
  Severity=Warning, carrying origin + channelCapacity + queueLength +
  blockDurationMs in Data. Routes via the shared emitter, NOT through the
  ingress channel (would deadlock, per §2.1a).
- (future M4.4.5 wiring) collector onEventCollected callbacks bind
  Action<EnrollmentEvent> directly to TelemetryEventEmitter.Emit.

Central pieces:
- EventSequenceCounter (thread-safe Interlocked-locked counter) with
  EventSequencePersistence — atomic temp-rename writes to
  {SessionDir}/Telemetry/event-sequence.json, flush-to-disk per §2.7 L.12.
  Starts at 1, seeds from file on recovery; persists every Next().
- TelemetryEventEmitter — assigns Sequence + RowKey
  ({Timestamp:yyyyMMddHHmmssfff}_{Sequence:D10}), serializes via Newtonsoft
  (PascalCase wire-format, byte-identical to Legacy; backend uses
  PropertyNameCaseInsensitive ingest), enqueues TelemetryItemDraft Kind=Event
  to ITelemetryTransport. PartitionKey = {tenantId}_{sessionId}.

Minor API change: IEventTimelineEmitter.Emit gains DateTime occurredAtUtc
(previously only parameters + state). EffectRunner already has
stepOccurredAtUtc in scope — forwarded as one-line change. FakeEventTimelineEmitter
test fake updated accordingly; no existing tests reference the old signature.

+33 tests (1280 -> 1313 green): sequence monotonicity + recovery-seed +
thread-safety, atomic persistence roundtrips + corrupt-file fallback, wire-
format field shape (EventType/SessionId/TenantId/Sequence/EventId/RowKey),
severity auto-derivation, reason-into-Message + Data propagation, Phase stays
Unknown for terminal events, RowKey format, immediate-flush forwarding,
back-pressure event shape + clock-driven timestamp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:40:59 +02:00
Oliver Kieselbach b8b5f22a61 refactor(v2core): M4.4.0 signal ingress (blocking-collection worker + back-pressure obs)
Single-writer SignalIngress per Plan §2.1a: dedicated Thread drains a bounded
BlockingCollection<IngressItem> (cap 256), assigns SessionSignalOrdinal +
SessionTraceOrdinal, appends to SignalLog before invoking the reducer, then
hands the DecisionStep to IDecisionStepProcessor (Orchestrator impl in M4.4.5).

Back-pressure observed via TryAdd(0) in Post(); blocking fallback measures
duration and notifies IBackPressureObserver, throttled 1x/min/origin. The
observer emission path deliberately bypasses the signal channel (would
deadlock). Real Events-Bridge wiring lands in M4.4.1 once EventSequenceCounter
exists.

New abstractions (all in V2.Core.Orchestration, §2.11-conformant):
- ISessionTraceOrdinalProvider (thread-safe Interlocked counter, seedable for recovery)
- IDecisionStepProcessor (post-Reduce sink; Orchestrator M4.4.5 implements)
- IBackPressureObserver (M4.4.1 wires to real Events-Bridge)

SignalIngress implements the existing ISignalIngressSink from M4.2 directly so
EffectRunner can re-ingest ClassifierVerdictIssued synthetic signals without a
separate adapter.

+24 tests (1256 -> 1280 green): lifecycle, ordinal monotonicity + recovery,
SignalLog-before-Reduce ordering, state progression across reduces, ApplyStep
exception resilience, ISignalIngressSink path, back-pressure (fast/slow,
throttle same/different origin, clock-rearm, null observer, observer
exceptions), concurrent producer ordinal monotonicity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:22:40 +02:00
Oliver Kieselbach 4ad23d4b0a refactor(v2core): M4.3.5 signal adapters (esp-and-hello coordinator + stall-probe)
Plan §2.1a / §2.2 — Abschluss M4.3 mit dem Production-Path-Coordinator plus
StallProbe. Damit ist die SignalAdapter-Schicht für alle Decision-relevanten
Collectors komplett.

New:
- EspAndHelloTrackerAdapter (Coordinator — Production-Wiring Target in M4.4)
  - HelloCompleted → HelloResolved / HelloResolvedPart2
  - FinalizingSetupPhaseTriggered → EspPhaseChanged (phase=FinalizingSetup)
  - WhiteGloveCompleted → WhiteGloveShellCoreSuccess
  - EspFailureDetected → EspTerminalFailure (merged aus ShellCore + Provisioning)
  - DeviceSetupProvisioningComplete → DeviceSetupProvisioningComplete
  - Independent per-kind fire-once dedup für alle 5 Signal-Kinds
- StallProbeCollectorAdapter
  - EspFailureDetected → EspTerminalFailure (SourceOrigin=StallProbeCollector
    unterscheidet von ShellCore/Provisioning/Coordinator — wichtig für
    Attribution via 4 Scan-Source-Stall-Classifier)

Design-Note:
- Coordinator vs. Sub-Tracker-Adapter: Sub-Tracker sind in EspAndHelloTracker
  private; Production nutzt NUR den Coordinator-Adapter. Sub-Tracker-Adapter
  aus M4.3.1-3 bleiben für Single-Tracker-Wiring-Szenarien und Tests.
- Mapping-Logik-Duplikation zwischen Coordinator + Sub-Tracker-Adaptern ist
  M4.5-Cleanup-Kandidat (SignalAdapterEmitters-Helper oder gemeinsame
  Mapping-Interfaces). ~50 LoC Duplikation aktuell akzeptiert.

Tests (+12, 135 total V2.Core.Tests):
- EspAndHelloTracker: Hello/Finalizing/WhiteGlove/EspFailure/DeviceSetupComplete
  je 1 Test + Part2-Mode + all-5-in-one-session dedup + null-args
- StallProbe: Happy-Path, empty-reason-fallback, fire-once, null-args

Testsuite: 1244 → 1256.

M4.3 komplett — 8 SignalAdapter + 1 StallProbe für alle Decision-relevanten
Collectors. ModernDeploymentTracker (nur Events, keine Signals) wird in M4.4
Orchestrator-Wiring direkt an TelemetryTransport gebridged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 17:02:14 +02:00
Oliver Kieselbach e97eb08ec5 refactor(v2core): M4.3.4 signal adapter (ime log tracker)
Plan §2.1a / §2.2 — schwerster Adapter: ImeLogTracker mit Action-Property-
Callbacks (nicht Events); 3+ Signal-Mappings + Phase-Value-Dedup + per-App-
Terminal-State-Dedup.

New:
- ImeLogTrackerAdapter
  - OnEspPhaseChanged(phase) → EspPhaseChanged (dedup per distinct phase value,
    §2.1a Idempotenz-Anforderung; null/empty → skip)
  - OnUserSessionCompleted() → ImeUserSessionCompleted (fire-once)
  - OnAppStateChanged(app, old, new) → AppInstallCompleted (Installed/Skipped/
    Postponed) oder AppInstallFailed (Error); non-terminal states ignoriert;
    per-AppId Dedup
- Chain-preserve: vorherige Action-Handler bleiben callable (Orchestrator kann
  diagnostische Callbacks parallel wiren); Dispose restauriert wenn wir noch
  der current handler sind
- Delegate-instance caching (_our* Felder) weil implizite method-group-conversion
  jedes Mal neue Delegate-Instanz baut → ReferenceEquals in Dispose sonst fail

NICHT Teil M4.3.4 (Tech Debt noted):
- WhiteGloveSealingPatternDetected Signal-Emission — ImeLogTracker hat keinen
  öffentlichen Pattern-Match-Hook; LastMatchedPatternId ist Property. Re-Mapping
  braucht entweder Polling-Adapter oder Tracker-Action-Surface-Erweiterung.
  Reducer-M3-Szenarien laufen ohne das Signal grün; Add-on in M4.4/M4.5.

Tests (+16, 123 total V2.Core.Tests):
- EspPhase first-emit / repeat-dedup / distinct-values / null-skip
- UserSessionCompleted fire-once
- AppState Theory (4 terminal states → 2 kinds)
- Non-terminal skipped; per-app fire-once
- Different apps independent; null-app ignored
- Prior-Action chain-preserve
- Dispose restores prior handler
- Null-args throw

Testsuite: 1228 → 1244.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:56:40 +02:00
Oliver Kieselbach 3c1fca8089 refactor(v2core): M4.3.3 signal adapter (provisioning-status tracker)
Plan §2.1a / §2.2 — ProvisioningStatusTracker mit 2 Events auf 2 DecisionSignalKinds.

New:
- ProvisioningStatusTrackerAdapter
  - DeviceSetupProvisioningComplete → DeviceSetupProvisioningComplete
    (liest GetProvisioningCategorySnapshot() — deviceSetupResolved in Payload)
  - EspFailureDetected → EspTerminalFailure (SourceOrigin unterscheidet von
    ShellCoreTracker — wichtig für Multi-Source-Attribution im Journal)

Dual-Source-EspTerminalFailure-Handling (ShellCore + ProvisioningStatus):
Duplicate Signals sind erwartbar und OK — der Reducer akzeptiert die erste,
weitere landen als Dead-End-Transitions im Journal. Adapter-Dedup ist
tracker-lokal (pro Instanz).

Tests (+5, 107 total V2.Core.Tests):
- DeviceSetupComplete → DeviceSetupProvisioningComplete Signal
- EspFailure → EspTerminalFailure mit distinct SourceOrigin
- Empty failureType → "unknown"
- Per-kind dedup
- Null-args throw

Testsuite: 1223 → 1228.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:50:21 +02:00
Oliver Kieselbach 03b27e1377 refactor(v2core): M4.3.2 signal adapter (shell-core tracker)
Plan §2.1a / §2.2 — ShellCoreTracker verbindet 3 Events mit 3 DecisionSignalKinds.

New:
- ShellCoreTrackerAdapter
  - FinalizingSetupPhaseTriggered → EspPhaseChanged (phase=FinalizingSetup, reason propagated)
  - WhiteGloveCompleted → WhiteGloveShellCoreSuccess
  - EspFailureDetected → EspTerminalFailure (failureType in payload, "unknown" fallback)
- Per-kind Dedup-Flags (independent fire-once pro Signal-Kind)

NOTE: ModernDeploymentTracker ist NICHT Signal-relevant (emittiert EnrollmentEvents
via onEventCollected callback → Events-Tabelle-Pipeline). Wird in M4.4 Orchestrator
direkt an TelemetryTransport als Kind=Event gebridged, nicht als SignalAdapter.
Event 509 (WhiteGlove Start) war in Legacy Scoring-Factor (+15); per M3-Erkenntnis
wurde das Weight in V2 entfernt → keine Signal-Emission nötig.

Tests (+7, 102 total V2.Core.Tests):
- Finalizing → EspPhaseChanged mit korrekter EnrollmentPhase.FinalizingSetup
- WhiteGloveCompleted → WhiteGloveShellCoreSuccess
- EspFailure → EspTerminalFailure mit failureType
- Empty failureType → "unknown" fallback
- Per-kind independent dedup (3 Events → 3 Posts, Duplicates gefiltert)
- All-3-in-one-session flow
- Null-args throw

Testsuite: 1216 → 1223.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:48:29 +02:00
Oliver Kieselbach 5a858cb4c5 refactor(v2core): M4.3.1 signal adapters (desktop-arrival, aad-join, hello)
Plan §2.1a / §2.2 — 3 erste SignalAdapter pro Collector. Sub-Split analog M3.x.

New in V2.Core.SignalAdapters:
- DesktopArrivalDetectorAdapter → DesktopArrived / DesktopArrivedPart2
- AadJoinWatcherAdapter → AadUserJoinedLate / UserAadSignInComplete
  (nur AadUserJoined-Event gemapped; PlaceholderUserDetected ist diagnostic,
   nicht Decision-relevant — dokumentiert)
- HelloTrackerAdapter → HelloResolved / HelloResolvedPart2 (payload trägt
  HelloOutcome: completed|skipped|timeout|not_configured|wizard_not_started)

Adapter-Pattern:
- internal sealed class : IDisposable
- Ctor: Collector + ISignalIngressSink + IClock + optional part2Mode
- Subscribe auf Collector-Event; dedup-Flag (fire-once)
- TriggerFromTest() internal hook für Tests (umgeht Event-Plumbing)
- Dispose() unsubscribed
- Keine Modifikation der Collector-Files (§2.11 transitional-SKIP bleibt)

PII-Guard in AAD-Adapter: nur userDomain im Payload, nicht full email.

New tests (+21, 95 total V2.Core.Tests):
- Trigger → Post mit korrektem Kind/Evidence/Payload
- part2Mode → Part-2-Signal-Kinds
- Duplicate-Trigger → Dedup
- Null-Args → ArgumentNullException
- HelloOutcome-Varianten (Theory, 5 values)
- AAD PII-Guard (full email NICHT in payload)

Pattern-Erkenntnisse:
- Collectors sind meist internal — Adapter sind auch internal
  (InternalsVisibleTo Agent.V2 + V2.Core.Tests deckt Usage)
- Evidence.Derived braucht mind. einen DerivationInput — Adapter setzen
  sinnvolle Detection-Source-Keys
- Part-1/Part-2 via bool-Ctor-Param (einfacher als 2 Klassen)

Testsuite: 1195 → 1216. V2-isolation clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:40:00 +02:00
Oliver Kieselbach 4b3fd46f69 refactor(v2core): M4.2 effect runner (deadline/classifier/emit/snapshot wiring)
Plan §2.5 / §2.7b / L.17 — einziger Side-Channel vom pure Reducer zur Außenwelt.

New in DecisionCore:
- ClassifierVerdictLookup.LookupLastVerdictId(state, classifierId) — mapping
  classifier-id → hypothesis-slot. Shared between ClassifierAwareReplayHarness
  (refactored) and the production EffectRunner (DRY, L.15).

New in V2.Core.Orchestration:
- IClassifierRegistry + ClassifierRegistry (id→IClassifier, dup-detect)
- ISignalIngressSink — abstraction for posting synthetic signals (real impl M4.4)
- IEventTimelineEmitter — abstraction for EmitEventTimelineEntry (real impl M4.4)
- EffectFailure + EffectRunResult — orchestrator-consumable dispatch result
- IEffectRunner + EffectRunner
  - ScheduleDeadline/CancelDeadline → critical class: any exception sets
    SessionMustAbort=true with reason=timer_infrastructure_failure, stops
    processing subsequent effects
  - RunClassifier → optional class: anti-loop via ClassifierVerdictLookup +
    IClassifierSnapshot.ComputeInputHash(); exception → Inconclusive verdict
    posted via ingress, no abort
  - EmitEventTimelineEntry / PersistSnapshot → transient class: retry
    100/400/1600ms via IClock.Delay, exhaust → failure recorded but no abort
    (§2.7b explicit „State unverändert, weiter im Flow")

New tests (+18, 74 total in V2.Core.Tests):
- Schedule/Cancel forwarding + abort on scheduler exception + stop-subsequent
- Emit retry-then-success + retry exhaust (no abort, failure recorded) +
  failure does not block subsequent effects
- Persist retry-then-success
- Classifier happy (verdict signal posted correctly) + anti-loop skip +
  exception → Inconclusive verdict + unknown id → failure + missing snapshot
- Batch-dispatch-order test, empty-batch test

Testsuite: 1177 → 1195. V2-isolation clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:15:19 +02:00
Oliver Kieselbach 3f94254673 refactor(v2core): M4.1 generic telemetry transport (spool + orchestrator)
Plan §2.7a / L.10 — ein Spool für Events + Signals + DecisionTransitions,
TelemetryItemId als typ-unabhängiger Upload-Cursor.

New in V2.Core.Transport.Telemetry:
- TelemetryItem + TelemetryItemKind + TelemetryItemDraft (immutable DTOs,
  StringEnum-converted Kind für forward-compat)
- TelemetryItemSerializer (Newtonsoft-Ctor-Bind, JSONL)
- TelemetrySpool (disk-backed JSONL queue, monotonic TelemetryItemId +
  SessionTraceOrdinal=TelemetryItemId for session items, WriteThrough+Flush
  per Enqueue, tail-corruption tolerance, restart recovery of counters)
- UploadCursorPersistence (atomar temp+rename, parse-fail → cursor=-1)
- IBackendTelemetryUploader + UploadResult (Ok/Transient/Permanent) —
  abstraction keeps orchestrator HTTP-agnostic; real BackendApiClient impl
  comes with M4.4 wiring
- TelemetryUploadOrchestrator (Drain loop: batched, retry 100/400/1600ms
  backoff via IClock, permanent-fail skips retry, idempotent resume via
  spool cursor, exception→transient)
- DrainResult + ITelemetryTransport interface

New tests (+28, 56 total in V2.Core.Tests):
- Spool: monotonic ids, session-scoped trace-ordinal, sofort-flush, Peek
  respects cursor + max, MarkUploaded regression no-op, restart recovery,
  corrupt-tail-scan tolerance
- Orchestrator: happy drain, empty-spool noop, batching, 3-retry happy,
  exhaust→fail, permanent→no retry, exception→retry, resume after transient,
  cursor persists across restart, double-drain idempotent, cancel, dispose

Testsuite: 1149 → 1177. V2-isolation clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 15:47:14 +02:00
Oliver Kieselbach d76da2ad75 refactor(v2core): M4.0 persistence writers + deadline scheduler skeleton
- TransitionSerializer + StateSerializer in DecisionCore.Serialization
  (Newtonsoft-ctor-binding, shared between agent writers + backend verifier)
- V2.Core.Persistence: SignalLogWriter, JournalWriter (JSONL append-only,
  WriteThrough+Flush(true) per append — plan L.12 sofort-flush);
  SnapshotPersistence (SHA256-envelope, tempfile+rename, .quarantine/)
- V2.Core.Orchestration: IDeadlineScheduler + DeadlineScheduler
  (IClock-basiert; past-due fires on ThreadPool; same-name replace;
  RehydrateFromSnapshot; plan §2.6 / L.7 Timer-Monopol)
- V2.Core.Tests (new net48 xUnit project): 28 tests across persistence
  (roundtrip, monotonicity, tail-corruption tolerance, checksum-mismatch,
  quarantine) + scheduler (past-due, future, cancel, replace, rehydrate,
  dispose)
- V2-isolation preserved (no reference to Agent.Core)

Testsuite: 1121 → 1149 (+28). Foundation for M4.1–M4.5 (Transport, EffectRunner, SignalAdapters, Orchestrator-Wiring, transitional-Cleanup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 15:25:36 +02:00
Oliver Kieselbach 75c98d131f refactor(decisioncore): edge scenarios — hybrid reboot, ESP failure, recovery (M3.5)
Final M3 sub-milestone. Closes the plan's §4 M3 scenario matrix:

Handlers (DecisionEngine.Edge.cs):
 - EspResumed              mid-ESP reboot. Stage preserved; records
                           SystemRebootUtc on first observation (hash
                           stable on repeats).
 - EspTerminalFailure      Stage=Failed + Outcome=EnrollmentFailed,
                           deadlines cleared, enrollment_failed event
                           emitted with reason carried from payload.
                           Definitive — no classifier re-run.
 - SystemRebootObserved    records SystemRebootUtc so the WhiteGlove
                           sealing classifier credits the +15 reboot
                           weight on the next RunClassifier effect.

Scenario fixtures (committed-anonymized):
 - hybrid-reboot-v1.jsonl        ESP -> mid-phase reboot -> EspResumed ->
                                 AccountSetup -> Hello -> Desktop ->
                                 Completed. No WhiteGloveSealed path.
 - esp-terminal-failure-v1.jsonl ESP -> AccountSetup -> terminal failure
                                 -> Failed / reason=
                                 esp_terminal_failure_account_setup.

Tests (EdgeScenarioTests, 5 new):
 - Hybrid reboot preserves stage, records reboot fact, completes
   normally without triggering WhiteGlove.
 - ESP terminal failure transitions straight to Failed.
 - Restart-recovery determinism: replaying the same JSONL twice yields
   identical FinalStepHash, Stage, Outcome and transition count.
 - Prefix-replay check: truncated log ends in the expected intermediate
   stage (simulates crash between signals).
 - Global sanity: every committed fixture (all 12) ends in a terminal
   stage — regression guard for any future reducer change.

Suite totals: 57 DecisionCore (was 52) / 507 Legacy / 557 Functions =
1121 tests green. All 14+ scenarios from plan §R6 Szenario-Matrix pass
through the reducer with the expected terminal outcome.

This closes M3 (DecisionEngine-Reducer + Classifier-Kernel). M4 wires the
SignalAdapter + EffectRunner + TelemetryTransport in the V2 agent runtime.

Plan: plans/REFACTOR_AGENT_V2.md §4 M3 sub-milestone M3.5 — M3 complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 14:26:13 +02:00
Oliver Kieselbach 8e4faf4251 refactor(decisioncore): WhiteGlove Part 2 + Part2CompletionClassifier + 2 scenarios (M3.4)
Plan §4 M3.4 + plan §2.3 WhiteGlove Part-2 state machine: post-reboot user
sign-in is now a first-class path with a dedicated classifier, a 24h safety
deadline, and an explicit Sealed -> AwaitingUserSignIn -> CompletedPart2 /
Failed progression.

Classifier (WhiteGlovePart2CompletionClassifier : IClassifier):
 - Scoring rules NEW (no Legacy equivalent): each of the four Part-2 user
   signals (+25) — UserAadSignInComplete, HelloResolvedPart2,
   DesktopArrivedPart2, AccountSetupCompletedPart2. All four together hit
   the Confirmed threshold (100); missing one drops to Weak (75).
 - WhiteGlovePart2CompletionSnapshot implements the new IClassifierSnapshot
   interface; existing WhiteGloveSealingSnapshot was retrofit to the same
   contract so the harness anti-loop is uniform.

Handlers (DecisionEngine.WhiteGlovePart2.cs):
 - SessionRecovered (Shared.cs) now bridges a WhiteGloveSealed state into
   WhiteGloveAwaitingUserSignIn and arms the 24h WhiteGlovePart2Safety
   deadline on the recovered signal's occurredAt.
 - Four Part-2 signal handlers (UserAadSignInComplete, HelloResolvedPart2,
   DesktopArrivedPart2, AccountSetupCompletedPart2) record facts + emit a
   Part-2 RunClassifier effect; facts arriving outside the AwaitingUserSignIn
   stage are recorded but no classifier runs (prevents stray Part-2 verdicts
   on non-sealed sessions).
 - WhiteGlovePart2Safety deadline handler terminates with Failed /
   enrollment_failed reason=part2_user_absent.
 - ClassifierVerdictIssued router extended to dispatch on classifier id —
   Part-2 verdicts route to ApplyWhiteGlovePart2Verdict; Confirmed triggers
   Stage=WhiteGloveCompletedPart2 + Outcome=WhiteGlovePart2Complete +
   whiteglove_part2_complete event.

DecisionState: four new SignalFact<DateTime>? fields for the Part-2 signals
(full builder parity).

ClassifierAwareReplayHarness: anti-loop lookup now routes per classifier id
(WhiteGloveSealing <-> WhiteGloveSealing.LastClassifierVerdictId;
Part2Completion <-> WhiteGlovePart2Completion.LastClassifierVerdictId).
Snapshot hashing works through the IClassifierSnapshot interface instead
of a WhiteGloveSealingSnapshot type check.

Scenario fixtures (committed-anonymized):
 - whiteglove-part2-happy-v1.jsonl  seal Part 1, reboot, 4 Part-2 signals
                                    within minutes -> Confirmed + Part2Complete.
 - whiteglove-part2-stuck-v1.jsonl  seal Part 1, reboot, 24h passes, safety
                                    deadline fires -> Failed / part2_user_absent.

Tests (3 new in WhiteGlovePart2ScenarioTests):
 - Happy terminal state + hypotheses + all four facts + no residual deadlines
 - Stuck terminal state + Part-1 Confirmed remains, Part-2 Rejected with reason
 - Bridge check: after SessionRecovered(Sealed), Part-2 safety is active

Suite totals: 52 DecisionCore (was 49) / 507 Legacy / 557 Functions.

Plan: plans/REFACTOR_AGENT_V2.md §4 M3 sub-milestone M3.4 + §2.3 Part-2 stages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 14:21:53 +02:00
Oliver Kieselbach f75a8d4375 refactor(decisioncore): WhiteGlove Part 1 handlers + sealing classifier + 4 scenarios (M3.3)
Plan §4 M3.3: the first kernel-service classifier goes live (plan §2.4 L.8),
the effect-based classifier-run contract is wired end-to-end, and the anti-loop
via InputHash is proven by a dedicated fixture.

Classifier (WhiteGloveSealingClassifier : IClassifier):
 - Scoring ported 1:1 from Legacy WhiteGloveClassifier (same weights, same
   thresholds, same asymmetric-conservative rule: only Confirmed drives the
   WhiteGloveSealed stage).
 - Positive weights: ShellCoreWhiteGloveSuccess=80, SealingPattern=40,
   DeviceOnlyDeployment=15, SystemReboot=15.
 - Hard excluders (-100 each): AadJoinedWithUser, DesktopArrived, HelloResolved.
   Soft exclude: AccountSetupActivity=-40.
 - WhiteGloveSealingSnapshot is the classifier input type and computes a
   deterministic SHA256-short InputHash over WG-relevant fields only — so
   the anti-loop triggers on meaningful fact changes, not on unrelated state.

Handlers (DecisionEngine.WhiteGlove.cs):
 - WhiteGloveShellCoreSuccess       records fact; emits RunClassifier effect;
                                    arms ClassifierTick if not already.
 - WhiteGloveSealingPatternDetected mirror handler for the IME-pattern path.
 - ClassifierVerdictIssued          applies the verdict to WhiteGloveSealing
                                    hypothesis; on Confirmed transitions to
                                    WhiteGloveSealed stage +
                                    Outcome=WhiteGlovePart1Sealed and emits
                                    the whiteglove_complete event.
 - HandleClassifierTickDeadlineFired (shared router)  re-emits RunClassifier
                                    and re-arms itself unless terminal.

ClassifierAwareReplayHarness (tests-only):
 - Loops per-step effects: any RunClassifier effect is resolved synchronously
   via the registered IClassifier; the verdict is wrapped in a
   ClassifierVerdictIssued signal and fed back into Reduce. Anti-loop skips
   the call when snapshot hash == state.WhiteGloveSealing.LastClassifierVerdictId.
 - Exposes ClassifierRunStats for regression tests.

DecisionState extension: two new SignalFact<bool> fields capture the WG
sightings (ShellCoreWhiteGloveSuccessSeen, WhiteGloveSealingPatternSeen). The
classifier reads them via WhiteGloveSealingSnapshot.

Scenario fixtures (4, committed-anonymized):
 - whiteglove-inline-v1.jsonl            Confirmed on ShellCore alone (80).
 - whiteglove-signal-correlated-v1.jsonl Weak after pattern, ClassifierTick
                                         anti-loop-skipped, Confirmed after
                                         ShellCore escalates the score.
 - whiteglove-false-positive-v1.jsonl    SealingPattern + late-AADJ hard-
                                         excludes -> Rejected; session still
                                         completes via Classic Hello+Desktop.
 - whiteglove-antiloop-v1.jsonl          Single pattern + two ticks; runs=1,
                                         skipped_by_antiloop=2.

Tests: 4 scenario tests in WhiteGlovePart1ScenarioTests, all passing.
DecisionCore.Tests: 49 (was 45). Legacy 507 + Functions 557 unchanged.

ReducerFailSafeTests + HarnessTests updated to use AppInstallCompleted as
the still-unhandled sentinel (WhiteGloveShellCoreSuccess is now wired).

Plan: plans/REFACTOR_AGENT_V2.md §4 M3 sub-milestone M3.3 + §2.4 classifier kernel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 14:15:06 +02:00
Oliver Kieselbach 0ebfa2fc3c refactor(decisioncore): SelfDeploying + Device-Only handlers + 2 scenarios (M3.2)
SelfDeploying-v1 vertical slice: DeviceSetupProvisioningComplete terminal
handler, DeviceOnlyEspDetection deadline wiring, and the
DeviceOnlyDeployment hypothesis progression UserPresent / DeviceOnly /
Ambiguous expressed via Hypothesis.Reason (plan §2.3).

Handlers (DecisionEngine.SelfDeploying.cs):
 - DeviceSetupProvisioningComplete  terminal. Cancels all deadlines,
                                    finalizes DeviceOnlyDeployment based on
                                    user-presence facts (AadJoinedWithUser,
                                    HelloResolvedUtc, DesktopArrivedUtc),
                                    raises EnrollmentType to Strong when
                                    no AccountSetup was observed before.
 - DeviceOnlyEspDetectionFired      hypothesis-only update to
                                    DeviceOnlyDeployment=Strong(DeviceOnly).
                                    Stage unchanged; completion still
                                    requires explicit provisioning signal.

Classic.cs extended:
 - First EspPhaseChanged(DeviceSetup) arms the DeviceOnlyEspDetection
   deadline (5 min window, plan §2.7).
 - EspPhaseChanged(AccountSetup) cancels it — UserDriven-v1 path.

Shared DeadlineFired router routes the deadline to the SelfDeploying body.

Scenario fixtures (tests/fixtures/enrollment-sessions/):
 - selfdeploying-happy-v1.jsonl            — 3 signals, SkipUserStatusPage
                                             path. Terminal Completed with
                                             DeviceOnly hypothesis confirmed
                                             and no user-presence facts.
 - selfdeploying-esp-exit-unknown-v1.jsonl — 4 signals, deadline fires at
                                             T+5m and sets DeviceOnly before
                                             provisioning-complete arrives.

Tests (SelfDeployingScenarioTests, 4 new):
 - SelfDeployingHappy terminal state + hypotheses
 - SelfDeployingHappy deadline cleanup
 - Device-Only ESP-exit-unknown deadline -> provisioning flow
 - AccountSetup on the Classic path cancels DeviceOnlyEspDetection
   (regression guard — UserDriven should never classify as DeviceOnly)

InternalsVisibleTo added to DecisionCore for DecisionCore.Tests so the
internal DeviceOnlyReasons tokens stay hidden from consumers while tests
assert on them directly.

Suite totals: 45 DecisionCore (was 41) / 507 Legacy / 557 Functions.

Plan: plans/REFACTOR_AGENT_V2.md §4 M3 sub-milestone M3.2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 14:04:52 +02:00
Oliver Kieselbach 0210d42c7f refactor(decisioncore): Classic UserDriven-v1 handlers + 3 scenarios (M3.1)
First vertical slice of real reducer behavior: six handlers for the Classic
UserDriven-v1 enrollment flow plus the shared DeadlineFired router, with three
committed-anonymized scenario fixtures driving the proof.

Handlers (DecisionEngine.Classic.cs):
 - EspPhaseChanged       stage progression DeviceSetup -> AccountSetup;
                         populates CurrentEnrollmentPhase + DeviceSetupEnteredUtc /
                         AccountSetupEnteredUtc / FinalizingEnteredUtc facts;
                         raises EnrollmentType hypothesis to Weak on AccountSetup.
 - EspExiting            records EspFinalExitUtc; schedules HelloSafety (300 s);
                         transitions to AwaitingHello and emits ScheduleDeadline.
 - HelloResolved         records HelloResolvedUtc + HelloOutcome; cancels
                         HelloSafety; completes session if Desktop already arrived.
 - DesktopArrived        mirror of HelloResolved; completion when Hello already
                         resolved.
 - ImeUserSessionCompleted  raises EnrollmentType hypothesis to Strong; records
                            matched pattern id.
 - AadUserJoinedLate     HYPOTHESIS-ONLY (project memory
                         feedback_aad_joined_late_not_completion). Records
                         AadJoinedWithUser fact + annotates EnrollmentType reason.
                         Stage unchanged, no terminal event. Regression guard test
                         verifies From/To stage equality.

Shared router (DecisionEngine.Shared.cs):
 - DeadlineFired dispatches on Payload[deadline]. HelloSafety timeout synthesizes
   HelloOutcome=Timeout and drives the same completion path as HelloResolved.
   Unknown deadline names cancel the deadline and record a neutral transition —
   forward-compat with M3.3 (ClassifierTick) / M3.4 (WhiteGlovePart2Safety).

Constants:
 - DeadlineNames enumerates the 7 pflicht-deadlines from plan §2.6.
 - SignalPayloadKeys standardizes payload keys used by collectors and the
   deadline scheduler.

Scenario harness + fixtures (tests/fixtures/enrollment-sessions/):
 - ScenarioTestBase locates repo root and wraps FixtureLoader + ReplayHarness.
 - userdriven-happy-v1.jsonl            — 7 signals, terminal Completed /
                                          EnrollmentComplete / HelloOutcome=Success.
 - userdriven-hello-timeout-v1.jsonl    — Desktop arrives before Hello, deadline
                                          fires at T+5m -> HelloOutcome=Timeout,
                                          Completed.
 - late-aadj-v1.jsonl                   — Late-AADJ mid-flight does not complete
                                          prematurely; session still completes
                                          normally via Hello + Desktop.

Tests: 4 scenario tests in ClassicScenarioTests + determinism check.
Total suite: 41 DecisionCore.Tests (was 37 at M3.0) / 507 Legacy unchanged /
557 Functions unchanged.

Plan: plans/REFACTOR_AGENT_V2.md §4 M3 sub-milestone M3.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 13:59:30 +02:00
Oliver Kieselbach b6a949bc02 refactor(decisioncore): engine foundation — dispatch, fail-safe, clock, serialization (M3.0)
M3.0 establishes the reducer infrastructure the scenario sub-milestones
(M3.1–M3.5) will build on. No domain-specific reducer behavior yet; only
kernel plumbing + a live SessionStarted / SessionRecovered / SessionAborted
lifecycle path.

Kernel (plan §2.5 / L.2 / L.3 / L.16):
 - DecisionEngine dispatches on (Kind, KindSchemaVersion) to partial-class
   handlers. Unhandled kinds produce a DeadEnd transition with a versioned
   reason string — never a throw, never a mutation.
 - Exception fail-safe (L.16): any handler exception is caught in the
   kernel, StepIndex + LastAppliedSignalOrdinal still advance (so the
   signal counts as processed and there is no retry loop), the transition
   records DeadEndReason="reducer_exception" + SHA256-short StackTraceHash.
   DecisionState is returned untouched beyond bookkeeping.
 - Shared helpers for Taken / DeadEnd transition construction keep
   partial-class handlers short and consistent.

State (plan §2.3 / L.3):
 - Immutable DecisionState gets a mutable DecisionStateBuilder companion —
   reducer handlers use state.ToBuilder().WithStage(...).Build() to
   express "copy with changes" without retyping 20+ ctor arguments.
 - Fluent helpers AddDeadline (replace-if-same-name), CancelDeadline,
   WithCurrentEnrollmentPhase carry the most common reducer operations.

Time (plan §2.6 / L.7):
 - IClock interface + SystemClock (wall-clock wrapper). The only way any
   decision-relevant code can observe time going forward. VirtualClock
   lives in the test assembly — never in shipping builds.

Serialization (plan §2.8 / §2.15 / L.14):
 - Newtonsoft.Json added to DecisionCore (both net48 + net8.0 consume the
   same DTOs with the same converters).
 - UnknownFallbackEnumConverter<T> maps unrecognized enum strings to a
   caller-provided fallback instead of throwing — lets an older backend
   read a newer DecisionCore's rows without crashing; the verifier will
   flag them as drift_tolerated in §2.10.
 - DecisionCoreJsonSettings registers fallback converters for every
   DecisionCore enum (DecisionSignalKind, EvidenceKind, HypothesisLevel,
   SessionStage, SessionOutcome, DecisionEffectKind, EnrollmentPhase).
 - SignalSerializer round-trips DecisionSignal JSONL with enforced
   Evidence.Identifier and Evidence.Kind — a line without Evidence throws
   just like constructor validation.

Lifecycle (DecisionEngine.Shared.cs):
 - SessionStarted: anchors the first journal transition; rejects mid-flight
   replay (StepIndex != 0) with a DeadEnd instead of silently resetting
   hypotheses.
 - SessionRecovered: neutral bookkeeping in M3.0 — WhiteGlove Part 1 → 2
   transition lands in M3.4 DecisionEngine.WhiteGlovePart2.cs.
 - SessionAborted: terminal, Outcome=Aborted, all deadlines cleared —
   orchestrator admin-action path (plan §2.7 audit).

Test coverage (37 total):
 - VirtualClockTests (6)     — monotonic clock, Delay instant-return
 - DecisionStateBuilderTests (5) — immutable semantics, deadline replace/cancel
 - SerializationTests (6)     — round-trip, UnknownFallback both string + numeric
 - ReducerFailSafeTests (6)   — null-arg, replay-on-active, unhandled kind,
                                SessionAborted clears deadlines, ReducerVersion
 - SmokeTests (7)             — smoke, updated: Reduce(SessionStarted) is a
                                real transition, not a throw
 - HarnessTests (6)           — empty-stream determinism, single-signal replay,
                                unknown-kind DeadEnd at harness level
 - VirtualClock helper (instance) covered via Harness integration

Suite totals unchanged elsewhere: 507 Legacy + 557 Functions. Full solution
builds 0 warnings, 0 errors.

Plan: plans/REFACTOR_AGENT_V2.md §4 M3 (sub-milestone M3.0 Foundation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 13:51:45 +02:00
Oliver Kieselbach 3f64b2809b chore: CLAUDE.md codex-review note, .gitignore additions, legacy buildcounter
Three small maintenance items accumulated during the M1/M2 session:

- .claude/CLAUDE.md: add "Codex will review your output once you are
  done!" note so the Codex review step is front-of-mind for agent work.
- .gitignore:
    * scripts/Deployment/build_and_upload_debug_agent_build_dev.ps1 —
      fill the gap next to the other two Legacy deployment scripts
      (already ignored).
    * plans/REFACTOR_AGENT_V2.md — the refactor plan stays local-only
      during active planning.
- src/Agent/AutopilotMonitor.Agent/buildcounter.txt: auto-incremented by
  MSBuild during the full-solution builds run for the M1/M2 gates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 13:36:48 +02:00
Oliver Kieselbach 710504549b refactor(agent): V2-Agent-Split skeleton + infrastructure copy (M2)
Three new projects as a clean-slate V2 fork of the Legacy Agent:

  AutopilotMonitor.Agent.V2         — net48 exe, minimal Program.cs,
                                      buildcounter 2.0.N, version-v2.json
  AutopilotMonitor.Agent.V2.Core    — net48 library, 94 files copied from
                                      Agent.Core via bulk sed namespace
                                      rewrite; same folder layout
  AutopilotMonitor.DecisionCore.Tests — xUnit net8.0 with ReplayHarness
                                        + 13 smoke/harness tests

V2 isolation is build-time-enforced via V2Isolation.targets imported from
both V2 csproj files — any Legacy ProjectReference fails the build with a
clear error. Grep-verified at commit time: zero `using
AutopilotMonitor.Agent.Core` statements and zero Legacy ProjectReferences
in the V2 tree.

Per plan §2.11, kernel items (CompletionStateMachine, CompletionGuards,
EnrollmentTracker*, Flows/*, WhiteGloveClassifier, WhiteGloveSignals,
EnrollmentStatePersistence) are transitionally retained so the V2 tree
compiles; M4 removes them when SignalAdapters + V2Orchestrator take over.

Per user direction (feedback_v2_kernel_only_refactor): the V2 refactor
replaces only the decision kernel. All peripheral capabilities — gather
rules, analyzers, diagnostics upload, timezone, server-driven actions,
summary dialog launch, env-var resolution, version checks, registry
watchers, network/DO/performance telemetry, self-update, bootstrap→cert
handover, self-destruct — are preserved verbatim via the bulk copy.

Supporting M3 bring-up platform:
 - ReplayHarness (empty-stream deterministic, SHA256 hash over terminal
   state fields) in DecisionCore.Tests/Harness/
 - Fixture scaffold under tests/fixtures/: committed anonymized directory
   with commit-policy README, plus gitignored enrollment-sessions-local/
   for raw exports
 - tools/export-session-fixture.mjs with --help / --dry-run / --validate
   modes; real MCP wiring marked TODO(v2-m3)

V2 bootstrap scripts scripts/Bootstrap/Install-AutopilotMonitor-v2(.|-dev).ps1
(committed, pure ASCII verified per feedback_ps1_ascii_only). V2 deployment
scripts under scripts/Deployment/V2/ are gitignored (analog to Legacy).

Test totals at M2 gate: 13 DecisionCore + 507 Agent.Core (unchanged) +
557 Functions (unchanged) = 1077 green.

Plan: plans/REFACTOR_AGENT_V2.md §4 M2 + §2.16 V2-Agent-Split.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 13:31:30 +02:00
Oliver Kieselbach 830a27bb70 refactor(decisioncore): add DecisionCore shared library scaffolding (M1)
New netstandard2.0 project src/Shared/AutopilotMonitor.DecisionCore/ with
14 scaffolding types across Signals/, State/, Classifiers/, Engine/.
Agent.Core and Functions reference the library without call-sites; the
full solution builds 0 errors, 0 warnings. DecisionEngine.Reduce is an
intentional NotImplementedException stub — M3 fills in the partial-class
reducer.

Invariants wired into the scaffolding:
 - Immutable DTOs (sealed + get-only + With-methods) per L.3
 - Pflicht-Evidence validation in Evidence ctor per L.5
 - KindSchemaVersion enforcement per L.6
 - AssemblyVersion 2.0.0.0 as ReducerVersion source per §2.10

Architecture principles L.1–L.18 extracted to
.claude/architecture-principles.md as persistent Claude-session context.

Plan: plans/REFACTOR_AGENT_V2.md §4 M1. Zero runtime risk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 13:29:19 +02:00
Oliver Kieselbach 6f758ea0f1 late-aadj fix 2026-04-17 14:47:16 +02:00
Oliver Kieselbach 4851a7dfa4 updated buidlcounter.txt 2026-04-17 13:34:28 +02:00
Oliver Kieselbach 3008ba69f6 chore 2026-04-17 13:34:25 +02:00
Oliver Kieselbach 6719464f34 fix WhiteGloveClassifier and Completion logic, fix Phase Timeline 2026-04-17 12:59:38 +02:00
Oliver Kieselbach 6efae2c709 feat(agent): WhiteGlove Detection Classifier and late-AADJ support 2026-04-17 12:35:25 +02:00
Oliver Kieselbach 64240249ac dashboard performance and more coding safety checks in integrity analyser 2026-04-17 11:16:41 +02:00
Oliver Kieselbach ef1d57f32b fix wording 2026-04-17 00:36:27 +02:00
Oliver Kieselbach 9d2d6c3ae7 run integrity analyzer only on startup 2026-04-17 00:29:19 +02:00
Oliver Kieselbach 2eb6d87b4d feat(agent): added IntegrityBypassAnalyzer 2026-04-17 00:20:24 +02:00
Oliver Kieselbach 2faba55b46 Known Issues also in Navbar 2026-04-16 23:37:10 +02:00
Oliver Kieselbach 8d661bbd70 whiteglove event 509 wording 2026-04-16 23:16:54 +02:00
Oliver Kieselbach 4d4a95e58b updated Changelog 2026-04-16 23:04:27 +02:00
Oliver Kieselbach fd73f5abfb prevent doubel completion_decision_process events, fix phase timeline 2026-04-16 22:31:35 +02:00
Oliver Kieselbach bda52df517 update known issues 2026-04-16 21:55:34 +02:00
Oliver Kieselbach f046a5c8d8 update known issues 2026-04-16 20:27:12 +02:00
Oliver Kieselbach d38bd14721 update known issues 2026-04-16 20:20:39 +02:00
Oliver Kieselbach 4b3db3a1cd whiteglove completion logic and orphan events handling 2026-04-16 20:06:28 +02:00
Oliver Kieselbach 7b8a242d44 fix HybridJoin detection 2026-04-16 14:37:22 +02:00
Oliver Kieselbach bb320e0ec8 added additional foouser detection (soft WhiteGlove Indicator) 2026-04-16 14:30:35 +02:00
Oliver Kieselbach aba8df6572 agent StateMachine fixes 2026-04-16 14:15:40 +02:00
Oliver Kieselbach 6e991b5ca8 update known-issues with agent work and interruptions based on this 2026-04-16 13:40:04 +02:00
Oliver Kieselbach 3cb3df2b16 completion logix fix 2026-04-16 12:52:38 +02:00
Oliver Kieselbach 21937fa950 whiteglove session timeline fix 2026-04-16 11:54:31 +02:00
Oliver Kieselbach 0fe7748684 agent metrics session limit bump to 500 2026-04-16 11:31:56 +02:00