feat(cli,core): identidad per-repo (D1) — LOGIN-T1..T3, PKP-H1..H4 y precedencia state-branch - #167
Merged
Conversation
…igner Guard en createSignedRecord y signRecord: notes es parte del digest de firma y el schema del protocolo exige minLength:1 — un notes vacio produce records que firman OK pero fallan lint --strict (corrupcion silenciosa que bloquea sync push; origen: 9 FeedbackRecords corruptos en QA). Validacion pre-sign: el keyProvider nunca llega a invocarse. 9/9 tests.
Bloque 2 de agent_key_per_repo_model (D1: human keys per-org, agent keys PER-REPO — RFC-02 §6.2): - Constructor gana repoId? opcional. agent:* con scope resuelve/persiste por (orgId, repoId, actorId); human:* ignora el scope (per-org, filas NULL). - storeKey: el archive/delete transaccional queda scoped por repo — rotar la key de un agente en un repo NUNCA archiva ni sobrescribe la de otro repo. - H4: agente SIN scope filtra repoId null (solo filas legacy) — sin esto, un provider unscoped resolveria la key repo-scoped de un repo arbitrario (findFirst sin orderBy): no-determinismo silencioso. Las keys repo-scoped solo son alcanzables via provider scoped. - Mock delegate espeja la semantica PG del cuadruple (NULLs distintos). - audit_types.test.ts: import sin uso eliminado (tsc 0). Gate: PKP 33/33 + KV e2e 12/12.
Bloque 3 de agent_key_per_repo_model (D1): gitgov login sube las agent:* keys locales del repo junto con la humana, en el mismo request identity.syncKey — cada entrada con su propio ephemeral keypair ECDH contra la server key. - Enumeracion desde los ActorRecords del REPO (FsRecordStore + DEFAULT_ID_ENCODER sobre <worktree>/.gitgov/actors) — nunca decodificando filenames del keys dir (el mapping :/ -> _ es lossy) ni leyendo un directorio global. - Hardening: log + skip por entrada — una key ilegible (permisos, corrupcion) no deja rehen al login; las demas y la humana siguen (simetrico con IDS-N2). - Sin agent keys locales: agentKeys se omite (request identico al actual). - Reporta agentKeysSynced de la respuesta. Gate: 46 tests + 1 todo, tsc 0, KS9-KS12 e2e 6/6.
…path humano s78b-15: gap descubierto por ejecucion real — el path already-synced del login (key humana local == SaaS) retornaba sin subir agent keys, dejando a los repos post-succession sin converger jamas. Fix simetrico al diseno existente (status → decidir → sync), aprobado por revision: - En todo login se computa el diff entre los agent keypairs locales y keyStatus.agentKeys (las publicKeys repo-scoped del server, IDS-O1); solo las entradas ausentes o distintas viajan como agentKeys[]. - Path already-synced: el diff sube via identity.syncKey en modo verify-only (publicKey humana actual SIN privateKeyEnvelope, IKS-G3) — la key humana no se retransmite nunca. Diff vacio → cero llamadas extra, request identico. - Amendment LOGIN-T1: se sube el DIFF, no todo agent:* local — mata la clase del bug: cada mitad (humana/agentes) se evalua por su cuenta. Gate: login-command 49 passed + 1 todo, tsc 0, cero casts nuevos.
…(s78) - agent_discovery_module: 3 functions (discoverInstalledAgents, mergeAgentSources, packageToAgentRecord), 8 EARS DISC-A1..C3, 8 tests - AORCH-P8: npm install suggestion when agent MODULE_NOT_FOUND - AORCH-P9: list discovered-but-unregistered agents post-audit - INIT-M1: agent status display post-init (installed/not-installed) - INIT-M2: informational status for unresolved default agents - Fix: stateBranchOverride defaults to 'gitgov-state' instead of throw (fixes 12 E2E failures in init/sync commands) - Core barrel: export discoverInstalledAgents 615 CLI tests green, 3063 core tests green.
Private: agent_platform roadmap/overview updated, 7 specs with new EARS, recall #3 for handoff, device_security epic docs, agent_contract_reference, multi_agent_audit_flow spec, input lifecycle folders. Core: LLM resolve test improvements (other agent stream). E2E: protocol.prisma sync (other agent stream).
…repo flow (other agent)
…ded canonical key Tercer y ultimo camino del login que faltaba cubrir. LOGIN-T2 (s78b-15) dejo el diff de agent keys en dos de los tres: already-synced y upload humano. --force-cloud retornaba tras descargar la key sin tocar el diff. No es un edge case: es el camino CORRECTO del escenario mas comun. Un dev que hace gitgov init en una maquina nueva mintea un keypair humano nuevo, la org ya tiene canonica, y el login entra en conflicto LOGIN-D2; adoptar la canonica es lo que corresponde porque el SaaS es la autoridad de la key humana. Con el gap, ese repo nunca recibia sus agent keys y los scans fallaban hasta que algun login futuro tomara otro camino por accidente. Detalle critico: el publicKey del syncKey verify-only es la canonica RECIEN DESCARGADA, re-leida del provider despues del download — con la local stale el server ve mismatch sin envelope y responde CONFLICT. El upload es no fatal: si falla, warn y seguir; el login, funcion primaria, ya quedo resuelto. Incluye la eliminacion de fondo de los 2 casts pre-existentes del test: - as unknown as IKeyProvider tapaba un mock incompleto (4 de 6 metodos) — se completaron sign() y deletePrivateKey() y se sello con satisfies. - as never de process.exit resuelto con mockImplementation(vi.fn()). Descubierto ejecutando el gate del flow AKR (e2e-private, Bloque 6). Gate: login CLI 51+1todo, tsc 0, cero casts en el archivo.
…nch override precedence El registro de comandos construia el projector de forma ansiosa (index.ts:60), lo que disparaba initializeStores() -> bootstrapWorktree() ANTES de que Commander parseara ninguna opcion. Con `origin/gitgov-state` presente en el repo, el bootstrap ataba el worktree al DEFAULT y lo persistia en config.json: `gitgov init --state-branch <custom>` heredaba actores y estado de otro proyecto SIN error, y la contaminacion quedaba para todos los comandos siguientes. LOGIN-P1 estaba expuesto al mismo patron: su override tambien se setea despues del registro. Medido con GIT_TRACE: `git branch gitgov-state origin/gitgov-state` + `git worktree add ... gitgov-state` disparados desde setupCommands, con override=null e initMode=false; misma instancia del singleton en las 5 llamadas — era ORDEN, no identidad. Repro A/B/C: la unica variable es `git fetch origin`; sin el, el bootstrap falla y el flujo cae al camino sano, por eso el bug era invisible en repos limpios y aparecia en cualquier repo real. El fix es estructural, no de orden afortunado: el registro recibe un thunk y la resolucion ocurre en el handler, cuando el comando ya fijo su override. Barrido de la clase: setupCommands quedo sin un solo await y ninguno de los 16 register* recibe instancias construidas. Specs (privado): INIT-L3, EARS-C3, EARS-C15 amendment, EARS-B2 y WTSYNC-A5/A6. Tests: unit `[EARS-C3]` (el primer intento fue falso verde — pasaba contra el codigo roto; el assert que discrimina es que el handler SI resuelve al ejecutar) + e2e `[INIT-L3]` con bare remote local, sin GitHub, 5s, CI-friendly. Gates: unit C3 + e2e INIT-L3 + CLI 621 passed / 31 archivos + tsc 0 + D/E/F 3/3 recuperados sin tocar sus tests.
… Task 1.2d, recalls 91/92)
…tras sesiones) Material de texto que estaba sin versionar en la raiz del monorepo: - inputs/ (8): adr_no_sigstore, kms_migration, landing_repositioning, marketplace_sandbox, pilot_validation, release_v1, triad_oss_launch, verify_command_alias - landing.md, PLATFORM_REVIEW.md, gitgov_agent_prompt.md, gitgov_content_map_diagram.md - .github/workflows/gitgov.yml — workflow CI - .gitignore: +packages/triad/ y packages/triad_pi_agent/ (plugin instalado, futuro submodulo) · .claudeignore Sin secretos: verificado contra patrones ghp_/github_pat_/AKIA/BEGIN PRIVATE KEY/xox*. Quedan FUERA por decision explicita — ver reporte de la sesion: screenshots de debugging, cli-main/ (proyecto ajeno), .playwright-mcp/ (artefactos de herramienta), packages/iatoolkit y packages/forge_apps (repos git independientes, y iatoolkit tiene un .env real).
Los 8 inputs de producto de la raiz entraron por error en 6db1ffc. Se sacan del tracking y quedan en disco (sin borrar) para que su dueño decida donde viven. El resto de ese commit (workflow CI, .gitignore, .claudeignore y los 4 docs de raiz) se mantiene.
No corresponden al versionado del monorepo (decision de Camilo). Los archivos siguen en disco, solo salen del control de versiones. Del commit anterior quedan versionados: inputs/ (8 inputs de producto), .github/workflows/gitgov.yml y .gitignore.
No debe ir versionado en el monorepo (decision de Camilo, s78b-34). El archivo queda en el working tree como untracked — no se borra del disco.
…-F1..F4, Task 1.2d, recalls 91/92)" This reverts commit 23d388a.
|
🎉 This PR is included in version 3.21.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Public side of the
mvp_blockersepic (s78/s78b). Companion to gitgovernance/private#42, which carries the specs, the SaaS side and the private E2E suite.What's in
Per-repo identity (decision D1: human keys per-org, agent keys PER-REPO):
PKP-H1..H4— repo scope inPrismaKeyProvider(core)LOGIN-T1— login uploads the repo's agent keys in the samesyncKeyrequestLOGIN-T2— the decision to upload agent keys becomes independent of the human path (local-vs-server diff); closed the already-synced gap, where a post-succession repo would never convergeLOGIN-T3—--force-clouduploads the diff too, using the freshly downloaded canonical publicKey (with the stale one the server answers CONFLICT). This is the correct path for the most common scenario: a dev runninginiton a new machine against an org that already has a canonical keyRSIG-A5—RecordSignerrejects empty or whitespace-only notes--state-branchprecedence (Task 1.2d) — product bug surfaced by the smoke:Command registration built the projector eagerly (
index.ts:60), which triggeredinitializeStores()→bootstrapWorktree()before Commander had parsed any option. Withorigin/gitgov-statepresent, the worktree ended up bound to the DEFAULT and that got persisted intoconfig.json:gitgov init --state-branch <custom>inherited another project's actors and state with no error, and the contamination stuck for every subsequent command.LOGIN-P1was exposed to the same pattern.Root cause measured with
GIT_TRACE(same singleton instance across all 5 calls: it was ordering, not identity). The fix is structural: registration receives a thunk and resolution happens in the handler, once the command has set its override — it no longer depends on who runs first. Class sweep:setupCommandsis left without a singleawait, and none of the 16register*calls receive pre-built instances.Tests
[EARS-C3]unit incli/src/commands/indexer/indexer.test.ts[INIT-L3]e2e ine2e/tests/init_state_branch.test.ts— local bare remote, no GitHub, ~5s, CI-friendlyGates: CLI 621 passed / 31 files · tsc 0 · zero forbidden casts in touched files · the 3 private flows that were red from this cause (
sync_state,golden_path,webhook_recovery) recovered 3/3 without touching their tests — they had been right since gitgov_2.Reviewer note
The
packages/privatepointer is NOT bumped in this PR (the bump was made and deliberately reverted: that call is Camilo's, handled separately). The matching content is pushed in the private repo; without the bump, this branch references a commit that predates those specs.