Skip to content

Task 3: Prove and lock Herdr-only #467

Description

@AkaraChen

Summary

Prove and lock Herdr-only for parent plan #463. Local PTY, the env/CLI switch, and sqlite session/profile authority are already gone (#436 Tasks 8–9 / #451 / #452). Task 1 (#464 on task-1-rename-pty-layer) renamed the session layer off pty. Task 2 (#465 on task-2-delete-twocode-runtime) deleted every TWOCODE_RUNTIME / --twocode-runtime trace. What survives is leftover live vocabulary (pty_sessions in current-state docs and DROP-by-name tests, portable-pty in comments, integration_pty_db.rs, absence-asserts that still embed deleted *_pty_* identifiers) and the missing repo lock. This task adds that audit and makes the live tree pass it. Same IPC semantics, same clicks and labels. No Local anything.

Herdr is the profile authority. sqlite profiles is not source of truth (the table is DROPped). projects / project_groups / checkout_notes stay in 2code sqlite. Default-runtime switch and deleting Local/PTY/profiles rows already shipped in #436 — do not re-litigate or re-add them. Never let Local and Herdr own the same worktree (Local is gone; do not bring it back). Do not change interaction. Naming leftover identifiers is not a click/label redesign.

This is #463 Task 3, not #394 Task 3 (#404), not #436 Task 3 (#441), and not #458 Task 3 (#461). Do not implement or close those tickets here.

Dependencies: Parent plan #463. Prerequisites: #464 (Task 1), #465 (Task 2). Stack on task-2-delete-twocode-runtime. Open task tickets from #394 / #436 / #458, leftover gaps (#396, #397, #398, #399, #401), line-count trackers, and #466 (share the user Herdr session) are out of scope.

