mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 20:53:06 +00:00
ce1ca3dbf47c94e8aec9f74bcd0463632ca26997
22 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
977ad37463 |
fix(agent): HelloWizardStarted signal — never complete while the Hello wizard is running (session 772fe502)
A flip-flopping user-scoped WHfB CSP read "disabled" once, the arm-C completion synthesized HelloOutcome="Skipped" and the session completed 5s later while the user sat inside the Hello wizard that had started 230ms earlier (Shell-Core 62404, previously InformationalEvent-only). Layer 1 — engine (DecisionCore): - New DecisionSignalKind.HelloWizardStarted + set-once fact DecisionState.HelloWizardStartedUtc (additive-nullable, census + snapshot wired). - Prevention: centralized HelloSatisfiedForCompletion / HelloPolicyDisabledWithoutWizard predicates veto the policy-disabled stand-in at all five Skipped-synthesis sites and in the completion_waiting prerequisites; the AdvisoryCompletion handler gets a HelloWizardObservedPromote branch (its fallthrough is re-arm-or-fail). - Cure: HandleHelloWizardStartedV1 retracts an engine-synthesized "Skipped" (exact-case discriminator vs the lowercase tracker vocabulary; first fact-nulling reducer, stage-agnostic because the RealmJoin gate-defer parks synthetic skips pre-Finalizing), cancels finalizing_grace, returns to AwaitingHello and arms HelloSafety. - Agent path: ShellCoreTracker raises HelloWizardStarted before FinalizingSetupPhaseTriggered; 62404 included in the startup backfill (fire-once); coordinator timestamp mirror; adapter emit (fire-once). Layer 2 — HelloTracker confirmation read: - ApplyPolicyRead state machine: "disabled" commits only after a second confirming read (~10s poll); "enabled" commits immediately; disabled→enabled flags flipFlopDetected=true; pending keeps the policy-unknown wait cadence. Audit fields confirmedReads / flipFlopDetected on hello_policy_detected. Fixtures: signal-kinds/hello-wizard-started-v1.json + enrollment-sessions/userdriven-hello-wizard-unskip-v1.jsonl (772fe502 ordering, ends Completed with HelloOutcome=completed). Tests: DecisionCore 462/462, V2.Core 1775/1775. Docs: OKF concept doc docs/agent/hello-wizard-unskip.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c6b6e4504a |
feat(decision-core): proactive completion arm C from 4-fact post-ESP user-session evidence
Session a4537c36: Windows closed the User-ESP page normally (Shell-Core 62407, errorCode=0) while the AccountSetup registry never wrote categorySucceeded - the strong gate (arm A) was unsatisfiable by construction and the session only completed because the Hello policy happened to flip to enabled after a reboot. ShouldTransitionToAwaitingHello gains arm C: AccountSetup entered + normal ESP final exit (post-AccountSetup by INGEST ordinal - backdated CMTrace timestamps are irrelevant, sequence is canonical) + genuine IME user-session completion (defaultuser0 guard: at-or-after the AccountSetup anchor, extracted as shared IsImeUserSessionGenuine and reused by the AdvisoryCompletion backstop) + DAD- validated real-user desktop. ALL FOUR mandatory; Hello gating stays at the call sites - arm C never bypasses an enabled-unresolved Hello. Wiring per signal ordering: desktop last via the fast-path (desktopArrived- InFlight), exit last via HandleEspExitingV1 (espFinalExitInFlight + deferred- completion parity mirroring the caa6cf50 branch), IME last via new completion/ promote wiring in HandleImeUserSessionCompletedV1. completion_waiting stops reporting account_setup_provisioning_complete once the evidence set holds. AdvisoryCompletion stays as the 30-min backstop for incomplete evidence. The 08c99638 regression guard (3-of-4, no IME fact -> parked) stays green unmodified; the L5 backdated-exit test now asserts the eager completion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
eb132b11fd |
feat(agent): complete self-deploying/kiosk sessions despite IME AccountSetup false positive
Kiosk devices (self-deploying profile) never completed: the IME logs an AccountSetup phase line for the kioskUser0 autologon session although user ESP never runs, which cancelled/blocked the DeviceOnlyEspDetection deadline - the sole SelfDeploying terminal path - and the session idled to the 5h backend timeout (reference session 320b3bf7; 436 false Failed platform-wide, 90% of all self-deploying failures). Platform sweep (8983 sessions / 63 tenants): CloudAssignedOobeConfig bits 0x20|0x40 are exclusive to self-deploying/kiosk/shared profiles (0 of 6785 user-driven sessions carry them, bits always co-occur). The fix seeds Mode=SelfDeploying@High from that registry marker via EnrollmentFactsObserved and waives the AccountSetup veto at the four existing decision points (DSPC short-circuit, deadline race guard B, EspPhaseChanged cancel, RealmJoin deferred-release re-check) - only when the seeded profile holds AND user ESP made no real progress. Bits absent = byte-identical behaviour. Also registers IsSelfDeployingProfile on SessionRegistration (display metadata; backend persistence follows separately). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
caa573678f |
fix(decisioncore,agent/v2): defang ESP terminal failure when ContinueAnyway + AccountSetup entered
Session 4fa5a2d4 in tenant c9787ba2 hit 199/202 false-positive enrollment_failed events: ESP
profile sets BlockInStatusPage=6 (ContinueAnyway=true), Windows transitioned to AccountSetup,
then wrote DeviceSetupCategory=failed (Certificates) 13 min later — the reducer terminated
even though the device demonstrably kept progressing. PR1 Tier-1 defang downgrades these
EspTerminalFailure signals to a non-terminal esp_failure_advisory event while keeping
monitoring armed (Hello/Desktop/IME completion paths continue to drive the session).
DecisionEngine:
- New SignalFact<DateTime>? EspAdvisoryFailureRecordedUtc on DecisionState as the fire-once
gate (additive ctor param, optional at end — preserves Newtonsoft positional-binding for
pre-PR1 v4 snapshots).
- HandleEspTerminalFailureV1: when ScenarioObservations.EspAllowContinueAnyway==true AND
AccountSetupEnteredUtc!=null, emit esp_failure_advisory (severity=Warning,
advisoryReason=esp_failure_defanged_continueanyway_with_accountsetup) instead of
transitioning to Failed. Stage / deadlines / outcome stay untouched.
- Duplicate handling: a second EspTerminalFailure dead-ends UNLESS the duplicate carries
registry-specific keys (errorCode/failedSubcategory/category) — those only appear in the
ProvisioningStatusTracker path and indicate enrichment of an earlier ShellCoreTracker
advisory. failureType alone is NOT a registry marker (both adapters emit it).
- New BuildEspFailureAuditTrail helper merges a whitelisted set of failure-context keys into
the audit-trail typedPayload so HRESULT/subcategory survive the wire. Closes a pre-existing
gap on the enrollment_failed path too.
Bootstrap:
- EnrollmentOrchestrator.PostEspConfigDetectedBootstrap switched from EspSkipConfigurationProbe.
Read() (tuple SkipUser/SkipDevice) to ReadFull() so EspAllowContinueAnyway +
EspSyncFailureTimeoutMinutes are synchronously available before any EspTerminalFailure
signal — defeats the fire-and-forget DeviceInfoCollector.CollectAll race that would have
left Tier 1 with ScenarioObservations.EspAllowContinueAnyway==null.
Tests:
- EspFailureAdvisoryDefangTests (9 new): advisory path, fire-once idempotency, enrichment
on registry-detail second-fire, dead-end on ShellCore-only duplicate, three negative paths
(ContinueAnyway false / unknown / no AccountSetup), TypedPayload whitelist merge on both
advisory + Failed paths.
- SerializationTests: backcompat test for old v4 snapshots without EspAdvisoryFailureRecordedUtc.
- DecisionStateSnapshotBuilderTests: fact count bumped from 14 to 15.
- EdgeScenarioTests + esp-failure-advisory-continueanyway-v1.jsonl: end-to-end replay against
the canonical Session 4fa5a2d4 signal stream including a duplicate dead-end.
- EnrollmentOrchestratorEspConfigBootstrapTests: 4 existing tests migrated to ScopedFullOverride,
4 new tests cover ContinueAnyway + SyncFailureTimeout propagation, class gated behind
[Collection("SerialThreading")] since the override targets a static probe slot.
UI:
- EventTimeline renders esp_failure_advisory with an amber "Advisory (ContinueAnyway)" badge
alongside the HRESULT/subcategory chips so analysts can distinguish defanged from real
failures at a glance.
- gather-rules/eventTypes.ts mirrors the new constant for autocomplete.
Build: dotnet build AutopilotMonitor.sln green; xUnit Backend(1937) + V2.Core(1139) +
Agent.Core(538) + DecisionCore(357) + SummaryDialog(44) = 4015 passed, 0 failed.
Web tsc clean, vitest 384/384 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
5eb95868d8 |
fix(decisioncore): defang SelfDeploying terminal via 5-min deadline + UI phase coverage
Session 88a53223-9795-4188-8352-7df9f0af9bb7 (v1 Classic UserDriven, AAD Joined, SkipUserStatusPage=false) was wrongly classified as mode=SelfDeploying after the ProvisioningStatusTracker 30-s fallback fired DeviceSetupProvisioningComplete before Windows transitioned DeviceSetup -> AccountSetup. The signal alone was terminal in the old reducer; the absence of AccountSetupEnteredUtc at signal time was sufficient to commit terminal SelfDeploying. New rule: DeviceSetupProvisioningComplete is no longer terminal. It records a new DeviceSetupResolvedUtc anchor and (cancel-then-)arms the DeviceOnlyEspDetection deadline for 5 min. The deadline-fired handler is the sole SelfDeploying-terminal entry point and re-checks six guards (stale: no-anchor / not-armed / DueAtUtc-mismatch; race: Stage-terminal / AccountSetup-entered / monotonic Mode conflict) before classifying. Any dead-end after the DueAtUtc match state-side CancelDeadline()s the past-due deadline so snapshot reload doesn't re-arm via the scheduler. RealmJoin-deferred release (CompleteIfDeferredOrBookkeep) re-checks the same guards, resets DeviceOnlyDeployment hypothesis to Unknown on bailout, and operates on preparedBuilder.* (not state.*) so WithResolved / WithTimeoutOutcome facts survive the bailout. UI phase coverage closed: phase_transition(FinalizingSetup) and phase_transition(Complete) emitted at every terminal-success site (Classic FinalizingGrace, SelfDeploying deadline-terminal, AdminPreemption-Succeeded, RJ-deferred completion) so the Web timeline opens Finalizing + Complete bars universally - was missing entirely for SelfDeploying / AdminPreemption paths. Other changes: - Removed ApplyDeviceSetupProvisioningComplete (dead code under defang), added ApplySelfDeployingDeadlineConfirmed with monotonic guard. - New RealmJoinFacts.ClearSelfDeployingDeferred() for the RJ-bailout path. - DecisionState schema bumped v3 -> v4 with additive nullable DeviceSetupResolvedUtc field. StateSerializer v3-deserialize-compat verified. - DeviceSetupProvisioningComplete added to DecisionStateSignalCensus so the enrollment_complete audit trail surfaces the anchor. Test coverage: - New SelfDeployingDeadlineTerminalTests.cs (12 scenarios): happy path, all six guards including snapshot-migration rollout race + DueAtUtc cancel-rearm race, replay idempotency, audit-census wiring, RJ-deferred release races (AccountSetup-arrives / monotonic / Timeout-variant). - Updated 9 existing test files for new semantics; 2 fixtures now drive DeadlineFired terminal explicitly. - ReplayHarness + ClassifierAwareReplayHarness set AgentBootUtc to the first signal's OccurredAtUtc so fixture scenarios run in their own time-bubble (replay-safety floor still tested directly by BootAnchorDeadlineFloorTests). Tests: DecisionCore 345, V2.Core 1135, Backend 1937 - all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
bc02dad6bc |
test(decisioncore,agent/v2): RealmJoin gate, adapter, SID resolver tests + signal-kind fixtures (Phase D)
- DecisionCore.Tests/RealmJoinGateTests.cs (9 tests): baseline regression guard,
RealmJoinDetected arms 60-min timeout, gate defers Finalizing on Classic AND-gate,
RealmJoinResolved + Timeout both release through CompleteIfDeferredOrBookkeep,
SelfDeploying terminal defers + direct-complete via SelfDeployingDeferredCompletion,
per-package facts mutation, and audit-trail attaches realmjoin* fields.
- V2.Core.Tests/SignalAdapters/RealmJoinWatcherAdapterTests.cs (6 tests): each of the
5 watcher events maps to the correct DecisionSignalKind + dual InformationalEvent;
phase-changed is timeline-only; DisplayName clamped to 256 chars.
- V2.Core.Tests/Monitoring/Runtime/UserSidResolverTests.cs (4 tests): well-known
BUILTIN\Administrators + NT AUTHORITY\SYSTEM SID round-trips, invalid/empty inputs
return false.
- tests/fixtures/signal-kinds/realmjoin-{detected,resolved,timeout,package_started,
package_completed}-v1.json: replay fixtures fulfilling the merge-block contract
declared on DecisionSignalKind.cs:8.
Verified: 9/9 reducer + 10/10 adapter/SID tests green; no regressions in adjacent
suites.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
bb4f663ace |
fix(agent/v2,decisioncore): gate AwaitingHello on AccountSetup truly succeeded
Session 330f73f3 completed via DeadlineFired:finalizing_grace (helloOutcome= Timeout) while ESP AccountSetup was still at "4 of 5 subcategories completed" and 3 of 8 apps still pending. ShouldTransitionToAwaitingHello promoted on AccountSetupEnteredUtc != null alone, but Windows CXH event 62407 fires at every ESP-page transition — the first occurrence (Device→Account handoff, 9 s after AccountSetup entered) was treated as the genuine final exit, armed HelloSafety against an in-progress AccountSetup, fired its 5-min synthetic timeout, and FinalizingGrace declared the session terminal mid-flight. Introduce a strong "AccountSetup truly succeeded" fact sourced from ProvisioningStatusTracker (categorySucceeded=true OR fallback after 30 s of all subcategories at succeeded/notRequired — same shape as DeviceSetup). Forwarded by EspAndHelloTracker, posted as new DecisionSignalKind. AccountSetupProvisioningComplete by the V2 adapter, lands as DecisionState. AccountSetupProvisioningSucceededUtc. The reducer's ShouldTransitionToAwaitingHello now requires this fact (or SkipUserEsp); the old AccountSetupEnteredUtc gate becomes an entry-time marker only. HandleAccountSetupProvisioningCompleteV1 has a deferred-promotion path: when the strong fact arrives AFTER a guard-blocked EspExiting (EspFinalExitUtc set) OR after a guard-blocked EspPhaseChanged(Finalizing- Setup) (FinalizingEnteredUtc set, ShellCoreTracker adapter fire-once- dedupes), the handler performs the deferred promotion + arms HelloSafety anchored at the AccountSetup-succeeded instant. The new fact is registered in DecisionStateSignalCensus so enrollment_* audit trails and final-status.json show whether the strong gate was satisfied at termination — load-bearing for diagnosing future false-negative timeouts under the new gate. Side cleanup: ProvisioningStatusTracker's fallback timer callbacks (new AccountSetup AND pre-existing DeviceSetup parallel path) now reset the timer field on every abort path AND in the outer exception catch. Without this, a transient subcategory flicker between fallback arm and timer fire could permanently prevent re-arming even when the state re-stabilises. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d16b60f14c |
feat(v2): WG Part-2 architectural cleanup + install-mode handoff fix
Bundled change covering three orthogonal pieces of work that all landed in
the same working tree before the next PR-style commit could be split out.
## WG Part-2 architectural cleanup (PR-A through PR-E)
Plan: ~/.claude/plans/wg-part2-architectural-cleanup.md.
The V2 agent carried a full separate "Part-2 apparatus" (4 special signal
kinds, 2 special stages, a special outcome, a special classifier, a 24h
safety deadline, a dedicated reducer file, adapter `part2Mode` switches,
a SessionRecovered bridge). This did not match OS reality: post-reseal
Part 2 is just a normal user-driven OOBE running through the Classic
flow. The apparatus also carried a real risk (24h false-positive
enrollment_failed mid-success) and was not even active in practice
(observed in session bdb3cf9d-541b-4c44-aebe-5b9e72743b0a, 2026-05-04).
V2 is now V1-symmetric:
PR-A — Orchestrator Resume Mechanic. EnrollmentOrchestrator detects a
persisted Stage=WhiteGloveSealed snapshot at Start(), archives the
reducer-state segments to state/.part1-<utc-ts>/, runs as a fresh
first-boot through the normal recovery branches, and posts a single
whiteglove_resumed InformationalEvent after the onIngressReady hook.
New helper class StateArchiver.cs + StateArchiverTests +
EnrollmentOrchestratorPart2EmissionTests.
PR-B — DecisionCore Cleanup. Removed:
- DecisionSignalKind.UserAadSignInComplete / HelloResolvedPart2 /
DesktopArrivedPart2 / AccountSetupCompletedPart2 / SessionRecovered
- SessionStage.WhiteGloveAwaitingUserSignIn / WhiteGloveCompletedPart2
- SessionOutcome.WhiteGlovePart2Complete
- DeadlineNames.WhiteGlovePart2Safety
- DecisionState's 4 Part-2 SignalFacts (+ builder + snapshot slots)
- WhiteGlovePart2CompletionClassifier + WhiteGlovePart2CompletionSnapshot
- DecisionEngine.WhiteGlovePart2.cs (entire reducer file)
- HandleSessionRecoveredV1 dispatcher + handler
PR-C — Adapter Cleanup. Dropped the inert `part2Mode` ctor parameter
from AadJoinWatcherAdapter, DesktopArrivalDetectorAdapter,
HelloTrackerAdapter, EspAndHelloTrackerAdapter (and their tests).
AadJoinHost is the only production caller using a named-arg form,
unaffected.
PR-D — Backend + Web. Removed `WhiteGloveCompletedPart2` cases from
TelemetryPayloadParser.IsTerminalStage and DecisionGraphBuilder
(IsTerminalStage + DeriveTerminalOutcome) plus their tests. Dropped
the dead `UserAadSignInComplete` constant from Shared.Constants.
Extracted pure helper computeWhiteGloveSplitSequence(events) in
app/sessions/[sessionId]/utils/eventHelpers.ts; useSessionDerivedData +
utils/sessionExportUtils now share it. New 6-case vitest covering
splitter behaviour (no marker, primary trigger via whiteglove_resumed,
agent_started fallback, Pending-only variants, primary-vs-fallback
race). PhaseTimeline + eventTypes were already on the new model.
PR-E — Tests + Fixtures Final Cleanup. Deleted the two
whiteglove-part2-*.jsonl fixtures + EdgeScenarioTests references;
removed dead `whiteglove_part2` case + test from SummaryDialog
OutcomeMapper; trimmed PR-B history breadcrumbs that still embedded
removed symbol names so the final grep is clean. Updated MEMORY
entries (project_v2_completion_location, project_wg_part2_simple_model,
project_wg_signal_validation_pending, project_bdb3cf9d_wg_followups,
MEMORY.md index).
What stayed: SessionStage.WhiteGloveSealed (Part-1 marker), the
`_isWhiteGlovePart2` Hint-Bool (V1-symmetric, plan §11) wired only into
EnrollmentTerminationHandler / SoftwareInventoryAnalyzer for shutdown-
analyzer phase=2 tagging, the `whiteglove_part1_complete` event-type +
`whiteglove_part1` outcome-string, `PausedForPart2` terminal-outcome
label for the Inspector, the `whiteglove_resumed` event as UI-splitter
trigger.
Final acceptance: grep for the removed symbol set in src/ excluding V1
(Agent.Core/) is empty. V2.Core.Tests 991 / DecisionCore.Tests 282 /
Functions.Tests 862 / SummaryDialog.Tests 44 / Web vitest 227 — all
green. Solution build clean.
## Resume-path correctness fixes (Codex review of the cleanup)
Three findings on the new resume mechanic:
P1a — AgentRuntimeHost was still skipping LifecycleEmitters
PostEnrollmentFactsObserved + PostSessionStarted on isWhiteGloveResume.
After PR-A's archive-and-reset the decision state is empty, so the
Classic flow needs both anchors (otherwise ScenarioProfile stays at
default and the Hello+Desktop AND-gate misclassifies). Fixed: post
unconditionally for facts; SessionStarted is now only gated by an
AdminAction, not by the resume flag. The orchestrator's
whiteglove_resumed lands first (smaller sequence) so the Web splitter
(`splitSequence = resumed.sequence - 1`) still puts SessionStarted
into the Part-2 timeline block.
P1b — StateArchiver was archiving event-sequence.json along with the
reducer state. Part 2 reuses the same SessionId and the backend orders
events session-wide by (SessionId, Sequence); the Web split also uses
`splitSequence = resumed.sequence - 1`. Resetting the counter at the
resume boundary would collide Part-1 and Part-2 sequences and break
the splitter. event-sequence.json is now intentionally preserved; the
three reducer-state segments still archive. StateArchiverTests +
RecoveryPathTests asserting the new contract.
P2 — V1 reset the local session-age clock on Part-2 resume
(SessionPersistence.ResetSessionCreatedAt, MonitoringService.cs:530).
V2 was clearing only the WhiteGlove marker, so a crash/reboot during
Part-2 AccountSetup would let the next start run the emergency-break
watchdog against the original Part-1 timestamp and trip too early.
AgentRuntimeHost now also calls SessionIdPersistence.SaveSessionCreatedAt
(DateTime.UtcNow) right after ClearWhiteGloveComplete. New
ProgramGuardsTests test asserting the V1-symmetric resume contract.
## Install-mode handoff hardening
Plan reference (memory): project_v2_install_runtime_handoff.md.
Bootstrap script + Program.InstallMode + InstallModeTaskXmlTests.
PR1 — Replace `schtasks /Run` with WMI Win32_Process.Create for the
install-time runtime handoff. schtasks /Run reports SUCCESS as soon
as the run is queued; the same battery / AV / OOBE-defer paths that
motivate PR2 also queue a manual /Run (observed WinPE 2026-05-04,
task stuck in Queued state). WMI also gives the runtime process the
same isolation Task Scheduler uses, without the Job-Object inheritance
risk of a direct Process.Start.
PR2 — Register the BootTrigger Scheduled Task from a hardened XML
definition rather than `schtasks /Create`. The CLI form inherits XML
schema defaults DisallowStartIfOnBatteries=true and
StopIfGoingOnBatteries=true, which queue the BootTrigger run
indefinitely on a laptop that boots on battery in OOBE / WinPE. The
XML disables both, sets StartWhenAvailable, and pins
ExecutionTimeLimit=PT0S so the long-running agent is never killed at
the schtasks 72h CLI default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
579291272e |
fix(v2): split EnrollmentFactsObserved from SessionStarted to fix JoinMode race
V2 tracker / Backend-register-session race could land SessionStarted after ESP/Tracker signals had already advanced Stage past SessionStarted. The HandleSessionStartedV1 dead-end-Wache then swallowed the registry-derived profile facts (enrollmentType + isHybridJoin), leaving JoinMode=Unknown on every affected enrollment_complete payload (Session 10c8e0bf). Root cause was a concept mix in SessionStarted: a lifecycle anchor doing double duty as a static-facts seed. Split into two signals: - SessionStarted: pure lifecycle anchor (Stage / ClassifierTick / step). - EnrollmentFactsObserved: stage-agnostic, idempotent, monotonic facts carrier. New reducer handler delegates to ApplyEnrollmentFactsObserved (renamed from ApplySessionStarted, with set-once reason for v1 to honour the documented idempotency contract). AwaitEnrollment is transparently covered: AgentBootstrap blocks until the MDM cert exists, so the registry is guaranteed populated by the time PostEnrollmentFactsObserved runs; a single post per agent run suffices. Tests: - DecisionCore: 234/234 (4 new fact-handler tests + idempotency + monotonicity) - Agent.V2.Core: 771/771 (3 new producer tests, 1 reshaped) - Agent.Core (V1): 507/507 sanity-check unchanged Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
0305c0d635 |
test(v2): add UserDriven Hello-skipped fixture (anon df7abbc2)
Real-world V2 enrollment-session replay covering a UserDriven-v1 happy variant where Hello-for-Business was enabled but per-user enrollment resolved as "skipped" rather than "Success". Exercises the Hello+Desktop AND-gate's tolerance for any terminal Hello outcome. Asserts Stage=Completed, Outcome=EnrollmentComplete, HelloOutcome=skipped, ScenarioProfile.Mode=Classic (set by ImeUserSessionCompleted). README replaced with pointer to ScenarioTests.cs as source of truth — old "(empty)" placeholder dropped, only the new entry listed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
3bfe7d7fdb |
v2 codex-04: wire AppInstall signals as observation-level facts
Closes Codex review follow-up #4. Before this commit the reducer had no handler for DecisionSignalKind.AppInstallCompleted / AppInstallFailed — the signals were real (ImeLogTrackerAdapter posts one terminal per app) but fell through to HandleUnhandledSignal, so the journal accumulated 'unhandled_signal_kind:AppInstallCompleted:v1' dead-ends and the per-app outcomes disappeared. Subsequent ESP / IME classifiers had no structured evidence of which apps blocked. Scope: observation-only, as the Master-Tracker recommended. Neither handler changes Stage or Outcome — they roll the terminal outcome into a new DecisionState.AppInstallFacts aggregate that: - increments CompletedCount (+ Installed / Skipped / Postponed breakdown from the adapter's newState payload) - increments FailedCount and appends the appId to FailedAppIds, deduplicated and capped at MaxFailedAppIds (50) so pathological sessions cannot bloat DecisionState. Every DecisionSignalKind now has at least one handler; the dead-end path is reachable only through an unknown (kind, schemaVersion) pair — the genuine forward-compat failure mode per plan §2.5. The 'Still unwired' dispatch comment is replaced with a 'Fall-through: unknown (kind, schemaVersion)' label. Tests: - new AppInstallSignalHandlerTests (+10): Installed/Skipped/Postponed breakdowns, unknown newState forward-compat, no stage/outcome change, failed count + appId recording, dedup on repeated appId, MaxFailedAppIds cap keeps first-observed entries, mixed-stream integration covering all buckets in one fold. - HarnessTests.Replay_unknownSignalKind_recordsDeadEnd renamed to Replay_unknownSchemaVersion_recordsDeadEnd, switched from the now-wired AppInstallCompleted to a v99 SessionStarted so the dead-end path stays covered through its real-world trigger. - ReducerFailSafeTests.Reduce_unhandledKind same retarget. Suites green: DecisionCore 161/161 (+10), Agent.V2.Core 643/643, Functions 734/734. Build 0 warnings. Fixtures: - tests/fixtures/signal-kinds/app-install-completed-v1.json - tests/fixtures/signal-kinds/app-install-failed-v1.json Plan: .claude/plans/codex-04-appinstall-signals-wiring.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
386f3e909f |
v2 codex-02: EffectRunner posts EffectInfrastructureFailure on critical fail
Closes Codex review follow-up #2. Before this commit, a DecisionEffectKind.ScheduleDeadline / CancelDeadline failure raised EffectRunResult.SessionMustAbort but the decision world never heard about it — DecisionStepProcessor only logged and continued. A safety deadline could live in DecisionState without ever being armed on the scheduler, leaving the session to hang until the max-lifetime watchdog tripped hours later. Flow now: 1. EffectRunner.TryRunCriticalDeadlineEffect fails. 2. EffectRunner posts a synthetic DecisionSignalKind.EffectInfrastructureFailure signal onto the ingress before returning. Payload: reason + failingEffect. 3. Ingress worker picks it up next iteration. 4. HandleEffectInfrastructureFailureV1 (new, in DecisionEngine.Shared.cs) transitions Stage=Failed, Outcome=EnrollmentFailed, ClearDeadlines, and emits one EmitEventTimelineEntry effect for enrollment_failed with the reason from the payload. 5. DecisionStepProcessor observes the terminal stage and fires the Terminated event; EnrollmentOrchestrator tears down normally. EnrollmentFailed (not Aborted) distinguishes infrastructure-induced failures from user/operator-initiated aborts in forensics. The ingress post is wrapped in a try/catch so a full/closed channel cannot crash the worker — the max-lifetime watchdog remains the last-resort exit. Changes: - DecisionSignalKind: new EffectInfrastructureFailure enum value. - DecisionEngine: dispatch entry (v1). - DecisionEngine.Shared: HandleEffectInfrastructureFailureV1 handler, mirrors HandleSessionAbortedV1 shape but with EnrollmentFailed outcome and a failure timeline effect. - EffectRunner: PostEffectInfrastructureFailureSignal helper, called from the critical-effect path before returning SessionMustAbort. - EffectRunResult: XML-doc clarifies that SessionMustAbort is now observability-only (reducer terminates on its own). - DecisionStepProcessor: comment update — no code change needed, the synthetic signal reaches state naturally. - tests/fixtures/signal-kinds/effect-infrastructure-failure-v1.json: replay fixture per the enum's self-documented contract. Tests (+8): - DecisionCore EffectInfrastructureFailureHandlerTests: transition shape, deadline clearing, enrollment_failed emission with reason, missing-reason fallback, double-post idempotency. - EffectRunnerTests: schedule-fail posts signal with correct payload, cancel-fail same, ingress-post exception tolerated. - FakeSignalIngressSink: ThrowOnPost toggle for the tolerance test. All suites green: DecisionCore 144/144 (+5), Agent.V2.Core 633/633 (+3), Functions 727/727. Build 0 warnings. Plan: .claude/plans/codex-02-effect-failure-to-decision.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
96dc1482c7 |
chore: drop local-only tooling + fixtures from repo
- tests/fixtures/enrollment-sessions-local/ is raw/pre-anonymization session fixture staging — keep untracked; remove .gitkeep + README.md from the index and simplify the gitignore to ignore the whole folder. - tools/export-session-fixture.mjs is a local conversion helper; add to gitignore next to the existing tools/orphan-analysis.py entry. Files remain on disk locally; only the Git index is pruned. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7c29b607dc |
v2 single-rail 6.1: guard DeviceSetupProvisioningComplete terminal on Classic path
HandleDeviceSetupProvisioningCompleteV1 completed unconditionally — in session e259c121-dc13-46d6-8e96-118f1da9845e (2026-04-22, Classic UserDriven-v1) a late DeviceSetupProvisioningComplete with payload deviceSetupResolved="unknown" fired AFTER AccountSetup had started and DesktopArrived had been observed, but BEFORE HelloResolved — terminating the session at Stage=Completed before Hello even ran. Guard: when state.AccountSetupEnteredUtc != null, treat the signal as informational only (step index + ordinal advance, stage unchanged, no completion effect). On the Classic path, terminal completion still requires the Hello+Desktop AND-gate (HandleHelloResolvedV1 / HandleDesktopArrivedV1). The original SelfDeploying / Device-Only terminal semantics are preserved for AccountSetupEnteredUtc == null. The redundant AccountSetupEnteredUtc == null check around the EnrollmentType upgrade is now implicit and removed. Regression coverage: - New fixture tests/fixtures/enrollment-sessions/userdriven-late-provisioning-complete-v1.jsonl reproduces the 6-signal sequence from the reference session. - New test ClassicScenarioTests.LateProvisioningComplete_afterAccountSetup_doesNotCompletePrematurely asserts terminal trigger == HelloResolved, DeviceSetupProvisioningComplete transition is taken but stage-unchanged, and DeviceOnlyDeployment hypothesis stays Unknown. Tests: 109/109 DecisionCore + 559/559 V2.Core green; solution builds clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
489f124c6d |
v2 single-rail 4.1a: add InformationalEvent signal kind + payload keys
Introduce the generic pass-through signal that carries an EnrollmentEvent shape through the decision engine. Any peripheral collector / lifecycle source needing the Events timeline will post this instead of calling TelemetryEventEmitter directly, so the engine is the single ordering source for the wire. Promotion to a decision-specific kind is a later, localized change. - DecisionSignalKind.InformationalEvent (new). - SignalPayloadKeys: EventType + Source (mandatory), Message, Severity, ImmediateUpload, DataJson (optional). - tests/fixtures/signal-kinds/informational-event-v1.json documents the wire shape with a realistic NTP-probe payload. No reducer handler yet — that is the next subtask (4.1b); this commit is purely the contract extension. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5b7f424d19 |
refactor(agent-v2): route whiteglove_resumed / system_reboot_detected / admin preemption through reducer effects
V2 parity cleanup - the three lifecycle events were emitted from Program.cs
directly while the corresponding reducer handlers (HandleWhiteGlovePart1To2Bridge,
HandleSystemRebootObservedV1) returned Array.Empty<DecisionEffect>(). Admin
preemption synthesised an EnrollmentTerminatedEventArgs and invoked
terminationHandler.Handle directly, bypassing the reducer entirely.
This commit unifies all three flows: the reducer is the single state owner, the
telemetry event appears as an EmitEventTimelineEntry effect of the state
transition, and the orchestrator's Terminated event fires automatically when
stage goes terminal - no more synthetic bypass.
- B1: HandleWhiteGlovePart1To2Bridge emits whiteglove_resumed timeline entry.
- B2: HandleSystemRebootObservedV1 emits system_reboot_detected with payload
keys previousExitType/lastBootUtc.
- B3: new DecisionSignalKind.AdminPreemptionDetected + new SessionOutcome.AdminPreempted.
Reducer HandleAdminPreemptionDetectedV1 transitions to Completed/Failed based on
adminOutcome payload, emits enrollment_complete/_failed timeline entry with
adminAction+source data keys. Program.cs posts the signal; terminationHandler
still subscribes to orchestrator.Terminated (no direct Handle calls on the
startup-preemption path).
Fixtures: tests/fixtures/signal-kinds/{system-reboot-observed,admin-preemption-detected}-v1.json.
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>
|
||
|
|
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>
|