Repository navigation
Settings ownership P53: agent and startup restore admission (E2992 v23) #3147
Description
Current authoritative specification: v29 / Full R23
All five original participants newly approved this exact master and twenty phase digests. Thirteen recipes now validate Cargo workspace at the authorized repository root; target/executable containment and STOP on error remain mandatory. P52 source commit1d5f6b9 is preserved, so its next stage is verification. This corrects documentation and source provenance within the original scope.
Master: D:/0_repos/AgentsCommander_iac/.ac/plans/2992-settings-owner/plan-v29-settings-owner.md
Plan-SHA256: E12A49A810F4270C93BFA7D2EA0C9829EE99AF0B95231912C5CFC30FA80A2051
Mapping: D:/0_repos/AgentsCommander_iac/.ac/plans/2992-settings-owner/phase-issues-v17.json
Identity-SHA256 (compact identity object): 43A926CB7FE16F1FB9CC1D16E5437851C894993C51CD7BBBA5CFF1DFE5EC8D2A
Consensus: D:/0_repos/AgentsCommander_iac/.ac/project-shared/2992-settings-owner/minimum-path-20261016/full-r23-consensus-approved-v1.json
Standing authorization: D:/0_repos/AgentsCommander_iac/.ac/plans/2992-settings-owner/v29-standing-authorization-receipt-v1.json
Complete joined phase and file-impact packet: D:/0_repos/AgentsCommander_iac/.ac/project-shared/2992-settings-owner/minimum-path-20261016/v29-mapped-review-packet-v1.md
All twenty child/branch identities and BASE values are preserved. Six delivered phases remain closed; fourteen phases remain sequential. Execution requires a fresh cold gate and explicit lead dispatch. Shipper exclusively runs product checks; no intermediate app build. Previously requested final no-bundle build once after all phases merge from clean synchronized main, with room09 artifact path/hash/bytes/UTC/sourceSHA; no installation/release/version bump.
Phase P53, child#3147, owner ac-dev-rust-v5, class design-bearing, branch refactor/3147-settings-owner-p53-agent-restore-admission, dependencies P52.
Canonical phase file (sole implementation specification): D:/0_repos/AgentsCommander_iac/.ac/plans/2992-settings-owner/P53-v3-agent-restore-admission.md
Phase-SHA256: 4642F184C9CD61E9C74B52FFC8CD1BAEBA87CEEBC3EC2762373D215ACDB04AE7
Allowed MODIFIED paths (0A/0D):
- src-tauri/src/config/agent_config.rs
- src-tauri/src/config/coding_agent_profiles.rs
- src-tauri/src/commands/ac_discovery.rs
- src-tauri/src/commands/session.rs
- src-tauri/src/phone/mailbox.rs
- src-tauri/src/loops/delivery.rs
- src-tauri/src/cli/list_peers.rs
- src-tauri/src/config/session_context.rs
- src-tauri/src/pty/container_repos.rs
- src-tauri/src/lib.rs
Implementation remains gated by previous dependency delivery and its own cold dispatch. Functional acceptance remains the complete frozen phase recipe.
Historical issue body, superseded only by the current exact specification above:
Part of #2992. This phase implements the user-required strict rejection of duplicate JSON members in consumed AgentsCommander configuration. Return an error with the file intact; never choose first/last, repair, default or partially consume duplicates. Other errors preserve their existing policy.
Approved Full R17 specification: D:/0_repos/AgentsCommander_iac/.ac/plans/2992-settings-owner/plan-v23-settings-owner.md
Plan-SHA256: FA45A1A72BD0A8DF30F3AFD0BAE6DDA3159FA5DCD65D5F7B4EC5CDFF4F6B34F3
Phase: P53; owner ac-dev-rust-v5; class design-bearing; depends on P52 (#3146), which must be merged before starting.
Phase file: D:/0_repos/AgentsCommander_iac/.ac/plans/2992-settings-owner/P53-v2-agent-restore-admission.md
Phase-SHA256: 2B6B424A13AB810CBDD628B188FAF94D8272CACAE90105A2BB415CF4C7B7523A
Consensus: D:/0_repos/AgentsCommander_iac/.ac/project-shared/2992-settings-owner/minimum-path-20261016/full-r17-consensus-approved-v1.json
Source is gated by the exact mapping/standing-authorization receipt and a successful cold implementation dispatch. Branch is created only at phase start from current synchronized main. Product checks are executed exclusively by shipper; no intermediate app build. One final requested no-bundle build after all phases merge.
The following immutable phase specification contains the situation, decided change, exact allowed files, acceptance and verification commands. It is the sole implementation specification; the issue/mapping supplies administrative identity only.
E2992 P53 v2 — agent-restore-admission
Status: READY_FOR_IMPLEMENTATION
Versión: 2; anterior P53-v1-agent-restore-admission.md inmutable. Motivo Full R17: corrección acotada C1 (selección y rechecks sin rechazo de cambios válidos). Readiness sólo AUTORÍA; consenso/ejecución pendientes.
Owner: ac-dev-rust-v5. Class: design-bearing. Changed contract: admisión de configuración. Depends-on: P52.
Epic2992; workflow Full continuación R17, gate frío full-r17-planning-cold-gate-approved-v1.json. Sólo autoría autorizada; fuente bloqueada.
Participantes exactos: ac-architect-v5, ac-dev-rust-v5, ac-dev-webpage-ui-v5, ac-dev-rust-grinch-v5, ac-architect-grinch-v5. Mismo master+veinte digests, revisión pendiente; lead despacha, votos previos no se transfieren.
Repo D:/0_repos/AgentsCommander_iac/.ac/room-09-ac-dev-team-v5/repo-AgentsCommander; diseño9ce147f8454af7ad9d4bd27c1a358f93fd229850. Rutas producto relativas al repo, planes desde .ac.
Fuente pedido: autorización rechazo p45-user-reject-duplicate-json-paths-authorization-v1.json/decisión restore v2/propuesta v3/reconciliación R15-B1, no permiso de fuente.
Riesgo rutinario funcional transversal: selección/conservación/concurrencia cooperante, sin host attestation/signing/PATH hostil.
Archivos permitidos
Sólo MODIFIED/ADDED0/REMOVED0; 10 paths máximo10, un dueño, secuencial tras dependencia merged. No reabrir seis entregadas.
| Archivo relativo al repo | Impacto |
|---|---|
| src-tauri/src/config/agent_config.rs | MODIFIED |
| src-tauri/src/config/coding_agent_profiles.rs | MODIFIED |
| src-tauri/src/commands/ac_discovery.rs | MODIFIED |
| src-tauri/src/commands/session.rs | MODIFIED |
| src-tauri/src/phone/mailbox.rs | MODIFIED |
| src-tauri/src/loops/delivery.rs | MODIFIED |
| src-tauri/src/cli/list_peers.rs | MODIFIED |
| src-tauri/src/config/session_context.rs | MODIFIED |
| src-tauri/src/pty/container_repos.rs | MODIFIED |
| src-tauri/src/lib.rs | MODIFIED |
Objetivo y corte verde
Migrar consumidores agent/replica y preparar restore automático antes de setup; no mover PTY ni tarea async a run.
P52 ya aporta lector sessions read-only/checked agents/profiles/Root/teams. El tipo preparado nace aquí, sin tipo futuro de P50.
Co-managed y catálogo completan la MISMA preparación en P56/P57; no afirmar rechazo global hasta esos cortes/P45/P50.
Consumidores y retirada
- agent_config.rs y coding_agent_profiles.rs: migrar llamadas internas restantes a checked y retirar/cfg(test) wrappers Option ya sin callers productivos. Razón DuplicateJsonMember siempre se propaga antes de .ok/default. Mantener wrappers requeridos únicamente por futuras fases expresamente enumeradas, nunca uso nuevo.
- commands/ac_discovery.rs: read_preferred_agent_id_logged/read_replica_config_with_valid_identity y lecturas directas :266/1750/2785/3037 admiten bytes; Err duplicado llega discover_ac_agents/discover_project_inner/get_replica_context_files. No invalid-identity fallback ni lista parcial. Otros errores conservan warnings actuales.
- commands/session.rs: selección/origen/profile, attach_local_config_telegram_if_any y last-agent-message :6132 usan checked; error precede shell/perfil/default/PTY. Si existe pre-create provisional, rollback_pre_created_session y liberación permit existentes antes de Err.
- phone/mailbox.rs y loops/delivery.rs: selección/wake/context usan checked; fallo responde por outbox/request existente o abandona delivery con ERROR sanitizado al sink, sin proveedor/coordinador alternativo ni falsa credencial. No alterar auth preexistente.
- cli/list_peers.rs: construir salida después de lecturas agent/replica checked completas; Err no imprime peers parciales.
- session_context.rs: mitades checked de lecturas :2336/2831/2918 reciben callback final obligatorio tras activation.as_ref() en wrapper activated; mantener el token y prefijo oracle tests/cli_workgroup_team.rs:2138. No import scanner hacia hoja.
- container_repos.rs: read_repos_array y resolve_repo_mounts reciben callback final requerido y Result. commands/session provee adaptador por arco existente. LocalProcess no abre repos de ContainerTransport.
- lib.rs: consume Root checked, y productor/paquete/consumidores siguientes. Wrappers de pruebas inline esperan Result válido; no helper tolerante productivo.
Preparación única antes de efectos (normativa)
En lib::run, después de loaded_settings/SettingsState (:4460-4474) y ANTES de construir/ejecutar Builder.setup, llamar síncronamente:
prepare_native_restore_startup(settings_snapshot: AppSettings, config_dir: &Path) -> Result<PreparedNativeRestore,String>.
Staged usa loaded_settings legítimo actual. En activación el mismo sitio recibirá snapshot del owner, nunca loader físico alternativo. Err duplicate se convierte en StartupError y retorna a presenter/exit1 ANTES de setup/servicios. Sin runtime, await, red, AppHandle, block_on, manager, layout, seed o PTY en productor.
Leer UNA vez filas con read_startup_sessions_for_admission; mover a preparación normalización was_active y binding seeds de :2391/:5068. Fallos ajenos conservan clasificación/fila preservable. Obtener UNA Vec teams por API checked P52 con project_paths/recorrido existentes; quitar discovery duplicado :5441 y consumir ese mismo valor. No discovery por fila ni archivos no usados.
Root path usa root_agent_dir:1124; admitir Root config/state existente antes de ensure_root_agent_dir:1213, que sigue async. Ausencia es válida, no provisionar aquí.
PreparedNativeRestore privado en lib, sin Default/Deserialize/constructor externo/global/Tauri: snapshot legítimo, teams Vec, mismas filas normalizadas/seeds, Root path/acción y planes wake POR ÍNDICE original. run lo mueve a setup y start_native_restore_startup, después al body FnOnce. No guard/lease/handle FS en paquete; run conserva autoridad legítima. No segundo store ni caché durable.
No Root: mismo orden de :2749-2890 (binding metadata,CWD existente,archived root,agent_fqn/is_coord con backstop,persisted_is_working,restore_session_should_wake). Dormant/CWD ausente conservan defer/archive en body, no abren launch inputs. Root usa root_ps/should_wake_root_agent_on_restore; sin receta read_last_coding_agent_checked, should_auto_create_root_agent_on_first_restore y resolve_root_agent_command existentes. Leer Root config sigue justificado por ensure aun sin auto-create; ningún candidato adicional.
Seam concreto de lanzamiento y consumo
commands/session define pub(crate) struct PreparedRestoreLaunch con campos privados: argumentos actuales cwd/name/agent/shell, resolved_spawn/ResolvedAgentHostShell,is_coord/root y paths/modos efectivamente admitidos para los rechecks de esa receta. No inventario global de fuentes autorizadas. Sin AppHandle/manager/permiso/constructor público.
prepare_persisted_restore_launch(settings:&AppSettings,row:&PersistedSession,cwd:&Path,is_coordinator:bool,teams:&[DiscoveredTeam],validate:fn(&Path,&[u8])->Result<(),String>)->Result<PreparedRestoreLaunch,String>.
prepare_first_root_restore_launch(settings:&AppSettings,root_dir:&Path,last_agent:Option<&str>,teams:&[DiscoveredTeam],validate:fn(&Path,&[u8])->Result<(),String>)->Result<PreparedRestoreLaunch,String>.
Lib invoca segundo sólo tras auto-create positivo; ausencia de agente no se vuelve error fatal nuevo. Extraer selección pura de build_configured_agent_spawn_for_cwd:3548/resolve_root_agent_command:5549 y misma admisión del launch común; no ejecutar create_session para probar.
Replica: mitades lectura/parse, expected_wg_replica_identity/repair_wg_replica_config_value puras en RAM tras validar bytes; no publicar repair/contexto. ContainerTransport prepara container_path_context_for_cwd/resolve_repo_mounts checked antes del pending, sin crear contenedor/reservar recurso; LocalProcess conserva short-circuit.
Contexto: añadir admit_restore_context_configuration(cwd,teams,validate)->Result<Vec,String> en session_context: mismas lecturas JSON/selección que materializer habilitado por target_filename/managed names. Compartir parses from-bytes y teams preparado, sin Markdown/seed/rotate/layout/escritura ni dry-run materializador. Launch live también llama esta mitad antes de pending_session.
Restore persistido mantiene attach_persisted_telegram_if_configured con snapshot bots, no lee local config por costumbre. Auto-Root admite antes config/state del attach_local_config_telegram_if_any:6066. P56 añade co-managed y P57 catálogo/tuning en estos productores ya existentes sin API futura invocada ahora.
Targets son sólo paths/modos leídos al preparar esta invocación, incluidas ausencias legítimas; no serializar/loguear, no registry/map global/token de validez. No sustituyen cap/lock/autoridad/identidad ni relectura. La pareja comando/perfil resuelto+host-shell manda para el spawn y se conserva del mismo snapshot; el recheck no vuelve a seleccionarla ni compara conjuntos para autorizar fuentes. Cada reader posterior mantiene la selección/settings de su propio dominio actual; no forzar snapshot viejo a metadata/coordinador/instrucciones/auto-clear/catálogo/tuning por uniformidad.
start_native_restore_startup recibe prepared:PreparedNativeRestore en lugar de snapshot/persisted/teams/setting_on sueltos; otros handles/flags/guards/closure factories iguales. No repite primeras lecturas :2391/:2433 ni discovery :5441.
Lib :2599 y :2934 usan create_session_inner_for_restore_prepared(transaction:&SelectionTransaction,session_mgr:&Arc<RwLock>,pty_mgr:&Arc<Mutex>,plan:PreparedRestoreLaunch,pending_start_fresh:Option,pending_communication:Option,coding_agent_binding:Option)->Result<SessionInfo,String>.
Adapter expande args existentes :1793 y añade al FINAL de create_session_inner_impl:2062 prepared_restore:Option. Callers live/restart/PTY en mismo archivo pasan None y admiten antes de efectos; viejo wrapper restore sólo cfg(test). Dos callers productivos obligatoriamente preparado.
Auto-Root: execute_root_transaction_prepared(transaction,request:RootJobRequest,plan:PreparedRestoreLaunch) comparte inner con execute_root_transaction:5882 (live wrapper None). No campo en RootJobRequest, cambio de root_lifecycle/selection.rs ni #1341. Rama crear consume plan; Root live/dormant surgido entre medias conserva dedup/rechecks/transaction, nunca permiso por paquete.
Después update gate wait_until_done:2102 y ANTES resource permit/pending/context/seed/PTY, re-admitir los paths/modos de la receta preparada con permisos/caps/identidad actuales, leyendo bytes de nuevo sin re-resolver comando/perfil/host-shell. Duplicado -> Err y rollback pre-create si corresponde, preservando receta/bytes de operación. Ausencia legítima y otros errores siguen su política; cambio válido por sí solo no cancela ni exige retry. Fallos reales de autoridad/permisos/identidad/cap conservan sus negativas existentes. Cada lectura posterior real valida SUS mismos bytes antes de parse/merge/uso dependiente, incluso si su reader legítimo selecciona otra fuente (ej. primer proyecto de tuning o catalogPath); propaga Err duplicate, sin exigir pertenencia a targets iniciales ni usar su admisión como certificado. No framework de inventario, segunda autoridad ni atomicidad multiarchivo.
Root body revalida inmediatamente ANTES ensure_root_agent_dir; execute_root_transaction_prepared también antes de ensure:5889. No basta check posterior dentro launch. Ausencia luego creada por defaults compilados se valida presente en primer lector; jamás merge de duplicado.
Quitar block_on de load_sessions_purging... :5055 para restore. Al inicio body admitido P51 revalidar targets antes de purge/Root effects; fallo retorna conservando tail/guard. Purge original continúa ASYNC bajo sessions_save_lock con relectura propia y retención legítima antes del lock según política existente/P45. No reemplaza filas preparadas ni agrega filas a restore; nunca guarda snapshot obsoleto. No sessions lock dentro settings capture.
Error inicial: productor no purga/archiva/guarda/muta manager y setup no arranca. Error posterior: fallo recoverable sin default ni borrar receta; oracles bytes preimagen de operación, no rollback global de publicaciones independientes.
P51 spawn_restore_startup:3191-3254 firma/current_read/unwind/bodyfactory/guarddrop/complete/continuación EXACTOS. Sin mover PTY/await/tail/red a run/setup. Preparación fallida retorna antes de instalar barriers; Invalid/panic async mantiene salida/completion/tail una vez. No reabrir fuente/spec P51.
Fuentes de la corrección R17
QA factual full-r16-user-questions-architect-clarification-v1.md y full-r16-user-questions-rust-source-facts-v1.md al diseño fijo: spawn+host-shell ya resueltos antes update gate; lectores posteriores conservan dominio actual. Reconciliación full-r17-original-scope-reconciliation-v1.json retira hipótesis sin caso suficiente; no nueva aprobación de producto ni fallback JSON universal. Estos documentos están en project-shared/2992-settings-owner/minimum-path-20261016 desde .ac. P56/P57 amplían este mismo seam y sus rechecks, sin congelar sus readers posteriores.
Evidencia y aceptación propia
Confirmado estático: lectores P52, funciones/orden en lib:2340-3176 y sesión:1793/2062/2324/2861/3109; callbacks no agregan imports hoja. Factibilidad Send/lifetimes y ejecución permanecen revisión Rust/shipper, no PASS aquí.
Inline e2992_duplicate_json_: duplicado seleccionado wake/auto-Root falla preparación real antes contadores service/purge/body; válido consume adapter real hasta async y conserva callback/activation; dormant no lee inputs no usados; sustitución posterior por duplicado niega uso y conserva receta/bytes. En el mismo fixture con barrier del update gate, cambio válido mantiene comando/perfil+host-shell preparados; reader posterior de tuning consume selección legítima actual y valida sus bytes (válido continúa, duplicado Err antes de tuning/PTY). No error por fuente no preparada, congelación global de settings ni default shell/profile/partial peers. Reusar guards/barriers/fakes existentes, sin harness ni Wry nativo. Casos P51 Invalid/syncpanic/bodypanic/tailpanic/completion intactos.
Handoff P54: team checked ya produce la Vec única, faltan consumidores team app/CLI; P56/P57 completan dominios co-managed/catalog en este MISMO paquete; P50 cambia únicamente fuente del snapshot y autoridad.
Contrato común de admisión y preservación
Único dueño settings/RAM, sin nueva fuente productiva. Configuración JSON AC realmente consumida incluye core, menús/ayuda, catálogos, project/team/agent/replica/co-managed y config.state mixto por selección/perfiles. Primera lectura necesaria para boot/restore automático rechaza startup; lectura/reemplazo posterior rechaza uso dependiente. Sin discovery de no usados, defaults/partial/Off ante duplicate, reparación automática ni atomicidad multiarchivo.
Igualdad de claves decodificadas por objeto recursivo (x/\u0078 iguales); padre repetido invalida, padres/índices diferentes y literal a.b versus a/b válidos; overrides entre documentos válidos. No casefold/normalización Unicode/identidad filesystem de keys. No confundir IDs repetidos con duplicate JSON.
Bytes admitidos son los mismos que se parsean; antes Value/typed/filter/merge/shadowing/refresh/naming. Caps/locks/autoridad/identidad/revocation/rechecks actuales preceden y acompañan lectura; preparado no acredita actualidad. Ausencia verificada conserva ausencia; otros errores mantienen política, sintaxis siempre pasa por parser nativo.
Err duplicateJsonMember: con archivo/causa sin keys/values/env/token; conservar razón tipada donde existe. IPC/WS Err envelope actual, CLI mensaje/exit no cero, boot StartupError/present_fatal; workers sin Result cortan acción dependiente y ERROR sanitizado al sink/error_log_event existente. No log sólo WARN y fallback. Pre-auth remoto nunca revela path/payload; respuesta fija sensible intacta, diagnóstico local autorizado.
No tocar sessions/clocks/TASK/protocolos/journals/caches puramente operativos, capture/state ni loops TOML; config.state sí se valida entero por uso mixto. No parser/deps/framework/registry/transporte/esquema nuevo.
Preservar dueño/availability/guard por expresión/PTY permit hasta write, principal/caps/privacidad, nueve sessions/F2/FinalRetention, retención activa+archivada y QuitGate. P51 current_read fallible/factories/catch/guards/barriers/tail y cadencia/cancelación exactos; sin reabrir seis entregadas. Fallo parcial real nunca se llama rollback/no-write ni adopta defaults.
Capas y evidencia estática
Base9ce147f8454af7ad9d4bd27c1a358f93fd229850. Índice raíz room09 ready44413/262454 sin SHA; metadata_changed obliga fuente directa focal, no prueba universal de callers. Blueprint v3 y reconciliación R15-B1 respaldan receta, no sustituyen decisiones de esta spec ni son spec alternativa.
Delta duplicate previsto: cero parejas de módulos nuevas/retiradas; adaptador en path_identity y forwarding por relaciones existentes. Callbacks puros hacia hojas local_overlay/co_managed/capture_catalog/container_repos/session_context evitan imports al scanner/host. Nada lower->AppHandle/Tauri/CLI/WS. PRE histórico c61cfbd/SCC89 y module-arcs DDBC8A498AF2FBFCFA8275001F0A623A77BD4615E1AF601AECBA3231A47A16FC no son POST actual.
Shipper debe comprobar PRE/POST integrado exacto, SCC member sets idénticos, cero nuevas parejas externas, arcos regenerados byte-idénticos y guards verdes; manual métodos/macros/guards complementa detector. No compile/Send/lifetime/runtime/test/graph/CI/GUI PASS durante autoría. Hallazgo no previsto detiene corte/revisión acotada, no ampliar baseline.
Ejecución futura y entrega (no ejecutada durante autoría)
No mutar producto antes de cinco votos exactos, revisión humana y despacho del lead. Un solo corte en vuelo; dueño ac-dev-rust-v5. Revisor Rust Grinch, arquitectura Grinch y dueño UI revisan el mismo conjunto. Child/branch/bases/PR/recibos van en registro externo MAP provisto por lead externo, nunca editar Markdown congelado. Un child y un PR por fase; epic2992 permanece abierto hasta integración/build final.
Todas las compilaciones/tests/fmt/grafos/npm son ejecución FUTURA EXCLUSIVA ac-shipper-v5; Rust/UI sólo editan fuente autorizada. Los comandos focales anteriores se ejecutan DESPUÉS de inicializar R/EVIDENCE/toolchain/containment abajo. Shipper retiene stdout/stderr/exit/UTC con timeout del runner; filtro vacío/fallo/timeout bloquea. Verificar Node22.14.0/npm11.6.2/Rust1.98.1 y locks intactos; Cargo sólo proceso, sin config/jobs/profile/caches globales. Antes de integration tests repetir --no-run --message-format=json con sus tres --test y el mismo chequeo de executable containment. No build intermedio; único build de producto final tras todas las fases.
Git Bash en Windows; cwd raíz repo room09. Ejecutar con timeout/log/stdout/stderr/exit del runner existente, stdin no interactivo; guardar evidencia en target/2992-evidence/<PHASE_ID>. Fallo/timeout/filtro vacío no es PASS. No reset/clean global ni restauración que pise terceros.
set -euo pipefail
PHASE_ID="P53"
EXPECTED_DEPS="P52"
R="D:/0_repos/AgentsCommander_iac/.ac/room-09-ac-dev-team-v5/repo-AgentsCommander"
cd "$R"
test "$(git rev-parse --show-toplevel)" = "$R"
test -z "$(git status --porcelain=v1 --untracked-files=all)"
test -z "$(git diff --cached --name-only)"
: "${MAP:?lead debe proveer ruta exacta del mapping validado de este conjunto}"
export MAP PHASE_ID EXPECTED_DEPS
# CHILD/BRANCH provistos por lead desde mapping validado, no inventarlos.
export CHILD BRANCH
node --input-type=module - <<'NODE'
import fs from 'node:fs'; import assert from 'node:assert/strict';
const m=JSON.parse(fs.readFileSync(process.env.MAP,'utf8'));
const p=m.phases[process.env.PHASE_ID]; assert(p);
assert.equal(String(p.child),process.env.CHILD); assert.equal(p.branch,process.env.BRANCH);
assert.deepEqual(p.dependencies,process.env.EXPECTED_DEPS.split(',').filter(Boolean));
assert(new RegExp('^refactor/'+p.child+'-[a-z0-9-]+$').test(p.branch));
NODE
test "$(gh issue view "$CHILD" --repo mblua/AgentsCommander --json state --jq .state)" = OPEN
gh api --paginate repos/mblua/AgentsCommander/issues/2992/sub_issues --jq '.[].number' | grep -Fx "$CHILD"
git fetch origin main
git diff --name-status 9ce147f8454af7ad9d4bd27c1a358f93fd229850 origin/main
# Clasificar drift por paths/formatter/toolchain/CI. Refrescar sólo evidencia relevante.
EVIDENCE="$R/target/2992-evidence/$PHASE_ID"
mkdir -p "$EVIDENCE"
node --input-type=module - <<'NODE' > "$EVIDENCE/dependencies.tsv"
import fs from 'node:fs';import assert from 'node:assert/strict';
const m=JSON.parse(fs.readFileSync(process.env.MAP,'utf8'));
for(const id of m.phases[process.env.PHASE_ID].dependencies){
const p=m.phases[id]; assert(p.pr&&p.mergeSha); console.log(p.pr+'\t'+p.mergeSha);
}
NODE
while IFS=$'\t' read -r dep_pr merge_sha; do
test "$(gh pr view "$dep_pr" --repo mblua/AgentsCommander --json state --jq .state)" = MERGED
test "$(gh pr view "$dep_pr" --repo mblua/AgentsCommander --json baseRefName --jq .baseRefName)" = main
test "$(gh pr view "$dep_pr" --repo mblua/AgentsCommander --json mergeCommit --jq .mergeCommit.oid)" = "$merge_sha"
git merge-base --is-ancestor "$merge_sha" origin/main
done < "$EVIDENCE/dependencies.tsv"
git switch -c "$BRANCH" origin/main
BASE="$(git rev-parse HEAD)"
printf '%s\n' "$BASE" > "$EVIDENCE/base.sha"
git branch --show-current
git status --porcelain=v1 --untracked-files=all
node --version # esperado22.14.0
npm --version # esperado11.6.2
export CARGO_TARGET_DIR="$R/target" CARGO_BUILD_BUILD_DIR="$R/target"
cd "$R/src-tauri"
rustc +1.98.1 -vV
cargo +1.98.1 --version
cargo +1.98.1 metadata --locked --no-deps --format-version=1 > "$EVIDENCE/metadata.json"
export EVIDENCE R
node --input-type=module - <<'NODE'
import fs from 'node:fs';import path from 'node:path';import assert from 'node:assert/strict';
const m=JSON.parse(fs.readFileSync(process.env.EVIDENCE+'/metadata.json'));
const normal=p=>path.resolve(p).replaceAll('\\','/').toLowerCase();
assert.equal(normal(m.target_directory),normal(process.env.R+'/target'));
assert.equal(normal(m.workspace_root),normal(process.env.R+'/src-tauri'));
NODE
cargo +1.98.1 test --locked --lib --no-run --message-format=json > "$EVIDENCE/test-artifacts.jsonl"
node --input-type=module - <<'NODE'
import fs from 'node:fs';import path from 'node:path';import assert from 'node:assert/strict';
const normal=p=>path.resolve(p).replaceAll('\\','/').toLowerCase();
const root=normal(process.env.R+'/target')+'/';let n=0;
for(const l of fs.readFileSync(process.env.EVIDENCE+'/test-artifacts.jsonl','utf8').split('\n').filter(Boolean)){
const a=JSON.parse(l);if(a.executable){assert(normal(a.executable).startsWith(root));n++}
}assert(n>0,'no test executable');console.log({containedExecutables:n});
NODE
cargo +1.98.1 test --locked --lib e2992_duplicate_json_ -- --list > "$EVIDENCE/duplicate-list.txt"
grep -F e2992_duplicate_json_ "$EVIDENCE/duplicate-list.txt"
# Lista debe incluir casos de ESTA fase; revisar nombres/count>0, no aceptar sólo casos previos.
cargo +1.98.1 test --locked --lib e2992_duplicate_json_ -- --test-threads=1 --nocapture
cargo +1.98.1 check --locked --all-targets
cargo +1.98.1 fmt --all -- --check
cargo +1.98.1 test --locked --test loops_layering --test instance_gitignore_layering --test project_settings_layering --no-run --message-format=json > "$EVIDENCE/integration-artifacts.jsonl"
node --input-type=module - <<'NODE'
import fs from 'node:fs';import path from 'node:path';import assert from 'node:assert/strict';
const normal=p=>path.resolve(p).replaceAll('\\','/').toLowerCase(),root=normal(process.env.R+'/target')+'/';let n=0;
for(const l of fs.readFileSync(process.env.EVIDENCE+'/integration-artifacts.jsonl','utf8').split('\n').filter(Boolean)){
const a=JSON.parse(l);if(a.executable){assert(normal(a.executable).startsWith(root));n++}
}assert(n>=3,'missing integration executables');console.log({containedIntegrationExecutables:n});
NODE
cargo +1.98.1 test --locked --test loops_layering --test instance_gitignore_layering --test project_settings_layeringConservar Node22.14.0/npm11.6.2/Rust1.98.1 de CI y locks existentes. Sin cambios de versiones/deps/formatter ajenos. Al escribir: revalidar branch/base/index/paths, registrar preimagen y output propio por path. Recuperar sólo bytes propios aún intactos; preservar cambios ajenos y reportar conflicto. Comparar full BASE..HEAD (jamás HEAD~1), staged y ordinary-untracked; todo hunk propio dentro de la tabla. Cero es estado válido, no ausencia de evidencia.
Antes de push/PR integrar origin/main localmente y resolver sólo conflictos autorizados. Registrar PRE actual (origin/main) e integrated HEAD limpios. Si merge incorpora cambios ajenos, mantener full diff desde BASE y documentar por separado ese delta byte-idéntico a upstream; comprobar también PRE..HEAD para aislar trabajo propio, sin ocultar cambios ni reiniciar diseño por drift ajeno. No ampliar union propia para silenciar gates.
cd "$R"
git fetch origin main
PRE="$(git rev-parse origin/main)"
git merge --no-edit origin/main
# Resolver/commit si corresponde; después debe estar limpio.
test -z "$(git status --porcelain=v1 --untracked-files=all)"
mkdir -p "$EVIDENCE/pre-source"
git archive "$PRE" | tar -x -C "$EVIDENCE/pre-source"
set +e
node scripts/01-rust_module-dependency-cycles.mjs "$EVIDENCE/pre-source/src-tauri" --emit-graph "$EVIDENCE/pre.json" --quiet > "$EVIDENCE/pre-detector.log" 2>&1
pre_exit=$?
node scripts/01-rust_module-dependency-cycles.mjs src-tauri --emit-graph "$EVIDENCE/post.json" --quiet > "$EVIDENCE/post-detector.log" 2>&1
post_exit=$?
set -e
test "$pre_exit" -le 1
test "$post_exit" -le 1
node scripts/02-module-arc-record.mjs --graph "$EVIDENCE/post.json" --out "$EVIDENCE/post-arcs.txt"
export EVIDENCE
node --input-type=module - <<'NODE'
import fs from 'node:fs';import assert from 'node:assert/strict';
const root=process.env.EVIDENCE,pre=JSON.parse(fs.readFileSync(root+'/pre.json')),post=JSON.parse(fs.readFileSync(root+'/post.json'));
function scc(g){
const a=new Map(g.modules.map(n=>[n.id,[]]));for(const e of g.edges)if(e.from!==e.to)a.get(e.from).push(e.to);
let i=0,idx=new Map,low=new Map,stack=[],on=new Set,sets=[];
function walk(v){idx.set(v,i);low.set(v,i++);stack.push(v);on.add(v);
for(const w of a.get(v)){if(!idx.has(w)){walk(w);low.set(v,Math.min(low.get(v),low.get(w)))}else if(on.has(w))low.set(v,Math.min(low.get(v),idx.get(w)))}
if(low.get(v)===idx.get(v)){let z=[],w;do{w=stack.pop();on.delete(w);z.push(w)}while(w!==v);sets.push(z.sort())}}
for(const v of a.keys())if(!idx.has(v))walk(v);
const cyclic=sets.filter(z=>z.length>1).sort((a,b)=>a[0].localeCompare(b[0]));
assert.equal(cyclic.length,g.summary.moduleCycles);return {sets,cyclic,map:new Map(sets.flatMap((z,i)=>z.map(v=>[v,i])))};
}
const a=scc(pre),b=scc(post);assert.deepEqual(b.cyclic,a.cyclic);
const arcs=g=>new Set(g.edges.filter(e=>e.from!==e.to).map(e=>e.from+' -> '+e.to)),old=arcs(pre),now=arcs(post);
const internal=(f,t)=>a.map.has(f)&&a.map.get(f)===a.map.get(t)&&a.sets[a.map.get(f)].length>1;
assert(internal(a.cyclic[0][0],a.cyclic[0][1])); // control positivo sobre SCC real
const outside=a.sets.find(z=>z.length===1);assert(outside);assert(!internal(a.cyclic[0][0],outside[0]));
const added=[...now].filter(x=>!old.has(x));for(const x of added){const [f,t]=x.split(' -> ');assert(internal(f,t),'new cross-boundary arc '+x)}
console.log(JSON.stringify({cyclicSccs:b.cyclic.length,memberSetsIdentical:true,newArcs:added,newCrossBoundaryPairs:0}));
NODE
cmp "$EVIDENCE/post-arcs.txt" src-tauri/module-arcs.txt
npm run check:arcs
npm run check:cycles
git diff --check "$BASE" HEAD
git diff --name-status "$BASE" HEAD
git diff --name-status "$PRE" HEAD
git diff --cached --exit-code
git status --porcelain=v1 --untracked-files=allReviewer compara grafos por SCC con algoritmo del instrumento: coverage.graphShape.cyclicSccs debe ser igual PRE/POST, TODOS los conjuntos completos de miembros idénticos, cero NUEVAS parejas from->to entre SCC PRE distintos; referencias internas sólo en SCC preexistente. No basta igual número/SCC count. Graph exit1 con archivo válido es ciclos existentes; exit2/3/análisis ausente bloquea. Instrumento1.1.1 ignora métodos/macros y algunos paths: revisar manualmente referencias reales/guards y el permiso PTY; no afirmar cero universal. Registro regenerado byte-idéntico y guards verdes antes de publicación, no POST imaginario.
Todos los jobs disparados y configured-required verdes en exact PR-head. CI actual: ci-tier/test-debt/rust-regression Windows/clippy-linux/rust-regression-linux/rust-linux-release-parity/rust-regression-macos/rust-fmt/terminal-snapshot-portable/windows-release-cli-smoke/issue-1850-windows-profile/frontend-regression según tier. Lead consulta reglas required reales y aplica ci:full para evidencia OS nueva; skips inexplicados/otro SHA bloquean. P41 incorpora filtros e2992_ en Linux/macOS existentes; Windows ya corre --lib --bins --tests. No nuevo runner.
git rev-parse HEAD
# Tras autorización de publicar: PR base main, head BRANCH/SHA exactos, cierre sólo CHILD.
gh pr view "$PR" --repo mblua/AgentsCommander --json baseRefName,headRefName,headRefOid,closingIssuesReferences,statusCheckRollup
gh pr checks "$PR" --repo mblua/AgentsCommander --watch
gh pr checks "$PR" --repo mblua/AgentsCommander --requiredLead verifica conjunto de cierres exactamente {CHILD}, tested-head, mergeSHA y ancestry en main; guarda recibos externos. No cierre de epic por keyword de fase. Build histórico sólo tras todos los children merged: shipper ejecuta npm run build:prod:no-bundle en main limpio/upstream origin/main/igualdad targetSHA; copia no-clobber de agentscommander_testeable.exe al target/release/agentscommander_standalone_room-09__.exe (UTC del build), entrega path/hash/bytes/UTC/SHAfuente; lead verifica y recién entonces cierra epic. No instalación/release/version bump.
Activity
mblua commentedon Oct 28, 2026
Postponed by user decision (2026-10-28): the settings-ownership effort went out of scope and its re-plan #3146 was closed as not planned. Kept open with
postponedso it can be resumed later; not assigned to any room meanwhile.