Work to complete

  1. Clear leftover live pty vocabulary so the lock can pass

    • Parent-plan grep -rn "pty" is the intent (live tree, not applied migration history). Do not run that literal command as the lock: it matches empty / is_empty / empty.tsx. Use a word-boundary scan (\\bpty) plus the exclusions below. Uppercase PTY in docs/herdr-integration.md that means Herdr’s own pane process (DSR/DA “as PTY input”, workspace.close kills the pane PTY) stays — that is Herdr ownership, not Local vocabulary.
    • Rewrite current-state text that still names deleted identifiers (pty_sessions, pty_output_chunks, pty_logs, handler/pty.rs / infra::pty / service::pty, portable-pty, create_pty_session / attach_pty_output / …) as if they were live, or as the only way to say “gone”:
    • Rename live test identifiers that still say pty: src-tauri/tests/integration_pty_db.rs (file + pty_sessions_table_is_dropped), *_local_pty* / production_source_has_no_local_pty_runtime / list_omits_leftover_sqlite_pty_sessions / drop_pty_sessions_keeps_projects_and_notes test names. Behavior stays.
    • Do not edit src-tauri/migrations/. Do not edit openspec/changes/archive/. Do not sweep website/ blogs or live openspec/specs/ (not application code; not this lock’s tree).
  2. Replace deleted-name absence-asserts with durable invariants

    • Same rule as Task 2: a test that only contains("pty_sessions") / contains("create_pty_session") / contains("portable-pty") / contains("INSERT INTO pty_sessions") keeps the deleted vocabulary in the live tree and will fail the lock.
    • Current schema: after embedded migrations, user tables are exactly projects, project_groups, checkout_notes (plus Diesel’s schema-migrations table). Rewrite integration_pty_db.rs (after rename), db.rs init_db / drop_pty_sessions_keeps_projects_and_notes, and herdr.rs sqlite_session_ids() to that allowlist. Re-adding a session or profile table fails the allowlist without naming the dropped table.
    • docs_client_mode.rs, runtime.rs production_source_has_no_local_pty_runtime, herdr.rs gui_detach_is_not_pane_close / stream_pty_output negatives, terminalTransport.test.ts getPtySessionHistory / restorePtySession negatives: pin current names and structure (terminal IPC, TerminalSessionRecord, handler/terminal.rs, Herdr-only RuntimeBackend). Do not keep a forbidden-string list of deleted *_pty_* IPC.
    • Keep/strengthen existing structure proofs that name deleted types only if those literals are not pty / the deleted flag: LocalAdapter, RuntimeBackend::Local, mod local, select_gui_backend does not read env/argv (Task 2). The new audit owns “these must not return”.
    • Applied-history exception (exclude from the vocabulary scan, do not rewrite to a lie): src-tauri/tests/integration_migrations.rs and db.rs drop_profiles_migration_copies_notes seed SQL must still use the historical table name because they replay a pre-DROP schema. That is the same class of history as src-tauri/migrations/.
  3. Add the audit test and state the invariant

    • Add one Rust test (prefer src-tauri/tests/ or a service integration test) that walks the repo and fails if any of these reappear in the scanned tree:
      • a Local adapter or RuntimeBackend::Local variant (LocalAdapter, mod local, runtime/local.rs)
      • a portable-pty / native_pty_system / 2code ConPTY spawn dependency (Cargo.toml / production source; not the Windows App Execution Alias skip in handler/shell.rs)
      • an env- or flag-selected backend (std::env::var / std::env::args in select_gui_backend / RuntimeSelector / build_gui_runtime — keep Task 2’s distinction: Herdr sidecar extra_env / XDG_CONFIG_HOME is isolation, not backend selection)
      • a sqlite session/profile store used as authority (live Diesel schema / repo modules / production INSERT/UPDATE of those tables; projects stay)
      • live pty vocabulary (word-boundary pty, including pty_sessions, portable-pty, create_pty_session)
      • the deleted runtime-switch name (construct the needle at runtime so the audit source does not contain that flag)
    • Scan exclusions (document them next to the command): src-tauri/migrations/, src-tauri/tests/integration_migrations.rs, the drop_profiles_migration_copies_notes seed in db.rs, **/target/**, lockfiles, node_modules/, src/generated/, src/paraglide/, src-tauri/binaries/, openspec/, website/.
    • Construct pty / flag needles in the audit source (format! / concat) so the audit file itself does not reintroduce the vocabulary it forbids.
    • State the invariant in docs/architecture.md Design Decisions and root AGENTS.md anti-patterns without naming the deleted flag: no Local adapter; no env or CLI flag selects a runtime backend; GUI RuntimeRouter is always Herdr (fail closed); sqlite profile/session tables are not authority; live identifiers do not use Local session-layer names. Keep AGENTS.md / CLAUDE.md copies in sync where they already share that anti-pattern line. Point at the test, not a hand-run ritual.
    • Report the exact scan command and its output in the implementation reply (empty match list on this branch). Suggested shape (adjust to the test’s real walker):
      rg -n --hidden -g '!src-tauri/migrations/**' -g '!**/target/**' -g '!**/*lock*' -g '!**/node_modules/**' -g '!src/generated/**' -g '!src/paraglide/**' -g '!src-tauri/binaries/**' -g '!openspec/**' -g '!website/**' -g '!src-tauri/tests/integration_migrations.rs' '\\bpty'
      plus the constructed flag scan. The test is the lock; the command is the evidence.
  4. Prove behavior is unchanged

    • App still builds. Existing suites pass: GUI backend is always Herdr; Herdr-down still fail-closes; New Tab / write / resize / scroll / close / attach still hit RuntimeRouter; restore is still reattach of a live pane_id; projects still round-trip in sqlite; DROP migrations still leave the catalog.
    • Do not add a Settings runtime toggle, a new env/CLI switch, a Local adapter, or sqlite profiles / session authority.
    • If line count grows past the estimate, open a tracking issue; do not fold #466 or leftover gaps into this branch.

Likely files and directories

Estimated changes: 250–500 lines total, additions plus deletions, including tests and docs; excluding generated files, lockfile churn and binary artifacts. Material scope growth (rewriting website/ / openspec/, sharing the user Herdr session, leftover capability gaps) should become a separate issue.

Acceptance criteria

  • A repo-walk audit test fails if any of these reappear in the scanned live tree: Local adapter / RuntimeBackend::Local, portable-pty / ConPTY spawn dependency, env- or flag-selected backend, sqlite session/profile store as authority, word-boundary pty vocabulary, or the deleted runtime-switch name. The implementation reply quotes the exact command and its output (empty matches).
  • The scan uses word-boundary pty (so empty / is_empty do not fail) and excludes src-tauri/migrations/, lockfiles, target/, node_modules/, generated/paraglide/binaries, openspec/, website/, integration_migrations.rs, and the pre-DROP seed in db.rs. It does not exclude current-state docs, AGENTS.md, production comments, or non-history tests.
  • Live current-state docs and AGENTS.md / CLAUDE.md copies describe Herdr-only RuntimeRouter and live pane_id sessions without advertising deleted pty_* module/IPC/table names as live vocabulary. The invariant is stated in docs/architecture.md and AGENTS.md without naming the deleted flag. Copies that already share text stay in sync.
  • Current-schema tests assert the live sqlite allowlist (projects / project_groups / checkout_notes) instead of grepping a dropped table name. Historical migration filenames and integration_migrations.rs seed SQL stay. TWOCODE_RUNTIME still appears nowhere (Task 2); the new test keeps it that way without putting that literal in docs.
  • No behavior change. No Local adapter / portable-pty spawn / new env-flag backend. sqlite profiles is not source of truth. projects still round-trip in sqlite. No second events.subscribe. lib.rs still does not name HerdrRuntimeSync. v0.9.0 pin unchanged. GUI exit still does not server stop.
  • Named UX exceptions: none new. Existing Herdr-backed exceptions stay (route ids workspace_id; tab ids pane_id; splits flattened as extra tabs; non-git New Profile is folder workspace.create; Herdr-down New Tab errors). The audit and vocabulary cleanup are not a click/label redesign.
  • Migrations under src-tauri/migrations/ are untouched. openspec/ and website/ are untouched.
  • Tests: cargo test --workspace from src-tauri (or cargo test --workspace --exclude code where GTK is unavailable), including the new audit. Frontend lint/typecheck/tests if terminalTransport.test.ts (or other frontend fixtures) change. bun run typegen only if IPC signatures change (none expected); do not hand-edit src/generated/.
  • No push. No pull request.

Out of scope

Standing constraints

  • No behavior change. This is a documentation/test lock. Same IPC semantics, same clicks and labels.
  • No Local anything. Do not reintroduce an adapter, env flag, CLI flag, dependency, or fallback path, not even behind a cfg or a comment — including a differently named successor to the deleted runtime switch.
  • Migrations are history. Do not rename or edit files under src-tauri/migrations/.
  • Frontend callers move with the commands. This task must not rename IPC; if an accidental rename happens, update generated bindings and every caller in the same commit.
  • Do not treat sqlite profiles as source of truth. projects stay in 2code sqlite.
  • Never let Local and Herdr own the same worktree (Local is already removed; do not bring it back).
  • Default-runtime switch and deleting Local/PTY/profiles rows already shipped in Plan: 2code as a Herdr client (profiles derived from Herdr) #436; this task does not re-litigate those decisions and does not change them.
  • Do not change interaction unless the old UX cannot be backed by Herdr. This task has no named interaction exception.
  • One task; stack on task-2-delete-twocode-runtime. Do not push. Do not open a PR.

Why this is next

#463 has three tasks. Task 1 (#464) and Task 2 (#465) are done on task-2-delete-twocode-runtime. Task 3 is the remaining prove-and-lock audit; the phase is not closed until that test exists and leftover live pty vocabulary is gone. The leftover open gaps (#396/#397/#398/#399/#401) are Linux-unverifiable or upstream-capability leftovers, not in-flight replacements for this work. #461 is #458 Task 3 (line-count trackers). #404 is #394 Task 3 (sidecar). #441 is #436 Task 3 (profile list). #466 is a different parent plan.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions