Epic: Repo setup evolution — detect setup drift + propose remedial manifest updates#1792
Open
psdjungpulzze wants to merge 15 commits into
Open
Conversation
…ic 583e469f S1) Foundation for the repo-setup evolution flow (docs/design/repo-setup-evolution.md §5+§6) — the pure, unit-tested server "brain" later tasks wire up. No DB, no HTTP, no LLM call; no writes to any customer repo. - detector-registry.ts: data-driven, deterministic map of repo signals (mix.exs/package.json deps, config env refs) -> required services (redis/postgres/ mysql with default probes), env-required + implied service, and nix.packages for native-lib deps. `detectRequirements(signals)` is pure + order-independent; unknown deps fall through (no requirement) to LLM inference. - manifest-inference.ts: MANIFEST_DELTA_INFERENCE_PROMPT + buildManifestDeltaPrompt + draftManifestDelta — the delta sibling of the full-draft flow (minimal update; only changed sections). A delta that doesn't parse or adds no runnable change is discarded. - manifest-delta.ts: mergeManifestDelta (deterministic section-replace, §5), buildDeterministicDelta (short-circuit a known-service gap without the LLM), buildProposedManifestSummary + defaultProposalReason (display diff — added services/steps/env + reason + source), mirroring manifestToReviewSummary's vocabulary-clean style. 63 unit tests cover each detector mapping + fall-through, the merge/short-circuit/ diff-summary paths, and the delta prompt/parser. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…al store (epic 583e469f S1)
Wires T1's detector registry + delta core into the server: a detected requirement
gap now raises a REVIEWABLE manifest-update proposal on RepoAgentProfile, without
touching the operative setup (design §5/§6/§9).
- schema: four additive, nullable RepoAgentProfile columns — proposedManifest (candidate
body; operative keeps serving until adopted), proposedManifestSummary (computed diff +
reason + source), proposedManifestAt, proposedManifestDismissedAt. Migration is
additive/IF NOT EXISTS — applies cleanly, existing rows load with no backfill.
- repo-readiness: ReadinessReportSchema gains OPTIONAL, bounded detectedRequirements
(discriminated service|env|nix wire shape, structurally === T1's DetectedRequirement).
Additive/back-compat — reports without it still validate. Inferred type exported.
- manifest-proposal.ts: proposeManifestUpdate({connectionId, detectedRequirements}) loads
the operative manifest (accepted-draft rule mirrors the report route), diffs via T1's
deterministic short-circuit (buildDeterministicDelta), section-replace merges the
candidate (mergeManifestDelta), builds the display summary, and stores it profile-level.
No gap / empty delta ⇒ nothing stored. A newer detection supersedes an un-acted proposal;
a fresh proposal clears the prior dismissal. Operative manifest untouched, NO EngineGoal.
- assess/report route: calls proposeManifestUpdate after the profile upsert when the report
carries detectedRequirements — best-effort, so a proposal error never fails a committed
readiness report.
Store unit tests: gap→stored, no-gap→nothing, no-requirements→nothing (+ no profile read),
supersede overwrites + clears dismissal, operative untouched, no goal. Route tests: a
report with detectedRequirements raises a proposal (server-side only, not persisted on the
profile); absent ⇒ none; a proposal failure still 200s.
No agent-config update needed: this is server-internal setup evolution — no new PM-agent
tool or agent-visible domain entity.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e469f S1)
Adopt or drop a stored manifest-update proposal from the connection-scoped setup
dispatcher — the operator's "Use this update" / "Dismiss" on the driver card
(design §9 DECIDED / §10). Both are profile-level (NO EngineGoal) and never write
to the customer repo (the "commit back" PR is S1b, out of scope).
- manifest-proposal.ts: applyProposedManifestUpdate({repo,userId}) resolves the
connection (resolveRepoProposalContext), requires a pending proposedManifest
(else no_pending), and in ONE update makes the candidate the operative MANAGED
manifest for EVERY manifestSource — manifest ← proposedManifest,
manifestSource='manual' (committed is SUPERSEDED, never edited), proposed fields
cleared — then runs accept-then-verify EXACTLY as acceptManifestDraft: credit
testToolingDetected iff a runnable test step, stamp assessRequestedAt, set
preflightStatus pending/blocked_no_compute by hasIdleCompute over the user's
daemon machines, clear assessClaimedAt on the compute path. No goal status touched.
- dismissProposedManifestUpdate stamps proposedManifestDismissedAt + clears the
candidate; operative manifest/source untouched.
- test-mechanism-actions.ts: wire 'apply-update' / 'dismiss-update' into
TEST_MECHANISM_ACTIONS + dispatchTestMechanismAction (reusing errorFor), flowing
through the existing POST /connected-repos/[connectionId]/test-mechanism route
with no route change.
Tests: apply for each source (generated/manual/none/committed → operative managed +
verify requested; committed→manual; no goal); no-test-step, blocked_no_compute,
no_pending, forbidden gate; dismiss clears candidate + stamps dismissedAt. Route
test for both new actions incl. 409 no_pending.
No agent-config update needed: server-internal setup evolution, no new PM-agent
tool or agent-visible domain entity.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…elta card + manual check-for-updates (epic 583e469f S1) Closes the operator-facing end-to-end path for the setup-evolution flow: a detected requirement gap (raised as a profile-level proposal by T2/T3) now surfaces on the Fleet-setup driver as a reviewable DELTA the operator adopts or dismisses, plus a manual "check for updates" affordance that exercises the whole detect→diff→propose→review path (design §3, §10, §11, §12 S1). - driver (repo-readiness-driver.ts): new 'A setup update is proposed' state. ReadinessDriverSignals gains `updateProposed`; a new `apply-update` ReadinessActionKind + "Use this update" label drive the inline card (Dismiss is the secondary, rendered by the panel). Precedence surfaces the proposal prominently (right after the two in-flight checks) BUT never flips `ready` — a proposal is a candidate; the operative setup keeps serving. The pending-assess check still wins, so the accept-then-verify window after "Use this update" truthfully shows "Checking…". Vocabulary-clean (no manifest/assess/…). - setup-status route: loads proposedManifest*/DismissedAt in the profile select, derives updateProposed (non-dismissed pending proposal) into the signals, and includes a `proposal` block (stored diff summary: added services/steps/env + reason, plus the full proposed YAML for "View details"). - panel (readiness-panel.tsx): renders the review-then-use DELTA card off `apply-update` — Why: <reason>, the "+ Services/Environment/<phase>" delta, primary "Use this update" → POST apply-update, secondary "Dismiss" → POST dismiss-update, and a "View details" disclosure with the full proposed body (parity with the machines-page review). Reuses the inline-review card styling; "Use this update" flows through the existing poll-until-settled loop (accept-then-verify). Adds a manual "Check for updates" control that POSTs a fresh assess/detect (the sole S1 trigger — no automatic detection yet). - tests: driver new state + precedence (proposal alongside ready/failed base states; in-flight beats proposal; ready not flipped); setup-status route (proposal block + updateProposed + dismissed-guard); panel (delta card, View details, apply/dismiss/check-for-updates wiring). No PM-agent config update required: this is operator-facing setup infrastructure (RepoAgentProfile-level, connection-scoped), not an end-user PM entity/tool or a new agent-visible workflow — nothing in src/server/agents/configs surfaces it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-s1-proposal-surface-incremental-inferen Repo setup evolution S1 — proposal surface + incremental inference + detector registry
…3e469f S3)
S3 (§7B/§12): a pure server-side hook in acceptGoal — when a prBoundary goal
merges and its PR touched setup-relevant files, request a fresh detect+assess for
the affected connection so S1's report handler diffs detectedRequirements vs the
operative manifest and raises a proposal. S3 only REQUESTS the refreshed assess;
it never computes the diff or raises the proposal (S1's contract).
Reusable seams (same PR):
- Extract fetchPrFiles(ref, prNumber) into src/lib/engine/pr-files.ts; goal-
supersession.ts now reuses it.
- Extract the assess-request DB logic (writeCapable probe + hasIdleCompute + upsert
/clear-lease) into requestConnectionAssess(connectionId, {requestedByUserId}); the
assess route calls it too (no HTTP behavior change).
- resolveConnectionGitHub(connectionId) — per-connection GitHub read-token resolver
(integration PAT -> connectedByUser fallback), so fetchPrFiles uses the correct
repo's owner/repo+token per target.
- isSetupRelevantPath / anySetupRelevantPath — pure, data-defined matcher over
mix.exs/mix.lock/config/*.exs/package.json/lockfiles/*.nix/Dockerfile. Extensible.
Trigger (maybeRequestSetupDriftAssess): iterates the goal's repo-targets keyed by
connectionId via listGoalRepos (N-repo-ready; falls back to the singular goal.prNumber
+ primary connection until the multi-repo EngineGoalRepo junction lands in S5); for each
merged target it debounces per connection (coalesces when pending or requested within a
short window), fetches PR files, runs the matcher, and requests the assess. Never throws;
logs and swallows all errors. Wired into acceptGoal as a best-effort void call after the
post-merge publishEngineChanged on both prBoundary success paths (main merge + already-
merged reconcile), never affecting acceptGoal's return/throw.
Tests: path matcher (positive/negative/glob), multi-repo iteration, singular fallback,
debounce coalescing, and no-op/safety cases (non-prBoundary, no setup-relevant change,
no token, never-throws).
No PM-agent config change: this is a backend engine trigger with no new user-facing
tool/entity/workflow, so no TOOL_NAMES or specialist-prompt update is required (per
CLAUDE.md's agent-maintenance rule).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(epic 583e469f S2a)
Server-side S2a: the wire contract + failure-classification reuse for the daemon's
structured setup-failure signal.
- Extend ReadinessReportSchema (src/lib/agent/repo-readiness.ts) with an OPTIONAL,
bounded, STRUCTURED `setupFailure` discriminated union over kinds: service
{host,port}, tool {bin}, env {name}. Unknown kinds rejected, strings capped, port
range-bounded — a report omitting it validates exactly as today. Rides the
connection-scoped assess/report flow (co-located with S1's detectedRequirements).
Add the SetupFailure type export.
- Extract a shared classifySetupFailure helper co-located with classifyReportedFailure
(src/lib/engine/pause.ts) that maps the three high-precision signatures (connection
refused:<host:port> → service; command not found:<bin> → tool; missing env <NAME> →
env) into the structured signal + a legible reason. A setup failure classifies as the
existing retryable `infra` class, NOT a new RunFailureClass. High-precision only — no
fuzzy substring inference.
- On the failed-run escalation path (runs/[runId]/report/route.ts), derive the signal
from body.error and stamp a bounded setupFailure + setupFailureReason marker on
EnginePause.detail (mirrors the attachmentCredsFailure precedent) so /attention
renders a human cause — no raw logs persisted.
Tests: schema accepts each kind / rejects unknown-kind + oversized / validates when
omitted; the shared classifier + reason helper; the EnginePause.detail marker.
No agent-config update needed: this is an internal daemon↔server wire contract +
failure-classification seam — no user-facing tool or agent-visible entity changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…3e469f S2b)
Bind the daemon's structured setupFailure signal (S2a) to S1's proposal surface:
a failed run's setup signature becomes a reviewable, failure-sourced manifest-update
proposal on RepoAgentProfile — no LLM, no touch to the operative setup (design §6/§9,
spec §13.3/§13.4).
- detector-registry: add knownServiceForPort(port) — the reverse of SERVICE_PROBE
(6379→redis, 5432→postgres, 3306→mysql) with the registry's canonical probe, derived
from SERVICE_PROBE so the port↔service mapping has one source of truth. Powers the
deterministic service short-circuit; an unknown port names nothing → server no-op.
- setup-failure-proposal.ts: setupFailureRequirements maps the signal into a
DetectedRequirement in the SAME shape the registry mints — service {host,port} →
services: delta via the known-service short-circuit; tool {bin} → nix.packages delta;
env {name} → env-required delta; an unnameable service port → [] (falls through to the
daemon-side inference, so a server no-op). setupFailureProposalReason gives a
plain-language cause ("A run failed connecting to redis."). proposeSetupUpdateFromFailure
hands the requirements to proposeManifestUpdate with source: 'failure' + the reason,
reusing all of S1's parseManifest/hasRunnableSteps discard discipline.
- manifest-proposal: debounce (spec §13.4) — one proposedManifest per connection; when the
identical candidate is already the pending proposal, skip the write (new 'unchanged' skip
reason) so a repeated identical signal doesn't churn proposedManifestAt or re-notify. A
different candidate still supersedes; apply/dismiss null the candidate so a recurrence
re-raises.
- assess/report route: after the profile upsert, when the report carries setupFailure, call
proposeSetupUpdateFromFailure — best-effort + side-channel, mirroring detectedRequirements
(a proposal error never fails a committed readiness report). Behind the field: with no
daemon emitting setupFailure the branch never runs — a pure no-op; run-report unchanged.
Tests: signal→delta per kind incl. the known-port service short-circuit + unknown-port
no-op, source/reason stamping, debounce + supersede, and the no-setupFailure no-op /
schema-rejected kind at the route. Full suite + typecheck green.
No agent-config update needed: internal daemon↔server setup-evolution seam — no PM-agent
tool or agent-visible entity changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ION note (epic 583e469f)
Docs / ship-tracking only — no daemon code ships from this repo (thin-daemon /
fat-server; the daemon source is out-of-repo). Captures the GATED, hand-driven
fleet rollout that activates the S2 failure-driven trigger end to end.
- docs/design/repo-setup-evolution-s2-daemon-ship-runbook.md: the runbook —
(1) the exact setupFailure wire contract the daemon must emit (the S2a tagged
union: service {host,port} / tool {bin} / env {name}), high-precision only,
structured signal never raw logs, with the three failure-tail signatures and
the two transports (structured proposal via assess/report vs server-derived
/attention marker via run-report); (2) daemon image change + smoke against a
controlled failing run per signature, incl. a negative case; (3) set
PM_MIN_DAEMON_VERSION to the new floor AFTER rolling the image (deploy-time env
var read by daemon-version.ts, NOT a code constant); (4) note the server side
(S2a+S2b) is already live behind the optional field, so the daemon rollout is
independent and the last step to activate the trigger. Plus rollback.
- .env.example: refresh the PM_MIN_DAEMON_VERSION note to point at the runbook and
the roll-image-then-raise-floor ordering.
No pm-agent config / TOOL_NAMES change: S2 adds no agent-visible tool or entity —
setupFailure is an internal daemon<->server wire field and the proposal reuses the
S1 RepoAgentProfile surface. Docs-only slice; nothing agent-visible changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…9f S2c) Review rework: the runbook existed but was not discoverable from the design doc. Add a link from §7A (Failure-driven trigger, daemon-dependency bullet) and §12's S2 slice bullet to docs/design/repo-setup-evolution-s2-daemon-ship-runbook.md so a reader on the design doc can find the gated hand-driven rollout runbook. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-s3-goal-delivery-diff-trigger-acceptgoa Repo setup evolution S3 — goal-delivery diff trigger (acceptGoal hook)
…-s2-failure-driven-trigger-daemon-setupf Repo setup evolution S2 — failure-driven trigger (daemon setupFailure signal)
…epic 583e469f S4) Reach half of S4: make a pending manifest-update proposal (RepoAgentProfile.proposedManifest*, raised by S1) reach the operator through the project /attention feed, deep-linking the Fleet-setup driver. - attention.ts: new `AttentionSetupProposedItem` union member (link-only, like goal_ready); shared `SETUP_PROPOSED_SELECT` + `listSetupProposals` single source (proposedManifest set, un-dismissed, connection.projectId == project); `coerceProposedSummary` for the stored JSON; owner ladder gains `connectionOwnerId` (= connection.connectedByUserId) so the badge bumps only for the connection owner. - Materialized in listAttentionItems (project-scoped, skipped for a taskId) and in every parallel surface — v2 pauses route, SSR seed (load-attention), and countMyAttentionItems — so the CLI list, web page, and badge can't disagree. - Web: SetupProposedCard renders "A setup update is proposed", the reason + added services, deep-linking to /settings/integrations anchored to the connection; wired through attention-client + approvals-client. - Digest helpers (ATTENTION_LABELS / title / displayNumber / href) handle the new type. - Tests: materializing query (pending shows / absent nothing / dismissed+adopted hidden at the source), owner resolution + badge parity, summary coercion, web card render+href. No agent-config change: this only READS existing profile data into an existing feed — no new tool, entity, or agent-invoked workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…83e469f S4) Raise a per-user notification-bell alert to a connection's OWNER when a manifest-update proposal is written — "<repo>'s setup needs an update: add redis" — instead of relying only on the shared profile-level proposed-manifest card. Mirrors the owner-only offline-fleet signal in engine/starvation.ts. - Add `SETUP_UPDATE_PROPOSED_NOTIFICATION_TYPE = "setup_update_proposed"`, deliberately kept OUT of TYPE_TO_PREF_KEY so it is never preference-suppressed (exactly as fleet_offline_starvation). - Hook the single trigger-agnostic site: proposeManifestUpdate's proposed-write branch, so both S2 (failure) and S3 (dep-change) triggers converge for free. Owner = IntegrationConnection.connectedByUserId (null → skip; no goal for a profile-level proposal). Message built from proposedManifestSummary.added + the repo label; actionUrl = integration settings; resourceId = connectionId. - Collapse N→1 PER CONNECTION (dedup on userId + type + resourceId): starvation- style findFirst → refresh-in-place if unread (no re-push), re-open if read (clear readAt, bump createdAt, re-publish), else create. Written directly via db.notification for collapse + suppression-bypass + manual realtime publish. - Resolve-on-adopt/dismiss: conditional updateMany(readAt:null) + publish notification.read so the bell clears without a push and a concurrent manual read wins. Tests: create / collapse-in-place / re-open / resolve-on-adopt / resolve-on- dismiss, owner = connectedByUserId, and absence from TYPE_TO_PREF_KEY. No agent-config update needed: no src/server/agents/configs/* prompt enumerates notification types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-s4-reach-attention-item-owner-notificat Repo setup evolution S4 — reach (attention item + owner notification)
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.
No description provided.