Commit Graph

52 Commits

Author SHA1 Message Date
Oliver Kieselbach acf4d0eeaa bootstrap(dev): v2.3-dev.2 - Cloud PC detection on captured evidence
First W365 field test (2026-08-06) proved the assumed WMI signature wrong:
a real Cloud PC reports the generic Manufacturer='Microsoft Corporation' +
Model='Virtual Machine'; the 'Cloud PC Enterprise ...' string exists only in
Intune/Graph inventory. Probe on the live Cloud PC captured two local
markers instead: HKLM\SOFTWARE\Microsoft\Windows365 and the
CloudManagedDesktopExtension service (W365 management agent).

Get-CloudPcModel is replaced by Test-IsCloudPc requiring BOTH markers (each
alone has look-alikes: W365-Boot physical clients / legacy managed-desktop
agents). Raw identity and both marker states are always logged. Tests
updated to the marker model (26 tests, green under PS 5.1 and pwsh 7).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 18:14:58 +02:00
Oliver Kieselbach 4c1889d011 bootstrap(dev): always log raw Manufacturer/Model for Cloud PC diagnosis
First W365 field test failed: the log said only "CloudPC: False" with no way
to tell whether Win32_ComputerSystem reported unexpected values or the query
failed. Get-CloudPcModel now logs the raw identity on every run and the
error message on failure. Detection pattern itself unchanged until the
probe from the real Cloud PC delivers evidence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 17:59:49 +02:00
Oliver Kieselbach acc95471a6 bootstrap(dev): Pester-testable structure + guard/relax test suite + CI
Refactor Install-AutopilotMonitor-dev.ps1 (v2.3-dev) into functions
(Get-CloudPcModel, Get-RealUserProfilePaths, Get-RelaxDecision,
Get-BootstrapDecision with reason codes) with an Invoke-Bootstrap main and
a dot-source entry guard, so the Pester suite can load the logic without
executing the bootstrap. Behaviour when run directly is unchanged.

Fixes a refactoring leak found by smoke-run: Write-Log used Write-Output,
which turned function return values into arrays and swallowed console
output; now Write-Host, guarded by a dedicated logging-purity test.

