• 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • …be04-main-checkout-drift
    graphlemon authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »

  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »

  • 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>
    bboonnee authored
     
    Browse Dir »
  • 三个都是 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>
    bboonnee authored
     
    Browse Dir »
  • c_skill_diff 和 U-028 的 c_artifact_get 一样,只存在于机器可读的
    sdd-mvcp-contract.json,03-C 正文从未写出来,已订正补全。
    c_skill_list/c_skill_get 两份文档都没定义,是 v_skill_library/
    v_skill_audit_log 自己的展示需求倒逼出来的缺口。
    
    新增 src/skill_query.py:list_skills()、get_skill_audit_log()、
    get_diff()(git diff <before> <after>——skill_update.py 的
    _write_and_commit 把每次迭代做成原子单文件 commit,整仓库范围的 diff
    天然只涉及那一个 Skill 文件)。
    
    关键安全考量:Skill 库活在 graphlemon 自己的仓库里,c_skill_rollback
    会真的在这个仓库里跑 git commit——如果 HTTP 层写死指向真实仓库根目
    录,任何针对这条路由的测试都会在正在跑的这个 graphlemon 仓库里留下
    真实提交。参照 db_path 已有的做法,给 create_server() 加了
    skill_repo_path 参数(默认 graphlemon 真实仓库根,测试传临时仓库),
    不通过 HTTP 暴露这个参数,避免变成任意写入的口子。记录在 05
    §5.4.1.4。
    
    真实服务器 + 临时仓库端到端验证过完整的 update→diff→rollback 闭环,
    确认过跑完之后 graphlemon 自己仓库的 git log 完全没变化。
    
    v_skill_library/v_skill_audit_log 不检查"当前项目"——Skill 是平台级
    资产,跟项目无关。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • c_artifact_get 在机器可读的 sdd-mvcp-contract.json 里其实定义过
    (GET /api/artifacts/:artifact_id),只是 03-C 正文一直没写出来,已订正
    补全。c_artifact_list 连 MVCP JSON 里都没有 operation 定义,纯粹是
    v_artifact_browser 自己的过滤字段倒逼出来的缺口,跟 U-025 补 c_task_list
    是同一类问题。
    
    新增 src/artifact_query.py:list_artifacts() 返回裸数组(跟已有的
    list_tasks() 保持同款风格);get_artifact() 把血缘链(按
    depends_on_artifact_ids 往回走,只走第一个依赖——这条流水线的实际产出
    只有线性 BRD→PRD→SDD 链)和文件内容(artifact 表本身没有 content 列,
    走 project.target_folder_path + artifact.file_path 现读磁盘)一起返回,
    预览面板一次请求就够。
    
    前端预览统一用 <pre> 纯文本展示,不做 Markdown 渲染/代码高亮——02-V
    原文想要那个效果,但需要引入第一个前端第三方库,跟这个控制台一贯的纯
    vanilla JS、无构建步骤做法冲突,是有意简化。顺手给 index.html 常驻导航
    栏加上了"产物"链接。
    
    真实服务器验证过三级血缘链(BRD→PRD→SDD)返回顺序正确。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • execution_log_file 的行生命周期 U-013 早就管好了,但从来没有代码真的往
    日志文件里写过内容——src/execution_log.py 新增 append_log() 补上这条
    缺失的写路径,设计成 run_fn 执行过程中随时可调用,不关心节点在做什么。
    get_log() 按 offset/limit 分页读,download=true 时走原始文件流。
    
    c_ws_log_tail(WS /api/ws/work-nodes/:node_exec_id/log)复用 U-024 手写
    的 RFC 6455 机制,轮询 execution_event(log_appended);节点一旦转终态
    (done/blocked/failed)服务端主动发关闭帧断开,不让客户端一直轮询。
    
    p_log_retention_cleanup 实现成纯函数 run_log_retention_cleanup():过期
    扫描 + max_total_size_mb 超限按最旧优先追加候选 + 单个文件删除失败只
    记录不阻塞其余项,没有配调度器(不在本 Unit 覆盖范围),留给未来的
    调度进程周期调用。
    
    过程中意外抓到一个真的并发 bug:_Server.server_close() 关闭共享
    sqlite 连接时原来没拿 conn_lock,如果这时候正好有个 WS 轮询线程在
    另一边持锁执行 SQL,两个线程无同步地并发操作同一个 C 级 sqlite3 连接
    对象,实测直接把解释器崩了(SIGSEGV)——U-024 就已经有这个隐患,这次
    高频 WS 测试把时序窗口撞开了。修好后连续跑几十次全部稳定通过,记录
    在 05 §5.4.1.3。
    
    frontend/app.js 的 v_log_viewer 挂在 v_task_detail 每个 to-do 节点的
    展开详情里,首次展开才建连接(懒加载),运行中态走 c_ws_log_tail 实时
    滚动、终态走 c_execution_log_get 静态拉取,活跃连接在任务级刷新/离开
    页面时统一关闭。真实服务器 + Node 原生 WebSocket/fetch 端到端验证过
    实时追加与终态自动断开的完整链路。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »

  • 纯前端 Unit,数据已经在 U-025 的 c_task_get 里,只是 task_query.py
    的 _work_nodes_for_run 补上了 hover tooltip 需要但当时没取的字段
    (input_schema_ref/output_schema_ref/quota_limit_type/
    quota_wait_started_at)。
    
    frontend/app.js 新增 computeGraphLayers():按 graph_manifest 节点的
    depends_on 边做拓扑分层,渲染成按执行层级排布的行,fan-out/fan-in
    都能正确分层——用一个 n1→[n2,n3]→n4 的真实 graph_manifest 验证过,
    直接从 app.js 里取出 computeGraphLayers 本体(不是重新实现一份逻辑)
    跑真实服务器返回的 JSON,分层结果完全正确。节点按 03-C 3.6 节六态
    配色,waiting_for_quota 态 hover 展示 quota_limit_type + 已等待时长。
    
    02-V §2.7 原描述还包含一份 Org Graph 层,但 v_task_detail 页面顶部
    已有等价的 org-strip 面板,这里判断重复渲染没有意义,只做了 Work
    Graph DAG 层,Org Graph 复用页面已有的那个。
    
    嵌入位置:v_task_detail 里原来"随 U-026 补上"的占位提示换成了真实
    的 DAG 面板。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • 三个页面需要的读接口此前都不存在,本 Unit 一并建了:
    
    - c_task_list/c_task_get(03-C §3.2,此前无 Unit 认领):新增
      src/task_query.py + GET /api/tasks、GET /api/tasks/:task_id。
      c_task_get 的返回比 03-C 字面契约更完整——额外带上
      work_graph_run(含解析后的 graph_manifest)+ 每个 run 的
      work_node_execution 列表,因为 v_task_detail 的 to-do 树需要这份数据,
      03-C 已订正补全。
    - c_org_loop_route(U-016 已实现 route_org_loop(),从未接 HTTP)第一次
      接上 POST /api/tasks/:task_id/route,v_task_detail 的人工干预
      (manual_resume)按钮需要它。
    - 预算护栏此前没有任何读接口(03-C 只写了 PUT),新增
      budget_guardrail.list_budget_configs() + GET /api/settings/budget
      给仪表盘预算条用。
    
    frontend/app.js 补上真实的 #/(org graph 三节点卡片、当前任务概要、
    预算条、事件时间线,全部通过 c_ws_subscribe 实时刷新)、#/tasks
    (关键字/状态/org_node 过滤)、#/tasks/:task_id(task 字段、to-do 树、
    关联 artifact/bug_record、blocked_pending_human 告警条 + manual_resume
    按钮)。路由改成对象形式支持挂载态清理,切页面时关掉上一个页面的
    WebSocket 连接。v_graph_visualizer/v_log_viewer 子模块明确标成占位
    提示(随 U-026/U-027 补上),不是假造内容。
    
    顺带把 U-023 里因 c_task_list 当时不存在而简化的 related_task_id
    换回真下拉,goal_composer/bug_feedback 提交成功后也改回跳转真实的
    v_task_detail。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • Python stdlib 没有 WebSocket 支持,src/ws_subscribe.py 手写 RFC 6455
    握手(hashlib+base64 算 Sec-WebSocket-Accept)+ 帧编解码(服务端帧不
    加掩码,客户端帧强制解掩码),不引入 websockets 之类第三方库
    (05 §5.7)。
    
    推送用轮询而非写路径 hook:execution_event 的写入分散在十几个调用点
    (org_node_trigger/work_graph_execute/work_node_run/task_create/
    bug_record/...),没有单一写入路径可以插桩,backend_server.py 的
    _handle_ws_subscribe 劫持 ThreadingHTTPServer 连接后每 0.5 秒按
    rowid 增量查一次 execution_event(project_id/可选 task_id 过滤),
    轻松满足 REQ-12 的 ≤2 秒目标。断线重连按 03-C §3.14 走 REST 快照,不
    在 WS 层做消息重放,新连接只看连接那一刻起的新事件。
    
    daemon_threads = True 避免长连接 WS 线程阻塞进程退出。
    
    验证:test_backend_server.py 用原始 socket 走真实握手+推送延迟断言;
    另外用 Node 原生 WebSocket 客户端做跨实现互操作性验证,连真实运行的
    服务器,从建连到收到 c_task_create 触发的真实推送全程走通。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • c_task_create(U-009 已实现的 task_create.create_task())第一次接上
    POST /api/tasks。c_bug_report_create 此前完全没有实现,新增
    bug_record.create_bug_report() + POST /api/bug-reports。
    
    发现并订正 03-C 一处输入契约遗漏:c_bug_report_create 的
    input_contract 没写 project_id,但 execution_event.project_id 是
    NOT NULL、bug_record 表自己没有 project_id 列可以在 related_task_id
    留空时兜底推导,字面上无法满足约束——按 c_task_create 同款处理方式把
    project_id 变成必填,03-C 已订正。
    
    frontend/app.js 补上 #/goals/new、#/bugs/new 真实交互;控制台还没有
    "当前项目"状态的正式载体(v_dashboard 是 U-025 才做),project_id 暂存
    localStorage,v_project_setup 创建成功时写入。related_task_id 因为
    c_task_list 未被任何 Unit 认领,简化成文本输入而非下拉,等 U-025 补上
    任务列表后再换。提交成功后原型写的是跳转 v_task_detail,该页面也是
    U-025 才建,暂时改成原页面内展示确认信息。
    
    端到端冒烟时额外发现一个真 bug:create_bug_report() 原本没校验
    project_id/related_task_id 是否存在,坏 project_id 会在 execution_event
    插入时触发未捕获的 IntegrityError,请求直接 500——补了存在性校验,
    改为干净的 INVALID_PARAM/400。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »