Repository navigation
Conversation
Adds failing tests against the PR-4.md interface contract (reconcile::{ProgramState,
ProgramOutcome, ApplyReport, AttributedPlan}, Reconciler::{plan_with_report,
apply_with_report}, ReconcilePlan::inactive, reconcile::configured_programs; the
controller's device_program_status storage; the proto's ProgramStatus/ProgramState).
New symbols the contract names are compile-only todo!() stubs; existing tests are
unmodified and still pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The controller kept one configuration status per device (applied or
failed, one error string), and one conflicting file blocks every managed
file on the device. An admin could not tell which program conflicted,
which were blocked by it, or which were configured but not pointed at the
gateway because the local LLM proxy was not running.
Agent. Each apply now reports an outcome per managed program, by
precedence failed > conflict > blocked > inactive > applied/removed >
unchanged:
- `Provider` gains a required `id()` (the existing `ID` constants, the
inventory's agent kind).
- `Reconciler::plan_with_report` plans every provider and keeps planning
past a failing one; `AttributedPlan::apply` applies with the same
all-or-nothing rules and returns the same first error as
`Reconciler::apply` (unchanged, still used by tests).
- `ReconcilePlan` records, per provider before it is appended, whether the
plan changes anything on disk (differing bytes, a removal, or a recorded
create/update/remove; a sidecar rewritten with the same bytes does not
count), its conflict lines, an inactive reason, and the paths it owns;
`apply` now also says where it stopped (conflict, changed observed file,
private directory, or the index of the failed write), so the failing
program is `failed`, programs already written are `applied` and the rest
`blocked`.
- The Copilot CLI and both VS Code files record `inactive` with the reason
where they already warned about a missing proxy.
- Programs are reported when configured (an exhaustive destructure of
`ProgramsConfig`, so a new key does not compile until mapped) or when
they have something to clean up, conflict or fail. Details are truncated
to 1024 bytes on a char boundary.
- The remote apply, the startup apply and `--once` log one `program
configuration outcome` line per program that is not applied or unchanged
(before a startup failure stops the daemon).
Proto (additive): `ConfigStatus.programs` (`ProgramStatus { program,
state, detail }`, `ProgramState` enum) and `ConfigStatus.programs_reported`.
Older controllers ignore them; older agents send neither.
Controller. Migration 0003: `device_program_status` (per device and
program, with the report position) and
`device_config_status.programs_reported`. `update_config_status` validates
the report (at most 32 programs, IDs `[a-z0-9-]` up to 64 bytes, no
duplicates, details up to 2048 bytes; an invalid report is an error, as
for telemetry) and writes the device row and the program rows in one
transaction, so a program missing from a report disappears. `GET
/api/v1/devices/{id}` returns `programs` (ordered as reported, unknown
states as `unspecified`) and `programs_reported` (null before any status).
The device list and overview are unchanged.
UI: a "Managed programs" card on the device page (tool, state badge,
detail, time); "Per-program status is not reported by this agent version"
for older agents; storybook fixtures for every state.
README: "Configuration status" under "Start locally, grow into a fleet".
Tests: baseline 6684bf5 (test author, 20 tests: 13 reconciler, 2 remote, 5
controller), unchanged; the test fake gained `id()`. Writer: Docker test
`program_status_reports_inactive_without_the_proxy` (VS Code image, the
outcome line in the daemon log) and two storybook stories. Agent lib 266,
controller 17, core 18, storybook 52.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed sidecar values, stable tunnel test) Review round 1 on pr4-v1 (conformance conforms; maintainer one major; adversarial minors). - `Reconciler::apply` wraps `apply_with_report` instead of keeping its own planning loop, so the all-or-nothing rule exists once and the tests that call `apply` run the code production runs (maintainer M1, adversarial n1). - `AttributedPlan::apply` builds every outcome in one place from how the apply ended (`Stop`: completed, refused before writes, or failed at a point with its owners); same results (maintainer m2). - A per-program report the controller rejects no longer breaks the stream: `store_config_status` keeps the device-wide status and drops the program rows (`programs_reported` false) with a warning; before, the agent reconnected, got the configuration again and sent the same report (maintainer m1, adversarial m2). - `json_merge` names only the position when a sidecar does not parse: the serde message could quote a value from the sidecar, which holds managed content such as a pairing value, and that text now reaches the program detail, the controller database and the UI (adversarial m1). The proto comment no longer claims more than the code does. - Outcome lines for failed, conflict and blocked programs are warnings (maintainer n2); removed and inactive stay info. - UI: a long detail wraps instead of being cut to one line (maintainer m5); an unused CSS class removed. - README: the controller migration is one-way; roll back from a database backup (maintainer m4). - Test code names no PR or spec; a stale comment in daemon.rs (maintainer m3, n3; adversarial n2). - `capi_tunnel_closes_when_the_cached_credential_expires` (PR 3b test) failed under parallel load: the cache entry's 300 ms reuse window ran out before the request arrived, so the proxy fetched a new credential (424). The entry is now reusable for 60 s while the credential still expires in 300 ms, which is the deadline the test checks. Local issue 013. Writer tests: `two_programs_planning_the_same_path_both_fail_and_the_rest_is_blocked`, `a_wrongly_typed_sidecar_field_does_not_put_its_value_in_the_detail` (reconciler), `a_rejected_program_report_keeps_the_device_status` (controller service). Agent lib 268, controller 18. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ate-directory stop path tested) - `store_config_status` falls back only when the report fails validation, and then stores the device-wide status and keeps the last accepted program rows and `programs_reported` (new `Database::update_device_config_state`). Before, any database error took the fallback, and the device page then claimed the agent did not report per-program status. Database errors are returned as they are. - Writer test `a_private_directory_that_cannot_be_created_fails_its_owner_and_blocks_the_rest` covers the private-directory stop path of `AttributedPlan::apply`. - Proto comment: details carry error chains of the kind sent in `ConfigStatus.error`. - README (previous commit): what a restart reports (lab S4). Agent lib 269, controller 18. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n rejected reports Delta 2 minor and nit: after a rejected report the device page kept the last accepted rows under the latest device status; rows whose revision differs from the device's now show it. README says what a rejected report keeps and which startup outcomes are logged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
Author
|
Superseded: landed upstream in agentdesktop-dev#146 (per-program status). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on this fork so the diff is this PR only (base:
copilot/pr3c-settings-jsonc). Eighth change in the series; James gets one integration PR fromcopilot/integrationwhen the series is ready. Product-wide: every managedprograms.*key, not only Copilot.Why. The controller kept one configuration status per device (applied or failed, one error string), and one conflicting file blocks every managed file on the device. An admin could not tell which program conflicted, which were blocked by it, or which were configured but not pointed at the gateway because the local LLM proxy was not running.
What it does.
Providergains a requiredid().Reconciler::plan_with_reportplans every provider and keeps planning past a failing one;AttributedPlan::applyapplies with the same all-or-nothing rules and returns the same first error as before (Reconciler::applynow wraps it).ReconcilePlan::applysays where it stopped (conflict, changed file, private directory, failed write), so the failing program isfailed, programs already writtenapplied, the restblocked. A program counts as changed when bytes on disk differ or it recorded a create/update/remove, so a sidecar rewritten with the same bytes does not make an unchanged program look applied. The Copilot CLI and both VS Code files reportinactivewith the reason. Reported programs: configured ones (an exhaustive destructure ofProgramsConfig) and ones with something to clean up, conflict or fail. Details are at most 1024 bytes; a sidecar parse error no longer quotes the file's values. Every apply logs oneprogram configuration outcomeline per program that is not applied or unchanged (warnings for failed, conflict, blocked).ConfigStatus.programs(ProgramStatus { program, state, detail },ProgramState) andConfigStatus.programs_reported. Older controllers ignore them; older agents send neither.device_program_status(with the report position) anddevice_config_status.programs_reported. A report is validated (at most 32 programs, IDs[a-z0-9-]up to 64 bytes, no duplicates, details up to 2048 bytes) and written with the device row in one transaction, so a program missing from a report disappears. A report that fails validation does not break the stream: the device status is stored and the last accepted program rows stay.GET /api/v1/devices/{id}returnsprogramsandprograms_reported; the device list and overview are unchanged.Process. Spec reviewed (Opus, 14 findings) and revised to rev 3 (the test author found attribution needs
Provider::id); baseline tests first (f1e6e15, rebased as 6684bf5: 13 reconciler, 2 remote, 5 controller), unchanged through every revision (the test fake gainedid()). Three review lenses: conformance conforms; maintainer one major (Reconciler::applykept its own loop), adversarial minors (a sidecar value could reach the detail, a rejected report looped the stream, two stop paths untested), all fixed with tests. Delta 1 and delta 2 passed with minors (fallback only for invalid reports and keeping the last accepted rows; private-directory stop path tested; earlier-revision rows marked). The PR 3b testcapi_tunnel_closes_when_the_cached_credential_expiresfailed under parallel load (a 300 ms cache window); fixed without weakening what it checks (ledger). Gate: agent 269, core 18, controller 18, storybook 52, clippy, fmt, schema, frontend.Lab (Linux VM, user mode, controller-pushed config, API and admin UI). First run by an independent agent on v1: the device page lists
claude-code,copilot,vscode; a comment inchatLanguageModels.jsongivesvscodeconflict with the path,claude-codeblocked ("not applied: vscode conflicted"),copilotunchanged, device failed, file untouched; without the proxy listenercopilotandvscodeare inactive with the reason; removingcopilotgivesremoved, then no row; an older agent shows "not reported by this agent version"; a newer agent against an older controller applies without errors (an older controller refuses a database migrated by this one, documented). One checklist expectation was wrong by design (after a restart the report saysunchanged, since the startup apply wrote the files first; documented, local issue 014 for the startup report). Repeat on v3 withlab/checks/pr4-status.sh: 14 of 14. The lab also found that its build script could install a stale binary when worktrees share a cargo target directory; fixed in the lab tooling.Not covered: Postgres (tests and lab ran SQLite; the migration uses the same statements as 0001/0002); a check that no pairing value appears in the stored API responses (the lab agent was not allowed to read the pairing file; to be run by hand); retry after a cleared conflict (the reconcile tick, PR 5).
Follow-ups (local issue tracker): 014 report the startup apply; 002 reconcile tick; 006 sidecar rewritten on every apply.
🤖 Generated with Claude Code