Add Install-AutopilotMonitor-dev.Tests.ps1 (25 tests: Cloud PC detection,
relax scenarios incl. the W365 field case, guards 1-5, profile collection
with WMI failure path) and .github/workflows/pester.yml running the suite
under Windows PowerShell 5.1 (IME runtime) plus an ASCII guard over all
bootstrap scripts. Tests pass 25/25 under PS 5.1 and pwsh 7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 20:17:10 +02:00
Oliver Kieselbach b142fa0994 updated bootstrapper for W365 devices 2026-08-05 19:49:39 +02:00
Oliver Kieselbach 3392a23da6 feat(bootstrap): promote dev script v2.2 to stable — download.autopilotmonitor.com endpoint + OOBE-relax (#118)
The stable Install-AutopilotMonitor.ps1 is now the previously field-tested
-dev variant (v2.2): agent download via the Front Door alias
https://download.autopilotmonitor.com/agent/ instead of the legacy blob
host, mandatory SHA-256 manifest verification (Content-MD5 fallback
removed), and the OOBE-relax exemption for the Windows Backup for
Organizations restore case. Test-ShouldBootstrapAgent.ps1 mirrors the
same guard logic; the separate -dev.ps1 files are gone.

CI dev-render anchor literals in build-agent.yml updated to the alias
URLs (stable + dev channel) so the literal-substitution check keeps
passing; local build.ps1 anchors updated too (gitignored).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 01:37:17 +02:00
Oliver Kieselbach ba3581e253 bootstrapper added download stopwatch 2026-07-23 09:35:30 +02:00
Oliver Kieselbach 5f08a6ec68 updated dev script 2026-07-20 19:52:54 +02:00
Oliver Kieselbach 588ef08fdf fix(bootstrap): correct scalar-unwrap in OOBE-relax profile age check
The $profilePaths assignment wrapped only the inner subexpression in @(),
so the trailing Where/Select-Object -Unique pipeline unwrapped a single
result back to a scalar string. With exactly one user profile present,
$profilePaths[0] then indexed into the string and returned its first
char ('C'), making Get-Item fail with "Cannot find path 'C'". The OOBE
age check crashed and the Windows Backup for Organizations restore case
never relaxed guards 2+3.

Force an outer @() so $profilePaths is always an array. Also surface the
profile age and the exact failing condition in the SKIP/NOT-applied logs
for diagnosing future OOBE attempts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 21:04:49 +02:00
Oliver Kieselbach 62d8d4d9d3 updated dev bootstrap script 2026-06-25 12:41:59 +02:00
Oliver Kieselbach fe64b38e7d new dev bootstrap scripts 2026-06-25 09:30:38 +02:00
Oliver Kieselbach d973f457c4 chore: untrack scripts/Backup/setup-backup-container.ps1 (operator-local)
The script applies a lifecycle policy + container creation against a live
storage account using operator-specific parameters (StorageAccountName,
ResourceGroupName). It is not portable across environments and each
operator keeps their own copy, so it does not belong in the repo.

The file is preserved on disk (git rm --cached, not -f) so the local
operator setup keeps working — only the Git tracking is dropped, plus
the matching .gitignore entry so a future `git add scripts/Backup` is
silently ignored.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:38:46 +02:00
Oliver Kieselbach d4034842ee feat(backend/backup): PR1 — critical-table backup feature (daily timer + manual GA trigger)
Adds the full backup pipeline that snapshots 15 critical tables (Admin/Tenant
config, rules, identities, audit, preview) into a logically-isolated blob
container, daily at 04:00 UTC and on operator demand. Restore endpoints follow
in PR2/PR3.

Architecture:
* Daily TimerTrigger + manual POST /api/global/backups/trigger (GA-only,
  fail-hard producer). HTTP returns 202 + jobId on enqueue, 500 if the queue
  write fails (and the BackupJobs row is rolled to Failed).
* BackgroundService queue worker (not QueueTrigger — matches the Analyze/
  IndexReconcile pattern that avoids the Functions-host connection-binding
  app-setting). Worker holds the maintenance lease for the whole run; service
  is intentionally lease-unaware so it stays unit-testable.
* BlobBackupStore wraps the new 'critical-table-backups' container with a
  shared maintenance-lease sentinel at '_lock/maintenance.lock' that
  serialises every backup or restore op across worker/timer/single-row paths.
* CriticalTableBackupService streams each table through CryptoStream(SHA256)
  into per-table NDJSON blobs, then writes manifest.json LAST as the
  durability anchor. Per-table failures degrade Outcome to Partial without
  aborting; per-run budget (50 min) → Skipped status.
* BackupJobsRepository persists the six-state machine
  (Queued/Running/Completed/Failed/Skipped/BlockedTerminal) with ETag-CAS
  updates. Enum→string mapping rejects unknown values on read so refactors
  surface loudly instead of silently shifting state.
* BackupJobWatchdog (30 min timer) heals stuck rows via a probe lease on the
  shared sentinel: Queued > 60 min → Failed, Running with stale heartbeat
  AND free lease → Failed (lease-busy means a live worker — leave alone).
* OpsEvents: distinct CriticalTableBackupPartial (Warning) vs Failed (Error)
  so operators can wire softer Telegram rules for partial captures.
* MaintenanceService.MonitoredPoisonQueues + OPS_EVENT_TYPES catalog
  registered (dual-register memory).

Test coverage (15 new tests, 1952/1952 backend xUnit green):
* BackupManifestJsonTests: enum-as-string (allowIntegerValues=false rejects
  numeric values), DictionaryKeyPolicy unset (Azure column names preserved
  case-faithfully), manifest field round-trip.
* BackupJobsRepositoryMappingTests: full entity↔domain round-trip, unknown
  Kind/State/BackupOutcome rejected, legacy BackupOutcome="Failed" rejected.
* CriticalTableBackupServiceTests: happy/partial/empty/budget-exceeded paths
  exercise the per-table loop with in-memory table + blob fakes.

Setup: scripts/Backup/setup-backup-container.ps1 — idempotent az-CLI bootstrap
for container + lifecycle rule (90 d delete on prefix). Soft-Delete and
Versioning are NOT applied automatically (storage-account-wide blast radius
— operator must validate per separate doc note).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 15:19:07 +02:00
Oliver Kieselbach e4c4dd57db feat(backend,web): optional Graph add-on permissions for Intune script display names
Adds opt-in resolution of Intune Platform Script + Remediation display names in
session timelines via a tenant-side appRoleAssignment grant. No app manifest
change, no impact on customers who don't enable it. Architecture is reusable for
future optional Graph permissions: catalog -> detector -> resolver -> endpoint.

Backend
- GraphFeatureDetector parses the `roles` claim from the SP access token and
  caches the (token, roles) bundle per tenant. GetSnapshotAsync surfaces a
  transient flag so admin-UI callers can render "unknown" instead of a
  misleading "0 of 0 granted" when token acquire times out.
- GraphTokenService now accepts a CancellationToken so optional-feature callers
  can apply tight budgets instead of inheriting the 5+15+30 s retry chain.
- ScriptDisplayNameResolver: cache-first, list-pull-on-cold-tenant, per-ID
  fallback. Throttle-aware abort: skip per-ID for kinds whose full-pull hit a
  transient failure; break the per-ID loop on the first HttpRequestException.
  Total budget 5 s, with a tight 1.5 s sub-budget on the token acquire.
- Azure Table `ScriptNameCache` + direct-TableClient fail-loud repository
  (mirrors TableOffboardingAuditRepository pattern). Daily
  ScriptNameCacheCleanupFunction: 7 d positive / 1 h NotFound TTL.
- POST /api/tenants/{id}/scripts/display-names (body-carried refs to dodge URL
  length limits; backend cap 200 refs/request).
- GET /api/tenants/{id}/graph-permissions/status (4 s budget; includes app
  ClientId so the UI can render an exact grant command).
- POST /api/tenants/{id}/graph-permissions/refresh (invalidates token cache).
- All three routes registered in EndpointAccessPolicyCatalog (MemberRead for the
  resolve, TenantAdminOrGA for the admin-UI surfaces).

Web
- `useScriptDisplayNames` hook with stable refset fingerprint (no spurious
  refetches on parent re-renders); clears stale map on tenant/refset change so
  a session switch never leaks names across tenants.
- Chunked POST loop: 200-ref batches with partial-success semantics. Preserves
  earlier chunks when later ones fail (non-2xx OR transport throw); only
  TokenExpiredError propagates so auth flow can react.
- ScriptExecutions renders " . {DisplayName}" alongside existing label/short-id,
  keyed by Kind + policyId so Platform/Remediation under the same id never
  collide.
- New Settings section "Optional Graph capabilities": status table with granted
  / not granted / unknown, copy-paste PS one-liner with ClientId baked in,
  refresh button gated on response.ok.

Customer-facing
- scripts/CustomerSetup/Grant-AutopilotMonitorAddOn.ps1: idempotent
  grant / verify / revoke script. -Features ScriptDisplayNames (high level) or
  -Permissions ... (admin-UI generated). Pure ASCII for PS 5.1 compat.
- docs/customer-graph-add-on-permissions.md: end-to-end customer guide
  covering prerequisites, the appRoleAssignment pattern, and revocation.

Tests
- 1718 backend xUnit / 364 web vitest. Highlights: JWT roles parsing edge
  cases; full-pull-429 suppresses per-ID; per-ID-429 breaks loop; transient
  detection via GetSnapshotAsync; chunked partial success (HTTP failure + thrown
  transport); TokenExpiredError propagation; EndpointPolicyCatalogCompleteness
  covers the three new routes by reflection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 01:14:21 +02:00
Oliver Kieselbach d51bde6017 refactor(release): parametric per-line agent build pipeline
Replaces V1/V2-hardcoded build + bootstrap with a single parametric
pipeline that scales to V3+ without rewrites. Full architecture in
.claude/plans/v2-cutover.md.

Stable namespace (Bootstrap-side, never renamed): generic Bootstrap
script, agent owns its --install defaults so it works for any line.
Versioned namespace (per-line, SelfUpdater-side): isolated *-v{N}.*
blobs; SelfUpdater never crosses lines or touches stable.

Backend GetAgentConfig dispatches per X-Agent-Version-Major to
AdminConfiguration.GetAgentLine(int) -> LatestAgentV{N}* fields.
Future V3 = add field set + switch arm. TableConfigRepository.Map
falls back to legacy unsuffixed columns for transparent migration.

V1 SelfUpdater now reads version-v1.json (would have been clobbered
by the V2 cutover otherwise). V2 agent owns its own 600s TenantId-wait
default so the Bootstrap calls --install plain.

Tests: 3289 across 6 suites, all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 19:36:53 +02:00
Oliver Kieselbach e39719d3ae updated bootstraper V2 2026-05-07 22:29:36 +02:00
Oliver Kieselbach 08107d7b68 fixed scripts v2 leftovers 2026-05-07 10:17:35 +02:00
Oliver Kieselbach d8e1eb9292 Changed bootstrap script and V2 agent binary name (now without V2, preperation for V1 replacement) 2026-05-07 10:12:37 +02:00
Oliver Kieselbach 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>
2026-05-04 18:36:37 +02:00
Oliver Kieselbach 3016c15919 feat(agent): event-driven TenantId wait via RegistryWatcher
Bridges the OOBE / hybrid-AAD-join race window where the agent fires
before AADTenantID / CloudDomainJoin\TenantInfo are written to the
registry. Field evidence (GardnerMedia, 2026-04-30): the agent fired
~5 minutes before the AAD device cert landed; an event-driven wait of
10 minutes recovers the run instead of bailing and waiting for next
reboot.

Mechanism (V2 only — V1 keeps the fast-fail path):
* New TenantIdAwaiter.WaitForTenantId(timeoutSec, logger, ct) sets up
  a CompositeRegistryChangeSignal over HKLM\SOFTWARE\Microsoft\Enrollments
  and HKLM\SYSTEM\...\CloudDomainJoin (Registry64 view, recursive,
  Name+LastSet+ThreadAgnostic filter), re-probes silently on every
  change, and returns the resolved TenantId or null on timeout.
* 30 s belt-and-suspenders periodic re-probe protects against a missed
  RegNotifyChangeKeyValue notification.
* 100 ms debounce after each signal so multi-value enrollment
  transactions land before the re-probe.

Configuration:
* Opt-in via --tenant-id-wait <sec>. Default 0 = no wait (legacy
  fast-fail). The PS1 bootstrap script owns the production default;
  the agent itself never assumes one.
* CLI value persisted to bootstrap-config.json by --install so the
  Scheduled Task picks it up on each restart.
* Install-AutopilotMonitor-v2.ps1 sets $tenantIdWaitSec = 600. Admins
  override per tenant by editing this single line and re-deploying.

Testability:
* IRegistryChangeSignal abstraction lets unit tests drive the loop
  with a fake signal and scripted probe — 8 deterministic cases cover
  timeout, signal hit, multi-signal, cancellation, periodic re-probe,
  whitespace/empty/null handling, zero-timeout short-circuit, and
  argument validation.
* RegistryWatcherIntegrationTests exercise the real Win32 plumbing
  (RegNotifyChangeKeyValue → Changed event) against HKCU so no
  elevation is required and the test runs in standard CI.
* Both classes opt out of xUnit parallelisation — the timing-sensitive
  Thread.Sleep handshakes are unreliable on a busy parallel pool.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 21:54:43 +02:00
Oliver Kieselbach 86ae616b3b feat(bootstrap): verify runtime process started after schtasks /Run
schtasks reports SUCCESS as soon as the run is queued, not once the
process is actually alive. A silent block (AV/EDR, AppLocker/WDAC) or
Task Scheduler defer during OOBE would otherwise stay invisible until
the next reboot triggers the task via /SC ONSTART.

After --install, poll Get-Process for AutopilotMonitor.Agent.V2 every
500ms up to 10s. Log pid + StartTime on success, or a clear WARNING
that points operators at TaskScheduler/Operational + AV/EDR logs on
failure. Same change applied to the production and dev variants.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 18:05:51 +02:00
Oliver Kieselbach bc453c31d7 chore 2026-04-29 08:57:16 +02:00
Oliver Kieselbach 710504549b refactor(agent): V2-Agent-Split skeleton + infrastructure copy (M2)
Three new projects as a clean-slate V2 fork of the Legacy Agent:

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 13:31:30 +02:00
Oliver Kieselbach 6702e1d43b feat: added DevPrepValidator (device association) and restructured agent to distinguish Classic vs v2 autopilot flow, in addition option for dev build added 2026-04-14 16:09:32 +02:00
Oliver Kieselbach 3f7fcbc684 feat: added StallProbeCollector to detect stalled sessions and identify ESP failure better
added Bootstrapper script version
2026-04-09 19:31:54 +02:00
Oliver Kieselbach 71e6475497 updated bootstrapper 2026-04-01 00:27:23 +02:00
Oliver Kieselbach 823c459138 bootstrapper exit values 2026-04-01 00:10:29 +02:00
Oliver Kieselbach 7596b586b0 bugfix bootstrap 2026-03-31 23:54:46 +02:00
Oliver Kieselbach dd8e155a11 comment update 2026-03-31 23:35:16 +02:00
Oliver Kieselbach 55b077659d bug fix bootstrap 2026-03-31 23:30:43 +02:00
Oliver Kieselbach 5f1bd1c470 bugfix bootstrapper and update architecture.md 2026-03-31 22:49:51 +02:00
Oliver Kieselbach 7c3d8f54bd added agent unit tests 2026-03-31 22:29:24 +02:00
Oliver Kieselbach 3b871b299d cahgned bootstrap guards and added more, new hybrid join completion logic 2026-03-31 22:00:08 +02:00
Oliver Kieselbach 1a89080f0f agent docs updates 2026-03-31 21:25:51 +02:00
Oliver Kieselbach 09156f64ad polished bootstrapper ps1 2026-03-30 18:13:16 +02:00
Oliver Kieselbach 705555369f fix bootstrapper scripts broken by non-ASCII characters
PowerShell 5.1 (used by IME) reads scripts without BOM as ANSI,
corrupting UTF-8 multi-byte chars (em-dashes, Unicode symbols) and
causing cascading parse errors that prevented agent installation.

Replaced all non-ASCII characters with ASCII equivalents in both
Install and Uninstall bootstrap scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 18:04:01 +02:00
Oliver Kieselbach d1ffac14bc hardened integrity check of agent via SHA256 from version.json and agent config (download and runtime check) 2026-03-29 22:56:11 +02:00
Oliver Kieselbach 1236c1b51e fix path 2026-03-20 08:12:09 +01:00
Oliver Kieselbach 39d888cfb1 SEO optimizations 2026-03-20 08:07:17 +01:00
Oliver Kieselbach bd48ddac00 removed old readme 2026-02-23 22:06:03 +01:00
Oliver Kieselbach e93cfb19d9 settings wordings and bootstrap log file name 2026-02-16 21:34:58 +01:00
Oliver Kieselbach e14675c847 fixed constants
fix for analyze rule reboot loop
added completed timestamp to progress portal
longer os age default in bootstrapper
removed legacy filed name in events
2026-02-16 12:29:03 +01:00
Oliver Kieselbach 8858b588a2 SerialNumber Validaion implementation against Intune Auopilot 2026-02-13 22:14:02 +01:00
Oliver Kieselbach 65a20febcb simplified bootstrapper script and introduced --install parameter for agent 2026-02-13 17:29:48 +01:00
Oliver Kieselbach 731894bc04 Agent: use remote-config exclusivly
Function: deliver new default config
2026-02-13 17:03:14 +01:00
Oliver Kieselbach 4ab889b6f6 bootstraper more robust download and integrity check 2026-02-12 21:27:56 +01:00
Oliver Kieselbach eca8aceead fixed startedAt
fixed bootstrapper ps script
enhanced HelloDetector
2026-02-12 21:01:05 +01:00
Oliver Kieselbach 9ad36c458c agent:
added boottime
functions:
fix completedAt
UI:
fix timeline missing events
2026-02-12 18:13:52 +01:00
Oliver Kieselbach 9f41300a13 more pre-flight checks:
agent already installed
2026-02-12 16:52:47 +01:00
Oliver Kieselbach 1810a8e3d4 Optional IMELogTracker pattern match logfile 2026-02-11 19:33:38 +01:00
Oliver Kieselbach b81687922c bootstrapper to public api
added maintenance mode config
2026-02-11 00:30:52 +01:00