-
…c231-main-checkout-drift
-
Three real self-hosting fixes (log content never written, skill detail page missing real content, artifact list not clickable), two design questions set aside per user's request (unified Planner-driven work graph across all org agents, loop visualization on the graph), three bugs left for a future batch, and two infrastructure bugs found and fixed along the way (manual_resume not actually continuing execution for most block reasons, run_goal_to_completion crashing instead of blocking on unrouted exceptions). Also upgrades the RollbackPathBFailureTest flakiness note -- three independent real sessions have now hit it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
…1a79-main-checkout-drift
-
…ea4d-main-checkout-drift
-
…d9ae-main-checkout-drift
-
…0a72-main-checkout-drift
-
…2b85-main-checkout-drift
-
…f2cf-main-checkout-drift
-
Second real self-fix (after order 2) -- 12 skills migrated verbatim from ~/.hermes/profiles/, Validator caught and redacted a leaked Feishu API token in 3 of them via byte-diff against source. Also records two operational lessons from actually running it (a real network blip, and my own mistake killing a daemon thread by running it from a one-shot script) and a flaky supervisor test failure that didn't reproduce on independent recheck. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Unlike order 1 (fixed by editing code directly), this one was resolved by actually running graphlemon's own Planner->Coder->Validator loop against its own repo (task 43dc7333, see 3bebe3b onward) -- the first real self-fix demonstration this doc's task list produced. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
check_canary_task() creates a real project + task through the candidate's own HTTP API to verify the write path works, but only ever cleaned up its throwaway git directory -- the DB rows it created via the API were never removed. Every real health check permanently grew whatever DB the candidate was using by one dead project (pointing at an already-deleted temp dir) and one task stuck at 'queued' forever. Found by inspecting the real ~/.graphlemon/graphlemon.db, which had 5 such leaked 'deploy-canary' projects from earlier real health-check runs (now archived by hand). Fix: archive the created project in a finally block that covers every return path, including task-creation/verification failures where only the project (not the task) got created. Best-effort -- the archive call failing must not override the real check result. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
New target projects never got .claude/agents/*.md deployed, so their first Planner call always failed immediately (claude --agent planner finds nothing in cwd) -- self-target usage never noticed since those files are already committed as part of this repo. create_project() now deploys and commits them alongside the existing .graphlemon/ init commit. Also: unroutable execution failures were recorded as a bare blocked_reason='execution_error' with no way to see what actually broke. scheduler._block_task()/task_execution._mark_blocked() now persist the real exception message into execution_event.payload (blocked_reason itself is unchanged -- budget_guardrail matches its exact value elsewhere). Found via a real console Start-button failure on a freshly created project; see docs/implementation-gap-analysis.md for the full writeup. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Users had no way to actually start a queued goal from the console -- task.status just sits at 'queued' forever. Traced the root cause through 03-C: c_task_create's own contract ("若 max_concurrent_goals 已达上限, 任务落 queued 但不触发调度") implies task creation auto-triggers scheduling otherwise, and 03-C/04-P repeatedly assume an always-on Org Graph scheduler that watches the queue -- src/scheduler.py builds the mechanism but nothing ever spins it up as a running process, so no "start" button was ever designed because none was supposed to be needed. Design (confirmed with user, both recommended options chosen): - Pause/Stop are a soft stop, not a real subprocess kill -- the current in-flight `claude` call finishes naturally, only the next phase is withheld. Hard-killing would need Popen + PID tracking, out of scope. - No new task.status enum value -- reuses blocked_pending_human with blocked_reason='user_paused'/'user_stopped', riding the existing manual_resume mechanism instead of a schema change. New src/task_execution.py: run_or_resume_task_lifecycle() resumes from whichever phase actually hasn't run yet (queued/planning_brd_prd_sdd/ coding), start_task_execution_in_background() runs it to completion on its own daemon thread + sqlite connection, request_pause() blocks an in-flight task. Extended /route's manual_resume handling so resuming from a user-initiated pause/stop actually re-continues execution in the background, not just flips the status field back. Real gap found while building this: backend_server.create_server() never stored db_path on the server object -- needed for the new background thread to open its own connection to the same db. 22 new tests (14 src/test_task_execution.py + 8 TaskExecutionRouteTest in test_backend_server.py, HTTP wiring mocked around the already-unit-tested background thread mechanics). Verified against a real running server via curl: /execute 409s a non-queued task, /pause 409s a queued task and a double-pause, succeeds on a coding task. src/ suite: 496 tests, all green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> -
Two new console pages requested directly by the user, beyond 02-V's original declared non-goal ("不做完整多项目管理 UI(项目列表/切换)"): - v_project_list: full project list + rename + archive/reactivate. "Import an existing directory with real code" needs no new code -- project_scaffold.create_project() already only ever adds .graphlemon/ inside the target folder. New: src/project_query.py (list/get/update), GET/PUT/DELETE /api/projects[/:id]. DELETE archives (status='archived'), never a physical row delete -- project_id is FK-referenced from task/ artifact/bug_record/etc., and 01-M's own active/archived enum already models this as soft-delete. - v_goal_list: extends the existing v_task_board/v_task_detail rather than duplicating them. Added sort/order to task_query.list_tasks() (column whitelist, no injection surface) and edit/delete on v_task_detail, both restricted to status='queued' (task_query.TaskNotEditableError, HTTP 409) -- editing or deleting a goal after real work has referenced it would leave that history pointing at something that no longer matches reality. Implemented directly rather than through graphlemon's own self-hosting loop -- confirmed with the user first that implement_code() always uses a hardcoded single-node manifest (no plan_work_graph() call for Coder), so a multi-page/multi-endpoint feature like this would've been one unreviewable Coder session with no way to decompose it. Real bug found on first verification pass (not caught by the existing _insert_task test helper, which never writes execution_event): a real task_create.create_task() always writes an execution_event row in the same transaction (01-M's FK, no ON DELETE CASCADE) -- even a never-touched queued task already has one, so DELETE FROM task raised sqlite3.IntegrityError. Fixed by clearing the task's own execution_event rows first; new regression test goes through the real create_task() path. 474 tests green. Verified end-to-end against a real running server (every new endpoint exercised via curl) since the Chrome extension is still unavailable this session. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> -
Attempt 13b was the first real run to get all the way through a real cutover -- task lifecycle, classification, staging, health check (fixed last commit), and a genuine blue-green cutover all succeeded for real. It correctly landed in 'monitor' and then correctly couldn't finish: the real production exit condition (deploy_monitor.MONITORING_WINDOW_HOURS=24h OR MONITORING_MIN_TASK_COUNT=10 real tasks in the monitored project, NOT the disposable per-cycle canary project) isn't practical to wait out in an interactive session. This isn't a bug -- those are sensible real defaults -- it's a testability gap: run_self_hosting_closed_loop already exposes monitor_timeout_cycles as an override knob for exactly this kind of situation, but never threaded the two constants that actually gate "monitoring_window_clean" through to run_monitor_cycle. Added monitor_window_hours/monitor_min_task_count params, defaulting to deploy_monitor's real constants (production behavior unchanged), threaded through to the actual run_monitor_cycle call. New assertion locks in that overrides actually reach it. Also documents the full run of 9 real bugs found and fixed across 13 attempts in development-plan.md's U-042 note. 564 tests green (441 src + 123 supervisor). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Attempts 7 and 11 both failed at exactly the same spot: process_alive read 'ok' within milliseconds of Popen, then http_health hit ECONNREFUSED in the same second and gave up. Real cause: process_alive only confirms the process EXISTS, not that backend_server.py has finished importing modules and actually bound its listen socket -- a real, non-zero startup gap. The old check_http_health did one urlopen(timeout=X) call; that timeout only bounds how long to wait for a response once connected, not how long to wait for the port to start accepting connections at all -- ECONNREFUSED fires essentially instantly when nothing's listening yet, regardless of timeout. This touches an explicit 03-C design statement ("不重试——重试属于给坏版本 第二次机会"), so confirmed with the user before changing it rather than just reinterpreting the policy unilaterally. Resolution: tolerating normal startup latency within ONE health-check pass isn't "retrying a failed check" -- check_process_alive already does exactly this (polls up to 30s for the pid to exist). http_health now does the same, polling within its own timeout for connection-refused-class errors specifically. A genuine non-2xx HTTP response is still a definitive answer from a live server and still fails immediately, no polling -- that boundary is unchanged. Two new real regression tests: a real server that starts listening 0.3s late is correctly waited for and passes; a real 500 response still fails immediately without being mistaken for "not up yet". 564 tests green (441 src + 123 supervisor). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> -
…be04-main-checkout-drift
-
Attempt 10 crashed immediately in the fix committed last: git add -A -- . ':!.graphlemon-worktrees' throws "paths are ignored by one of your .gitignore files" (exit 1, not just advisory text -- confirmed even with advice.addIgnoredFile=false suppressed) the moment that path is ALSO gitignored, which it now always is. Reproduced locally. Plain `git add -A` (no explicit pathspec) already skips gitignored paths correctly on its own -- the explicit exclude was redundant now that .gitignore covers it, and actively conflicting. Simplified ensure_worktree_committed() back down. Also added the same .gitignore entry to test_work_node_run.py's _init_git_repo() fixture so real-worktree tests match production config instead of coincidentally not hitting this. 562 tests green (441 src + 121 supervisor). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Attempts 8 and 9 both hit "local changes would be overwritten by merge" again, despite the earlier _check_prd_consistency worktree-isolation fix. Root cause is deeper: git worktrees deliberately SHARE project identity with their main checkout (same .git, just another checkout) -- a real `claude -p` call's memory:project write doesn't reliably respect the cwd it was invoked with, and can land directly on the main checkout regardless of which specific call triggered it. Worktree-wrapping one call (as done for _check_prd_consistency) only moves around which call happens to trigger this; it doesn't fix the underlying issue. ensure_worktree_committed() now also runs against repo_path itself (not just the worktree) right before every merge_worktree() call, in both work_node_run.run_work_node() and e2e_pipeline._check_prd_consistency(). Same already-tested mechanism, now applied generically regardless of which real call caused the drift or why. Fixing this surfaced a second real bug in the fix itself: running `git add -A` against repo_path while a node's own .graphlemon-worktrees/<id>/ checkout is still present on disk picks it up as a gitlink-like entry, which then shows as "deleted" the instant remove_worktree() runs right after. Fixed by excluding .graphlemon-worktrees/ from both the dirty-check and the add via pathspec (not relying on the caller repo's own .gitignore), plus added it to graphlemon's own .gitignore for correctness generally. New regression test: test_drift_on_main_checkout_is_auto_committed_before_merge reproduces a run_fn writing directly to repo_path instead of its worktree, and confirms a second node's own merge isn't affected by it. 562 tests green (441 src + 121 supervisor). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
The 7th real self-hosting attempt was the first to actually reach a real health check against a real candidate process -- it correctly failed and triggered rollback_path_a for real: candidate terminated, git revert landed cleanly. But the candidate's `git worktree add --detach` checkout (candidate_instance_dir, tracked since U-037 specifically to know where this lives) was never removed -- left permanently on disk and still registered in `git worktree list`. A confirmed-bad candidate is never needed again after path A, unlike a successful cutover's candidate (which correctly becomes previous_instance_dir, kept until the monitoring window closes) -- these are different lifecycles that shouldn't share the same "leave it" behavior. rollback_path_a gains an optional candidate_instance_dir parameter (default None, existing tests unaffected) and removes the worktree before git revert when provided. self_hosting_e2e.py now threads through the value stage_deployment() already returns. New regression test uses a real `git worktree add --detach` checkout and confirms both the directory and its git worktree registration are gone after rollback. Also cleaned up 3 orphaned processes left by earlier test_deploy_rollback.py runs (backing temp dirs already gone) and the real leftover candidate worktree from this run. 561 tests green (440 src + 121 supervisor). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
The 6th real self-hosting attempt (first to reach the full 4-node doc-writing graph after the previous five bugs) hit a genuinely new failure: this real graph_manifest had no edges at all, not just wrong field names -- worse than the case _normalize_graph_manifest already handled. With zero dependency info to normalize from, topological_order() scheduled 'benchmark' before 'sdd' (alphabetically first among zero-in-degree nodes), and build_prompt() crashed with KeyError: 'sdd_content' trying to format a template needing sdd's not-yet-produced output. _PROMPT_TEMPLATE always asks for exactly the same fixed 4-node linear chain -- that's a constant architectural fact, not something that should be re-inferred from the LLM's output every call. _normalize_graph_manifest now deterministically enforces brd->prd->sdd->benchmark whenever all four canonical artifact_types are present, regardless of what depends_on/edges the LLM did or didn't provide. Same "机制用代码" principle as ensure_worktree_committed and the earlier normalize logic. Three new regression tests: zero dependency info still gets the chain enforced, node_id != artifact_type still resolves to the real node_id (not a guessed string), and a partial set (missing one of the four types) is correctly left alone rather than guessed at. 560 tests green (440 src + 120 supervisor). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
The 4th real self-hosting attempt (first to reach the actual doc-writing Work Graph after the previous three bugs) hit a real git merge failure: "local changes would be overwritten by merge", exit code 2. Root cause: _check_prd_consistency() calls execute_work_graph() directly against target_folder_path, completely bypassing work_node_run.run_work_node's worktree isolation. It uses agentType='planner' with memory:project, which writes a real update to .claude/agent-memory/planner/MEMORY.md in whatever cwd it runs with. Every prior real run of this check only ever targeted a throwaway clone, so this never mattered -- until self-targeting, where cwd IS the main checkout: the memory write landed as an uncommitted local change on master, which then blocked the SDD node's own (correctly isolated) worktree merge right after it. Fix: apply 05 §5.8's same unconditional self-target worktree rule here, reusing work_node_run's existing create_worktree/ensure_worktree_committed/ merge_worktree/remove_worktree rather than inventing a new mechanism. Non-self-target calls are unaffected (verified by both regression tests). Also cleaned up the real, now-recovered state from the failed 4th attempt: merged a real SDD-node worktree branch that got stranded uncommitted (conflict was just two near-identical memory file wordings, resolved by hand), and removed three orphaned worktrees left by earlier test runs. 557 tests green (437 src + 120 supervisor). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
The third real self-hosting attempt (first to actually reach stage_deployment itself, past the earlier bootstrap/commit bugs) hit an uncaught FileNotFoundError: _copy_prod_db() assumed prod_db_path always exists and shutil.copy2()'d it unconditionally. graphlemon has never actually run as a persistent service, so ~/.graphlemon/graphlemon.db never existed. stage_deployment() already handles this exact class of "no prior state" bootstrap case for previous_commit_hash/previous_instance_dir -- prod_db_path never got the same treatment, and every existing test seeds a prod db in setUp so this was never exercised. Fix: when prod_db_path doesn't exist, create a fresh schema'd db instead of copying -- semantically correct, since a genuinely-first deployment has no production data to inherit. New regression test (BootstrapProdDbTest) covers it. 555 tests green (435 src + 120 supervisor). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
A real self-hosting run (U-042, first real exercise of needs_worktree=True since U-035 forces it unconditionally for self-target) exposed a real bug RealGitWorktreeIsolationTest never caught: its own run_fn always committed its edits, but a real Coder isn't instructed anywhere to git commit its own changes. The Coder genuinely edited src/org_loop_route.py and verified it in its own test_report -- but never committed, so merge_worktree()'s `git merge` was a true no-op ("Already up to date"), and remove_worktree() then silently discarded the dirty worktree along with the real work. code_diff came out empty; the change never reached the branch at all. Adds ensure_worktree_committed(): checks `git status --porcelain` in the worktree before merging and auto-commits any dirty state found. A no-op when the worktree is legitimately clean (e.g. a pure investigation task). Same "判断用 Agent,机制用代码" principle already used elsewhere in this codebase -- don't rely on persona prompt-engineering for a mechanical correctness guarantee that the system itself should hold. Two new regression tests: one reproducing the exact "edited but never committed" scenario, one confirming a legitimately-clean worktree still merges fine. 554 tests green (435 src + 119 supervisor). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Adds src/deployment_query.py (list_deployments/get_current_deployment/ get_deployment -- no project_id filter needed, deployment has no project_id column since there's exactly one graphlemon self-target) + three new backend_server.py routes (GET /api/deployments, /current, /:id, the last with recent deploy_health_signal history) + a new v_deployment_status page in frontend/app.js polling every 5s (deployment.status changes don't go through c_ws_subscribe -- Deploy Supervisor writes deployment/ deploy_health_signal directly, no execution_event of its own). No 02-V spec exists for this view (PH5 was added after 02-V was written) -- DOM/class conventions follow the existing pages' own patterns. Verified against a real running server via urllib (browser extension unavailable this session), plus 9 deployment_query unit tests and 5 real HTTP route tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Adds supervisor/self_hosting_e2e.py: run_self_hosting_closed_loop() chains every PH5 module built this session (U-034~U-041) with the Graph Engine's own scheduler (U-032) into one real, callable path matching self-development- feasibility.md's 9-step scenario -- self-target Goal -> Planner/Coder/ Validator -> core_engine classification -> stage -> health check -> cutover (or rollback path A if health check fails) -> monitor until either monitoring_window_clean (finalized) or anomaly_detected (rollback path B). Both real outcome paths are branches of the same call, not two separate scripted scenarios. Explicitly documented as this acceptance scenario's own orchestration glue, not a claim about Deploy Supervisor's eventual production poll loop (U-036's discovery-loop hook is still unfilled; building that is future work). Mocked wiring tests (6, run in normal regression) stub each already independently-tested module boundary and only verify the branching logic. The real, literal acceptance test is built and ready (RealSelfHostingClosedLoopTest, reproduces the document's own "loop_round > 3 should be >= 3" example) but gated behind RUN_REAL_SELF_HOSTING_E2E=1 -- deliberately a separate, distinctly-named env var from this project's usual RUN_REAL_AGENT_E2E, since this is the highest-blast-radius action in the project (can replace the running graphlemon process and git-revert commits on this real repo). Per this Unit's own explicit build instruction, not executed as part of implementing it -- doing so requires checking with the user first. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Adds supervisor/deploy_rollback.py: rollback_path_a (pre-cutover, health check failed -- terminate candidate, delete its one-time DB copy, git revert, record a bug_record(core_engine) audit trail) and rollback_path_b (post-cutover, anomaly detected -- stop the bad Live, revive previous_instance_dir, re-run the same three health-check signals directly against it, git revert only once re-verification passes). Design choices follow established conventions rather than inventing new ones: no unified rollback() entry point (deployment has no PID columns, same reason deploy_stage/deploy_cutover already require callers to pass pids explicitly); ROLLBACK_FAILED is the raised error name while the persisted deployment.status is 'blocked_pending_human' (same pattern CheckoutFailedError/CutoverFailedError already use). git revert conflicts fold into the same rollback-itself-failed fallback both paths share, per design doc §10 open question 3's own recommendation, with `git revert --abort` run afterward so a failed attempt doesn't leave the repo mid-conflict. This is PH5's highest-rework-risk Unit per development-plan.md's own flag, so its own "rollback itself fails" fallback is tested for real, not assumed: a genuinely broken previous_instance_dir (a real empty directory with no Python package in it) that really fails to bind its port, and a genuinely conflicting git history (a later commit touching the same line the self-modification changed) that really fails `git revert` -- both correctly land on blocked_pending_human without retrying forever or silently succeeding, and the conflict case leaves the repo clean afterward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Adds supervisor/deploy_monitor.py: run_monitor_cycle() is one sampling pass, called repeatedly by Deploy Supervisor's own poll loop (03-C's own text: not a one-shot call). process_alive/http_health/canary_task/ validator_approval_rate sampled in order, stopping at the first anomaly. validator_approval_rate's "approve rate" has no literal definition anywhere in 01-M/03-C/04-P (no such execution_event type exists) -- derived instead from a verified structural fact in scheduler.finish_task_lifecycle: PASS closes straight to done with no bug_record ever created, FAIL/INCONCLUSIVE always creates one via judge_root_cause. A task counts as approved iff no bug_record(source='validator') exists for it. Baseline is computed by the same function looking back MONITORING_WINDOW_HOURS from cutover_at rather than persisted as a new column; baseline=None (cold start) never flags an anomaly rather than defaulting to 0. Crash-count/consecutive-http-fail windows are approximated by sample count (N = CRASH_WINDOW / MONITOR_SAMPLE_INTERVAL) rather than a true rolling time window -- documented simplification, correct as long as sampling stays roughly interval-paced. Caught my own design misreading via the tests: process_alive's monitoring- phase anomaly condition is MAX_CRASH_COUNT crashes within CRASH_WINDOW (a crash-loop detector), not "currently not alive" -- unlike U-038's health check where any single failure fails closed. A single dead reading during monitoring is recorded but must not trigger an immediate rollback by itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Adds supervisor/deploy_cutover.py: stops the old Live process, retires the health-check-mode candidate, and relaunches the same already-verified candidate checkout bound to LIVE_PORT/PROD_DB_PATH -- http.server has no live socket-rebind operation, so "switch to production port/db" is implemented as stop-then-relaunch-from-the-same-dir rather than an in-place rebind (functionally equivalent, the only mechanism stdlib actually offers). Adds LIVE_PORT=8420 to 05 §5.9's MVP-fixed parameter table -- the one parameter that table was missing (frontend/app.js never needed one, same- origin relative fetches don't care, but successive deployments need to keep landing on the same port for a human bookmark to keep working). "在途 work_node_execution 重启后可恢复" is satisfied by relying on the existing REQ-06 recovery machinery unchanged -- cutover doesn't add any special-case logic, a cutover-triggered restart is just another interruption REQ-06 already survives. A real (not mocked) test caught a real bug: deploy_supervisor.is_alive()'s os.kill(pid, 0) check can't distinguish "truly running" from "exited but never reaped by its own parent" -- a zombie's PID slot answers kill(pid, 0) successfully until waited on. U-036's own subprocess test never caught this because that skeleton deletes its own PID file before exiting, sidestepping is_alive() entirely; deploy_cutover's test is the first one to actually poll for a stopped process to disappear, and it hung until fixed. Added reap_if_exited() (real os.waitpid for processes we spawned ourselves, falls back to is_alive() for pids from a prior session where ChildProcessError means we're not the real parent) and switched deploy_cutover._stop_process, deploy_health_check.check_process_alive, and deploy_supervisor.stop() to use it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Adds supervisor/deploy_health_check.py: run_health_check() runs the three pre-cutover checks in order, stopping at the first failure (03-C: "任一检查项 fail 立即整体判定 health_check_failed,不等待其余检查项跑完") and persisting a deploy_health_signal row for every check that actually ran. - process_alive: polls deploy_supervisor.is_alive() up to HEALTH_CHECK_STARTUP_TIMEOUT. - http_health: hits the candidate's /api/health -- added to backend_server.py (U-037 deliberately left this for U-038). Not just "process exists" -- runs a real SELECT 1 to catch a wedged sqlite connection a live-but-stuck process would otherwise pass. - canary_task: a real (not mocked) project+task creation round-trip against the candidate over HTTP -- no real Org Graph/claude calls, since c_task_create's synchronous 'queued' landing is already a deterministic, always-should-pass result, which is what a canary needs. Timeouts are the MVP-fixed drafts from 05 §5.9, explicitly flagged there as needing real calibration later, not hardcoded as authoritative. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Adds supervisor/deploy_stage.py: stage_deployment() creates a deployment row, git-worktree-checks-out the target commit into a fresh directory (--detach, never in-place git pull), copies the production DB to a one-time candidate copy, and Popens a real candidate backend process on a free port. Return value follows 04-P §4.6's state-machine table (status after the call is 'candidate_starting') rather than 03-C's one-line 'staged' summary -- 04-P's own trigger text ("c_deploy_supervisor_stage 完成" moves the row to candidate_starting) is the more detailed/authoritative source when the two disagree, same precedent used throughout this project. Found and closed two real gaps along the way: - deployment's own field list never included where a candidate's checkout directory lives -- added candidate_instance_dir to schema.py (also what the NEXT staging call's previous_instance_dir bootstraps from once a deployment reaches 'finalized'). - backend_server.py had no CLI entry point at all since U-007 -- create_server() was only ever instantiated in-process by tests. Added main() (argparse + serve_forever + SIGTERM->shutdown() from a separate thread, per http.server's own same-thread-deadlock warning). RealCandidateLaunchTest spawns and HTTP-probes a genuine `python3 -m src.backend_server` subprocess against a real git-worktree checkout of this repo (not mocked) -- caught a real ordering issue doing so: git worktree only sees committed content, so testing before this Unit's own commit landed would check out backend_server.py without main() at all. Fixed by using `git stash create` (builds a commit object from the current working tree without touching HEAD/index) as the commit-to-test-against, falling back to plain HEAD once the tree is clean post-commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> -
Ports graphloop's test_isolated_coding_workspace.py/test_workspace_security.py/ test_change_manifest.py/test_selective_apply.py into supervisor/isolation/ (only the import path changed -- the graphlemon modules were already adapted verbatim from graphloop, confirmed API-identical) plus a few extra edge cases the originals didn't cover. 52 tests green, replacing what had been a single manual smoke test since these modules were staged. Adds supervisor/deploy_supervisor.py: process lifecycle only (PID file, atomic status file, start/stop/is_running), no deploy decision logic yet -- that's U-037+'s job, injected via the poll_fn hook run_foreground() calls on a schedule. PID/status conventions adapted (not imported) from graphloop's hermes_watchdog.py atomic-write pattern. Fail-closed: any exit path (normal, SIGTERM, or an exception from poll_fn) unconditionally writes status='stopped' and removes the PID file, so is_running() never lies. RealSubprocessLifecycleTest actually spawns and stops a real `python3 -m supervisor.deploy_supervisor` process rather than mocking it, per this Unit's own "能独立启停" Done criterion. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Adds paths.repo_root()/is_self_target() and forces needs_worktree=True in work_graph_orchestrate.run_graph() whenever target_folder_path is graphlemon's own checkout, regardless of caller intent -- 05 §5.8's unconditional rule, since a self-target in-place edit risks corrupting the currently-running process's own source out from under it. Adds src/core_engine_classify.py (path-based rule: .claude/agents/*.md, src/*, supervisor/* = core_engine; frontend/, docs/ = not) and bug_record.record_core_engine_change() (a PASS-path audit record, distinct from create_bug_record's FAIL-path root-cause attribution -- no route decision, status='resolved' from creation). scheduler.finish_task_lifecycle() records this audit trail as an additive side effect after close_task_done() on self-target PASS closures. Found and resolved a real SDD-internal conflict (confirmed with the user via AskUserQuestion) before wiring this up: supervisor-auto-rollback-design.md §1 triggers Deploy Supervisor on self-target+Validator-PASS alone, but 03-C §3.11/04-P §4.6 literally gate it on an existing core_engine bug_record -- and task_create.py confirms goal-type self-improvement tasks (the exact self-development-feasibility.md walkthrough scenario) never produce a bug_record on their happy path, which would silently defeat that whole task class under the literal reading. Resolved per user decision: trigger on self-target+PASS alone; the bug_record is a byproduct, not a gate. p_deploy_supervisor_blue_green (04-P §4.6) never touches task.status, so close_task_done() still runs unconditionally -- Deploy Supervisor is a fully decoupled state machine, matching §2's "no shared failure domain" principle. org_loop_route.py is intentionally untouched -- fast_lane/full_replan is FAIL-path retry routing, orthogonal to this PASS-path trigger; deployment row creation stays U-037's job per its own Done criterion. 417 tests green; RealGitWorktreeIsolationTest (U-013/REQ-03's real git worktree regression) re-verified passing per this Unit's own Done criterion. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Adds the two Deploy Supervisor entities from 01-M §1.15/§1.16 (17 tables total): deployment (full 15-value blue-green state machine, per 01-M's own text rather than supervisor-auto-rollback-design.md §3's abbreviated summary table) and deploy_health_signal (append-only health/monitoring samples). ux_deployment_active enforces 01-M's "only one non-terminal deployment at a time" constraint via an expression-based unique index (CREATE UNIQUE INDEX ... ON deployment((1)) WHERE status NOT IN (...)) -- the same partial-unique-index idiom work_graph_run already uses, just with a constant instead of a real grouping column since there's exactly one graphlemon self-target. bug_record.root_cause_category's schema-level CHECK now accepts core_engine, but bug_record.py's own application-layer whitelist deliberately does not follow yet -- classifying a bug as core_engine is a file-path-based rule (05 §5.8), not something the Validator's existing 3-perspective judge_root_cause can produce, and wiring that classification + c_org_loop_route's routing is U-035's job. 392 tests green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
CLAUDE.md is explicit that PH5 (self-modification/auto-rollback) must not start until PH4 has genuinely closed -- not just "all unit tests green," but 06-验收与测试锚点.md's REQ-01~14 happy/boundary/failure cases actually covered. A REQ-by-REQ audit (the same discipline that caught U-031's integration gap) found 5 real gaps: - REQ-03 failure: work_node_execution.status jumped straight running->retrying, skipping 'reflecting' (declared in the schema enum, never once written by any code). - REQ-05 failure / REQ-09 boundary: the "scheduler keeps serving other queued tasks past one blocked_pending_human" scenario was never tested against a real scheduler -- the old test literally said "no scheduler exists yet to test against directly" (stale since U-032). - REQ-06 / REQ-07 / REQ-14 failure (the biggest gap, shared root cause): CorruptPersistedStateError, ClassifierBlockedError, and UsageLimitWaitTimeoutError were each correctly raised and each correctly marked their own work_node_execution 'blocked', but nothing ever routed that to task.status='blocked_pending_human' with the blocked_reason each REQ's acceptance text names. e2e_pipeline.run_end_to_end is split into run_task_lifecycle() so scheduler.py's run_goal_to_completion can create the task itself and catch these three specific exceptions, following the same UPDATE+execution_event pattern budget_guardrail.check_budget() already established for REQ-10. - REQ-02 boundary: PRD internal-contradiction check (verifying->reflecting) had no implementation anywhere -- artifact_write always marked validation_status='passed' unconditionally. Added an optional verify_fn hook to work_graph_orchestrate.run_graph() (one regeneration attempt, then ConsistencyCheckFailedError) and a real consistency-check agent() call scoped to the PRD node only. The first real run of this new call caught a genuine bug: the model returned "consistent": "false" as a JSON string, not a boolean, which `if ok:` silently treated as truthy -- fixed with explicit _coerce_bool(). - REQ-13 failure: minor test-assertion weakness (WS 404 rejection test didn't check the JSON error body). 384 tests green. REQ-01~14 now have real, exercised coverage rather than a "tests are green" assumption -- PH4 can be treated as genuinely closed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Reviews every real invocation of the migrated Planner/Coder/Validator personas since U-004's initial 2026-07-31 verdict (U-011's "SOUL 原则 #10" reference, U-014's full real snake-game closed loop, U-031's agentType spike + full plan_documents run) and finds no new persona-content deviation beyond the three already fixed in U-004 (Feishu Base token, lark-cli coupling, --dangerously-skip-permissions instruction). The disallowedTools bug found along the way (05 §5.4.1.2) is a tool-permission-layer Claude Code behavior, not persona content, so it doesn't extend A-2's deviation scope. Final verdict: stays "部分成立" (not upgraded to confirmed) -- the Coder rewrite already happened and later clean runs don't retroactively undo that -- but the deviation scope is now confirmed closed rather than open-ended, which is what U-033 exists to establish before PH5 planning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
04-P's blocked_pending_human branch, 01-M's task-table indexing rationale, and REQ-09's boundary case all presume an autonomous dispatcher scans queued tasks and keeps serving the queue past a blocked one -- but no such component existed. org_node_lifecycle._check_concurrency (U-010) only gates a single trigger call; e2e_pipeline.run_end_to_end (U-014) drives exactly one task, synchronously, only ever called from tests. Adds src/scheduler.py: run_scheduler_once() dispatches multiple Goals via ThreadPoolExecutor bounded by org_concurrency_config.max_concurrent_goals, each worker opening its own sqlite connection (with a busy_timeout) rather than sharing one across threads -- U-027's SIGSEGV already proved that needs a hand-held lock, which would just serialize everything back to front. Also closes a gap run_end_to_end's own docstring already flagged: it never called close_task_done (PASS) or org_level_loop.run_fix_loop (FAIL), leaving every task stuck at 'validating'. finish_task_lifecycle() applies that step without changing run_end_to_end's own tested behavior or scope. Real-write concurrency (multiple threads, separate connections, same db file) is stress-tested for real in test_scheduler.py without spending API budget -- the actually-new risk surface here is the threading/connection design, not the already-validated (U-031/U-017) work itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
U-031: closes a structural integration gap found during the REQ-01~14 regression audit -- plan_work_graph/execute_work_graph/run_work_node were each individually tested but never actually called together in production code (e2e_pipeline.py's only validated closed loop bypassed all three via flat org_node_trigger calls). Adds work_graph_orchestrate.py (topological sort over graph_manifest + one one-node dynamic-workflow agent() call per Work Node, needed for REQ-06's per-node breakpoint recovery), rewires e2e_pipeline.py's plan_documents/ implement_code onto the real chain, and fixes two bugs a real API run surfaced: graph_manifest's id/node_id/depends_on normalization (work_graph_plan._normalize_graph_manifest) and a missing repo_path in run_work_node's cwd resolution. work_node_run.py also now marks a node 'blocked' instead of leaving it stuck 'running' on any unretryable run_fn exception. U-024.5: the regression audit also found REQ-12's reconnect boundary case (refetch a snapshot after a dropped WS reconnects) was never implemented -- only the dashboard reacted to onStatusChange, and even it never refetched. Fixes mountDashboard/mountTaskBoard/mountTaskDetail (which embeds v_graph_visualizer) to refetch their current snapshot on reconnect. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
三个都是 config_id='default' 单例配置,首次使用即创建、之后原地更新 (upsert),不需要单独的初始化流程: - c_concurrency_config_update:新增 src/settings_config.py( org_concurrency_config 之前完全没人碰过),校验 max_concurrent_goals 落在 1-50。 - c_budget_config_update:budget_guardrail.py 新增 upsert_budget_config() ——跟 U-019 已有的 update_budget_limit_and_resume()(只更新已存在的 行)不是一回事,这个是真正完整的设置页控制器,支持首次创建,沿用 同一套"额度调高后自动恢复 budget_exceeded 阻塞任务"副作用(提取成 _resume_budget_blocked_tasks() 共享)。 - c_log_retention_config_update:同样在 settings_config.py 里,校验 retention_days > 0。 三处校验都在业务层做完整检查,HTTP 层额外兜了 except (InvalidParamError, TypeError)——payload 数值字段缺失时 None<=0 这类比较会抛 TypeError 而 不是业务异常,两个都要接住才能保证返回干净的 400 而不是裸 500。 backend_server.py 第一次加了 do_PUT。 前端三个页面共享一个 renderSettingsSubnav() 小组件模拟 02-V 原型里的 设置下拉菜单。真实服务器端到端验证过三个 PUT/GET 的完整读写循环。 PH3(人机交互控制台)至此全部完成:U-022~U-030。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>