mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-25 13:10:13 +00:00
7e199019cedfe4059ebf41f36efc7a1763ca4827
1162 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7e199019ce |
feat(agent-v2): M4.6.γ diagnostic probes, reporters, DO collector wired
Closes the third batch of peripheral-parity gaps — Legacy's fire-and-forget
startup probes, the two cert-less + authenticated error-reporting channels,
and Delivery-Optimization telemetry all run in V2.
StartupEnvironmentProbes (V2.Core/Runtime/)
- New helper that replicates the Legacy MonitoringService startup diagnostic
trio: device_location / timezone_auto_set / ntp_time_check.
- Sequential RunAsync(config, logger, emitter): geo lookup → (optional)
tzutil auto-set → NTP offset check. Each step is best-effort and feeds
through the orchestrator's TelemetryEventEmitter.
- Geo probe respects EnableGeoLocation; timezone probe respects
EnableTimezoneAutoSet AND requires an IANA tz from geo. NTP probe always
runs (diagnostic-only; Warning on offset >60s or failure).
- Program.cs wires this via Task.Run right after orchestrator.Start so a
slow network never delays the critical path.
EmergencyReporter + DistressReporter (live in Program.cs)
- Previously instantiated nowhere — now built once at startup with the
shared hardware info. RemoteConfigService ctor takes both so config-fetch
failures flow to the right channel (auth → distress, network → emergency).
- Initial AuthCertificateMissing distress fires when UseClientCertAuth=true
but BackendApiClient.ClientCertificate is null — surfaces pre-MDM-
enrollment dead-ends via the cert-less channel (Legacy parity).
- BackendTelemetryUploader uses the same hardware info (dedup'd the second
HardwareInfo.GetHardwareInfo call that was duplicated since M4.5.b).
DeliveryOptimizationHost (DefaultComponentFactory nested)
- New ICollectorHost wrapping DeliveryOptimizationCollector with:
• getPackageStates → ImeLogHost.PackageStates (existing accessor)
• onDoTelemetryReceived → ImeLogHost.Tracker.OnDoTelemetryReceived pass-
through (new internal accessor; keeps encapsulation)
• OnAppStateChanged chained into ImeLogTracker so WakeUp() fires when
any app enters Downloading or Installing — before any fire we preserve
the existing handler (the adapter's signal-ingest path must keep
running unchanged).
- Gated by CollectorConfiguration.EnableDeliveryOptimizationCollector.
Tests (+8 in V2.Core.Tests → 353 → 361)
- StartupEnvironmentProbesTests: event-shape for all three probes + geo
success/failure/null attempt, timezone success/failure severity, NTP
small/large offset + failure-with-detail.
All 4 suites green: 88 DecisionCore + 361 V2.Core + 507 Agent.Core + 557 Functions
= 1513 tests green.
Note: the "RegistryWatcher standalone admin-action-detector" from the
original M4.6 gap list was a Subagent fabrication — verified no such usage
exists in either Legacy or V2 (the class is only consumed transitively
inside AadJoinWatcher + ProvisioningStatusTracker, both already wired via
DefaultComponentFactory since M4.5.b). Removed from the M4.6.γ scope.
Remaining: M4.6.δ (--run-gather-rules + --run-ime-matching modes,
AnalyzerManager + 3 Analyzers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
17063be600 |
feat(agent-v2): M4.6.β terminal dispatch — FinalStatus, SummaryDialog, cleanup, server actions
Closes the second batch of peripheral-parity gaps so the V2 agent actually
terminates like Legacy does: write FinalStatus.json, launch the WPF summary
dialog in the user session, upload diagnostics (respecting mode/outcome),
write the enrollment-complete marker, run self-destruct, and signal shutdown.
All of this is driven off a single typed kernel event so nothing leaks back
into the DecisionEngine.
Terminal-stage detection (kernel hook)
- DecisionCore/State/SessionStageExtensions.IsTerminal() + IsPauseBeforePart2()
— Completed, Failed, WhiteGloveSealed, WhiteGloveCompletedPart2 are terminal;
WhiteGloveSealed is the "pause before Part 2" special case (session resumes
on reboot, cleanup MUST be suppressed).
- DecisionStepProcessor ctor gains Action<DecisionState>? onTerminalStageReached;
fires at most once per processor lifetime, swallows handler exceptions,
suppressed when initial state was already terminal (crash-recovery race).
- EnrollmentOrchestrator.OnDecisionTerminalStage translates the hook into the
public Terminated event with Reason=DecisionTerminalStage + Outcome mapped
from stage (Completed/Part2/Sealed→Succeeded, Failed→Failed, else TimedOut).
MaxLifetime watchdog is cancelled on the real terminal so the two paths can't
race each other.
Peripheral termination (V2.Core/Termination/*)
- FinalStatus DTO — wire-format mirror of SummaryDialog's Models/FinalStatus
(same JSON property names). Lives in V2.Core because the dialog is a
separate net48 WPF exe we can't ProjectReference.
- FinalStatusBuilder — pure function: DecisionState + AppPackageStateList +
TerminatedArgs + agent start time → FinalStatus. Maps outcome string
("succeeded" | "failed" | "timed_out" | "whiteglove_part1" | "whiteglove_part2"),
derives signalsSeen from DecisionState facts, counts apps by AppTargeted
phase (Device/User), tallies device vs user errors.
- SummaryDialogLauncher — writes final-status.json, copies dialog exe + deps
+ status file to a per-launch temp dir (%TEMP%\AutopilotMonitor-Summary\<guid>),
composes the Legacy-compatible CLI (--status-file, --timeout, --cleanup,
optional --branding-url), invokes UserSessionProcessLauncher.LaunchInUserSession
with the configured retry window. Fire-and-forget, swallows all errors.
- EnrollmentTerminationHandler — the Terminated-event handler that sequences:
build FinalStatus → write JSON + launch dialog → upload diagnostics
(respects DiagnosticsUploadMode: Off/Always/OnFailure) → write
enrollment-complete.marker (suppressed on WhiteGloveSealed) → run
CleanupService.ExecuteSelfDestruct (suppressed on WhiteGloveSealed OR when
SelfDestructOnComplete=false) → signal shutdown MRE. Idempotent; each step
is best-effort and logs rather than throws — nothing is allowed to prevent
agent shutdown.
DefaultComponentFactory
- Stores the ImeLogHost it creates and exposes its PackageStates to the
termination pipeline (factory.ImePackageStates) so FinalStatusBuilder has
the live package list without breaking host encapsulation.
Program.cs wiring
- Instantiates DiagnosticsPackageService, EnrollmentTerminationHandler, and
ServerActionDispatcher with all callbacks live:
• rotate_config → RemoteConfigService.FetchConfigAsync()
• request_diagnostics → DiagnosticsPackageService.CreateAndUploadAsync()
• terminate_session → EnrollmentTerminationHandler.Handle() synthesising a
DecisionTerminalStage/Failed termination
- Terminated event routed to the handler; the handler signals the shutdown
MRE so the main thread cleanly calls orchestrator.Stop().
- ServerActionDispatcher is alive but has no ingest-response consumer yet —
V2 batch endpoint is M5 scope, we plumb the API contract here so M5 can
drop in the response parser.
Tests (+47 total)
- DecisionCore.Tests: +15 SessionStageExtensionsTests (IsTerminal + IsPauseBeforePart2)
- V2.Core.Tests: +32 across terminal-stage hook, FinalStatusBuilder, handler
• DecisionStepProcessorTerminalStageTests: fires once per stage, skips
non-terminal, respects initial-terminal-state (recovery), swallows
handler exceptions, works with null callback
• FinalStatusBuilderTests: outcome mapping per stage, uptime, HelloOutcome
fact path, signalsSeen composition, empty package list, device/user
error tallying, JSON property-name contract
• EnrollmentTerminationHandlerTests: writes FinalStatus.json + marker,
runs/skips cleanup per config + WhiteGlove rule, idempotent shutdown
signal, diagnostics mode matrix (Off/Always/OnFailure × Succeeded/Failed),
suffix ("success" | "failure") per outcome
All 4 suites green: 88 DecisionCore + 353 V2.Core + 507 Agent.Core + 557 Functions
= 1505 tests green.
Remaining M4.6 scope: γ (Geo/NTP/Timezone/Emergency/Distress services wiring,
RegistryWatcher admin-action detector, DeliveryOptimizationCollector in
DefaultComponentFactory) + δ (--run-gather-rules + --run-ime-matching modes,
AnalyzerManager + 3 analyzers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
02bbb5490c |
feat(agent-v2): M4.6.α peripheral parity — lifecycle, install, self-update, watchdog
Closes the first batch of peripheral-parity gaps between Legacy and V2 so the
V2 agent is actually deployable/runnable in production. M4 kernel is unchanged
— only wiring and startup helpers.
Deployment + install
- Program.InstallMode.cs: --install mode ported 1:1 (payload copy, schtasks
Create/Run, bootstrap-config.json + await-enrollment.json persistence,
HKLM\SOFTWARE\AutopilotMonitor\Deployed marker)
- Configuration/BootstrapConfigFile.cs + AwaitEnrollmentConfigFile.cs
- Scheduled Task target is AutopilotMonitor.Agent.V2.exe (same task name
as Legacy — V2 replaces Legacy on the device)
Startup guards (Program.Guards.cs)
- Multi-instance guard (IsAnotherAgentInstanceRunning)
- clean-exit.marker writer registered in AppDomain.ProcessExit
- DetectPreviousExit: reads marker + crash_*.log + session files + event log
→ classifies {first_run, clean, exception_crash, hard_kill, reboot_kill}
- CheckEnrollmentCompleteMarker: ghost-restart detection via HKLM Deployed
key + enrollment-complete.marker → cleanup-retry + exit
- CheckSessionAgeEmergencyBreak: AbsoluteMaxSessionHours watchdog across
restarts (respects whiteglove.complete pause)
- WriteCrashLog: last-resort crash_YYYYMMDD_HHMMSS.log
- TryRead/Delete bootstrap-config.json + await-enrollment.json helpers
Self-update (SelfUpdater wired for the first time in V2 startup)
- CleanupPreviousUpdate + CheckAndApplyUpdateAsync called BEFORE logger init
- Cached remote-config.json read → BackendExpectedSha256 + AllowAgentDowngrade
- VersionCheckEventBuilder wired: emits agent_version_check event via
orchestrator.EventEmitter right after Start (when startup markers exist)
Agent max-lifetime watchdog (EnrollmentOrchestrator)
- New ctor param agentMaxLifetime: TimeSpan?
- New typed API: event EventHandler<EnrollmentTerminatedEventArgs> Terminated
with EnrollmentTerminationReason (MaxLifetimeExceeded | DecisionTerminalStage)
+ EnrollmentTerminationOutcome (Succeeded | Failed | TimedOut)
- M4.6.α fires MaxLifetimeExceeded only; DecisionTerminalStage is the M4.6.β
hook for CleanupService self-destruct + SummaryDialog launch
- Program.cs wires the event to the shutdown ManualResetEventSlim
Bootstrap-config cleanup (H-2 mitigation)
- BootstrapConfigCleanup.TryDeleteIfCertReadyAsync now actually called after
RemoteConfigService.FetchConfigAsync (cert probe confirms mTLS works)
SessionIdPersistence extensions
- New session.created companion file (UTC roundtrip) for the emergency break
- whiteglove.complete resume marker awareness
- SessionExists / LoadSessionCreatedAt / SaveSessionCreatedAt / IsWhiteGloveResume
- Delete now clears session.id + session.created + whiteglove.complete
Tests (+26 in V2.Core.Tests → 295 → 321)
- SessionIdPersistenceTests: +8 tests covering the new methods
- EnrollmentOrchestratorMaxLifetimeTests: watchdog fires once, respects null,
rejects non-positive, is cancelled by Stop (under SerialThreading collection)
- ProgramGuardsTests: 13 tests covering DetectPreviousExit state machine,
CheckSessionAgeEmergencyBreak (pass/trip/whiteglove-resume/recover-missing-
session.created paths), TryRead/Delete bootstrap + await-enrollment configs,
WriteCrashLog format
- V2.Core.Tests gains ProjectReference to V2 exe + V2 exe exposes
InternalsVisibleTo so the Program.Guards internal statics are testable
without duplicating code into V2.Core
All 4 suites green: 73 DecisionCore + 321 V2.Core + 507 Agent.Core + 557 Functions
= 1458 tests green.
Still open for M4.6.β: CleanupService terminal-event wiring, ServerActionDispatcher,
DiagnosticsPackageService, SummaryDialog launch + FinalStatusBuilder,
UserSessionProcessLauncher / UserProfileResolver wiring.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
9f45b5b7a6 |
test(v2core): M4.5.c stabilize SignalIngress + DeadlineScheduler threading flakes
Four tests were flaky under full-suite parallelism even though they passed
in isolation (SpinWait/ManualResetEventSlim timeouts expire when the
ThreadPool is contended by unrelated test classes):
- SignalIngressTests.Clock_advance_past_throttle_window_re_arms_emission
- SignalIngressTests.Null_observer_is_supported_and_back_pressure_is_silent
- DeadlineSchedulerTests.Reschedule_same_name_replaces_old_timer
- DeadlineSchedulerTests.RehydrateFromSnapshot_reschedules_each_deadline_past_due
Fix: [Collection("SerialThreading")] on both classes (xUnit runs them
sequentially against each other) + wait-helper/DefaultWaitMs bumped
2000ms→5000ms for threadpool-contention cushion.
Verified: 5× full-suite runs green (287/287 pre-M4.5.b, 295/295 post).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
0ee68c6192 |
feat(agent-v2): M4.5.b runtime entry + DefaultComponentFactory
Wires the V2 agent into a runnable state: - TenantIdResolver: HKLM\SOFTWARE\Microsoft\Enrollments → AADTenantID (EnrollmentType=6) - SessionIdPersistence: atomic session.id via File.Replace - IComponentFactory signature: +ISignalIngressSink +IClock (bridges the sink/clock access into factory-built adapters) - DefaultComponentFactory: 5 kernel hosts (EspAndHelloTracker coordinator, DesktopArrivalDetector, AadJoinWatcher, ImeLogTracker+ImeProcessWatcher, StallProbeCollector) + 2 peripheral hosts (Performance, AgentSelfMetrics) conditional on CollectorConfiguration toggles - Program.cs: full runtime entry (args → Logger → TenantId/SessionId → AwaitEnrollment → BackendApiClient+RemoteConfigService → MtlsHttpClientFactory → BackendTelemetryUploader → DefaultComponentFactory → EnrollmentOrchestrator → Ctrl+C wait → Stop → exit 0) Production host choice: EspAndHelloTracker as single coordinator entry (instead of 4 sub-tracker hosts in parallel) — avoids adapter signal duplication documented in M4.3 tech-debt. StallProbeHost drives its own 60s idle-tick timer (Legacy MonitoringService.IdleCheckCallback is gone). Tests: +8 SessionIdPersistenceTests (atomic-write, round-trip, corrupt- recovery, regeneration, multi-instance resume). V2.Core 287→295, total 1424→1432 green. Known peripheral gaps (tracked for follow-up M4.6): install mode, SelfUpdater, ServerActionDispatcher, CleanupService, Geo/NTP/Timezone wiring, Emergency/DistressReporter wiring, AnalyzerManager, SummaryDialog, DiagnosticsPackageService, gather-rules/ime-matching modes, watchdog timers, multi-instance guard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1e77305857 |
docs: lesson — generalize no-assumption-without-evidence to all decisions
Meta-Regel top-of-Rules: grep/path-name/plan-doc/agent-report sind Triage, nicht Evidenz. Vor Delete/Refactor/Classify/Behauptung muss die relevante Stelle gelesen worden sein. Im Zweifel ganze Datei lesen. Link auf feedback_no_assumption_without_evidence.md, das jetzt die erweiterte Meta-Version enthaelt (nicht mehr nur session-classification- spezifisch). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2ea959b93b |
docs: lesson — peripheral capability classification by function, not folder
Nach M4.5.a-Incident: ImeTrackerStatePersistence wurde als "enrollment-legacy" geloescht weil Pfad "Monitoring/Enrollment/Ime/" enthaelt. Tatsaechlich persistierte die Klasse collector-internal IME-Tracker-State (File-Positions, SeenAppIds, AppPackageStates mit DO-Telemetry + Install-Progress) — peripheral capability per feedback_v2_kernel_only_refactor. Rule: vor jedem Delete in V2.Core den File oeffnen + nach FUNKTION klassifizieren (DecisionEngine vs Collector-Concern), nicht nach Pfad-Location. Plan-Doc + Explore-Agent-Output sind Startpunkte, nicht Autoritaet. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
925fc707cc |
fix(v2core): M4.5.a restore ImeTrackerStatePersistence (peripheral capability, not dead code)
M4.5.a hat ImeTrackerStatePersistence faelschlicherweise als Legacy
geloescht. Korrektur: die Klasse persistiert den IME-Collector-internal
State, nicht DecisionEngine-State:
- LogFilePositionTracker: Byte-Offset pro IME-Logfile (ohne das
re-parst der Tracker nach Restart von Byte 0 → Event-Spam)
- SeenAppIds: Phase-Isolation (Device-Phase-Apps duerfen nicht in
AccountSetup-Phase bleeden)
- IgnoreList: App-IDs aus abgeschlossenen Phasen, die weiterhin in
Logs auftauchen koennen
- Packages (AppPackageStateList): vollstaendiger Install-Progress,
DO-Telemetry, ErrorPatterns, AppType, AttemptNumber — essentiell
fuer App-Download-Monitor, Install-Monitor, App-Summaries
- Phase-Tracking (CurrentPhaseOrder, LastEspPhaseDetected, etc.)
SignalLog-Replay deckt das NICHT ab — der SignalLog persistiert
DecisionSignals (SessionStarted, EspPhaseChanged, HelloResolved, …),
nicht per-App-Install-Progress oder Log-File-Positions.
Restore:
- ImeTrackerStatePersistence.cs wieder aus git history
- ImeLogTracker.cs: _statePersistence-Feld + Ctor-Init +
LoadState/SaveState/DeleteState-Bodies aus 8ca845c^ restored
(nur die beiden dead using-Imports auf Flows/Completion bleiben
entfernt, da deren Namespaces tatsaechlich geloescht sind)
Build + Tests gruen (286/287, known pre-existing Flake bleibt fuer
M4.5.c).
Danke an User fuer den Catch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
8ca845c9cb |
refactor(v2core): M4.5.a legacy dead-code cleanup (~9360 LoC removed)
Scope: alle Enrollment/Transport/Runtime-Glue-Files aus dem Legacy-M2-
Import, die durch DecisionEngine+Reducer (M3) und Transport/Telemetry
(M4.1) abgeloest sind und keine aktiven V2-Konsumenten mehr haben
(Program.cs ist noch M2-Skeleton, null Test-Refs auf Legacy).
Delete:
- Monitoring/Runtime/{MonitoringService, SessionPersistence,
CollectorCoordinator, PeriodicCollectorManager, AnalyzerManager}
- Monitoring/Enrollment/{EnrollmentTracker.*cs (4 partials),
EnrollmentStatePersistence}
- Monitoring/Enrollment/Flows/ (4 files, Ordner weg)
- Monitoring/Enrollment/Completion/ (6 files, Ordner weg)
- Monitoring/Enrollment/Ime/{ImeTrackerStatePersistence, LogReplayService}
- Monitoring/Transport/{EventSpool, EventUploadOrchestrator}
Fix peripherals (Import-Pruning inline):
- ImeLogTracker: ImeTrackerStatePersistence-Referenzen entfernt, Load/
Save/DeleteState sind no-op stubs. V2 resumed ueber SignalLog-Replay
+ SignalAdapter fire-once dedup.
- AgentSelfMetricsCollector: EventSpool-Param + spool_queue_depth-
Metric entfernt. V2-Equivalent via TelemetrySpool kommt in M4.5.b.
- 10 Files: dead using-Imports auf Enrollment.Flows / .Completion
entfernt (sed-Batch).
Keep (aktive V2-Konsumenten):
- BackendApiClient, EmergencyReporter, DistressReporter
→ RemoteConfigService.FetchConfigAsync() bleibt auf GetAgentConfig
bis M5 oder V2-Slim-Client
- NetworkMetrics (Utility), ImeLogTracker + Ime/-Support (aktiver
V2-Collector)
- Peripheral capabilities (per feedback_v2_kernel_only_refactor):
DiagnosticsPackageService, CleanupService, PerformanceCollector,
DeliveryOptimizationCollector, SelfUpdater, ServerActionDispatcher,
RegistryWatcher, UserProfileResolver, UserSessionProcessLauncher,
VersionCheckEventBuilder, NetworkChangeDetector, Analyzers
Inventory-Greps alle 0 (nur noch README.md + Kommentar-Refs):
EnrollmentTracker class, CompletionStateMachine, IEnrollmentFlowHandler,
EventSpool, EventUploadOrchestrator, MonitoringService (Code),
CollectorCoordinator, PeriodicCollectorManager, AnalyzerManager.
V2-Isolation-Grep (using AutopilotMonitor.Agent.Core): 0.
Build gruen, Tests: 286/287 (pre-existing Flake
SignalIngressTests.Null_observer_is_supported_and_back_pressure_is_silent,
M4.5.c-Scope).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
aca66a79c0 |
refactor(v2core): M4.4.5.g integration tests (14 M3 scenarios as real V2 runtime tests)
Release-Gate M4.4. Alle 14 M3-Szenarien laufen durch den echten V2- Agent-Runtime (DecisionEngine + DecisionStepProcessor + SignalIngress + EffectRunner + Persistence + TelemetryTransport + production-Classifier), nicht als M3-Harness-Replay. EnrollmentOrchestratorFixture lockstep-synchronisiert den Fixture- Replay ueber processor.CurrentState.StepIndex + ingress.Queue-Length: StepIndex wird erst NACH DecisionStepProcessor.ApplyStep inkrementiert (inkl. EffectRunner.RunAsync, das synthetische ClassifierVerdictIssued- Signale postet) — Wait auf signalLog.LastOrdinal allein war race-prone, weil LastOrdinal in SignalIngress.ProcessItem VOR EffectRunner updated. Szenario-Coverage (alle gruen): 01 UserDriven-Happy, 02 UserDriven-HelloTimeout, 03 SelfDeploying-Happy, 04 SelfDeploying-EspExitUnknown, 05 WhiteGlove-Inline, 06 WhiteGlove-SignalCorrelated, 07 WhiteGlove-AntiLoop, 08 WhiteGlove-FalsePositive-LateAADJ, 09 WhiteGlovePart2-Happy, 10 WhiteGlovePart2-Stuck, 11 Hybrid-Reboot, 12 ESP-Terminal-Failure, 13 Late-AADJ-Standalone, + Telemetry-Pipeline-Smoke. DevPrep-v2-Varianten nicht drin: keine M3-Fixture vorhanden (follow-up M5/M7). FixtureLoader als Copy aus DecisionCore.Tests (Plan §2.16 Copy-Not-Share fuer V2-Isolation). Fakes an System-Boundaries: FakeBackendTelemetry Uploader, VirtualClock, TempDirectory. Real: Persistence, Ingress, EffectRunner, Classifiers. +14 Integration-Tests (273 → 287 gruen). Gesamte Test-Suite: ~287 V2.Core + 73 DecisionCore + 557 Functions = ~917 Tests plus Legacy Agent.Core 507. Pre-existing Threading-Flakiness in SignalIngressTests/DeadlineSchedulerTests bleibt (unveraendert, tracked als known-flake in Orchestration-Testclass). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a6f515643f |
refactor(v2core): M4.4.5.f recovery paths (WG part-1 resume + segment quarantine + transport resume)
Plan §2.7 Sonderfaelle 1-3 umgesetzt:
Sonderfall 1 — WG Part-1 → Reboot → Part-2:
Orchestrator erkennt state.Stage == WhiteGloveSealed beim snapshot.Load
und setzt IsWhiteGlovePart2Resume-Flag. Nach ingress.Start (vor
Collectors/Adapters) wird ein SessionRecovered-Signal in den Ingress
gepostet — der Reducer bridged ueber HandleWhiteGlovePart1To2Bridge zu
WhiteGloveAwaitingUserSignIn. Self-Destruct-Disable bleibt Caller-Sache
(Info via IsWhiteGlovePart2Resume exponiert).
Sonderfall 2 — Beschaedigtes Segment:
Snapshot-File existiert aber Load liefert null (Checksum-Mismatch oder
Deserialize-Failure) → SnapshotPersistence.Quarantine + neuer
SegmentQuarantine-Helper bewegen Snapshot/SignalLog/Journal/
EventSequence nach .quarantine/{timestamp}/. Writer werden danach
reinstantiiert (stale LastOrdinal/LastStepIndex → reset auf -1). Fresh
start mit DecisionState.CreateInitial. Observability via
WasStartupQuarantine.
Sonderfall 3 — Transport-Resume:
Kein Code-Aenderung noetig: UploadCursorPersistence + TelemetrySpool
lesen persistierten Cursor im Ctor, Orchestrator resumed automatisch
ab LastUploadedItemId + 1. Wire-Format (PK, RK) garantiert Backend-
Idempotenz. Test verifiziert: seeded Spool mit 3 Items + Cursor@0 →
Resume liefert nur Items 1+2.
+5 RecoveryPathTests (268 → 273 gruen). Helper-Klasse
Persistence/SegmentQuarantine (parallel zu SnapshotPersistence.Quarantine)
als oeffentliche API fuer zukuenftige Recovery-Szenarien.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
81b3e053a3 |
refactor(v2core): M4.4.5.e whiteglove sealing pattern IDs injection
Neue AgentConfigResponse.WhiteGloveSealingPatternIds-Property traegt die Tenant-Config-Liste der Pattern-IDs, die vom V2-Agent als WhiteGloveSealingPatternDetected-DecisionSignal re-emittiert werden. Default leer = Feature off = M3-kompatibel. IComponentFactory.CreateCollectorHosts nimmt zusaetzlich whiteGloveSealingPatternIds entgegen. Orchestrator akzeptiert die Liste per Ctor-Param und reicht sie an die Factory weiter, die sie an den ImeLogTrackerAdapter-Ctor uebergibt (M4.4.4 hat den Hook vorbereitet). EnrollmentOrchestratorEventBridgeTests FakeFactory-Signatur nachgezogen. +3 WG-Pattern-Injection-Tests (265 → 268 gruen; null → empty coerce, verbatim passthrough, empty collection erhalten). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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> |
||
|
|
6f758ea0f1 | late-aadj fix | ||
|
|
4851a7dfa4 | updated buidlcounter.txt | ||
|
|
3008ba69f6 | chore | ||
|
|
6719464f34 | fix WhiteGloveClassifier and Completion logic, fix Phase Timeline | ||
|
|
6efae2c709 | feat(agent): WhiteGlove Detection Classifier and late-AADJ support | ||
|
|
64240249ac | dashboard performance and more coding safety checks in integrity analyser | ||
|
|
ef1d57f32b | fix wording | ||
|
|
9d2d6c3ae7 | run integrity analyzer only on startup | ||
|
|
2eb6d87b4d | feat(agent): added IntegrityBypassAnalyzer | ||
|
|
2faba55b46 | Known Issues also in Navbar | ||
|
|
8d661bbd70 | whiteglove event 509 wording | ||
|
|
4d4a95e58b | updated Changelog |