@orkastery/cli 0.6.4 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,6 +39,8 @@ exports.reconciliarSessaoSemWatcher = reconciliarSessaoSemWatcher;
39
39
  exports.validarRegistroWatcher = validarRegistroWatcher;
40
40
  exports.validarFonteWatcher = validarFonteWatcher;
41
41
  exports.prepararFonteWatcher = prepararFonteWatcher;
42
+ exports.esperarProntidaoWatcher = esperarProntidaoWatcher;
43
+ exports.esperarConfirmacaoDoPaiWatcher = esperarConfirmacaoDoPaiWatcher;
42
44
  exports.iniciarWatcher = iniciarWatcher;
43
45
  exports.acompanharSessao = acompanharSessao;
44
46
  /** D6: observador restrito a uma sessão, cursor canônico e resultados idempotentes. */
@@ -62,6 +64,7 @@ const session_watcher_grok_1 = require("./session-watcher-grok");
62
64
  const conta_esgotada_1 = require("./conta-esgotada");
63
65
  const trabalho_thread_1 = require("./trabalho-thread");
64
66
  const pedido_conducao_1 = require("./pedido-conducao");
67
+ const codex_task_complete_1 = require("./codex-task-complete");
65
68
  /** O gate estreito da própria sessão vence diagnósticos genéricos do watcher. */
66
69
  function pedidoDaConducao(eventos, sessionId, despachoEm) {
67
70
  const evento = eventos.filter(e => e.tipo === 'gate_blocked' && e.origem === 'mcp.request_conduction' &&
@@ -389,6 +392,16 @@ function observarSessao(carregado, sessionId, opcoes = {}) {
389
392
  return observarSessaoResolvida(carregado, sessionId, resolvida, opcoes);
390
393
  }
391
394
  catch (e) {
395
+ if (e.code === 'SESSION_STATE_SPLIT' && threadId) {
396
+ const thread = (0, thread_1.lerThread)(carregado.raiz, threadId);
397
+ const registrada = thread.sessoes.find(s => s.sessionId === sessionId);
398
+ if (registrada) {
399
+ const sessao = (0, session_events_1.exigirSessaoDespachada)(registrada, sessionId);
400
+ const prova = (0, codex_task_complete_1.taskCompleteFixadoDaSessao)(carregado.raiz, thread, sessao, (0, ledger_1.lerLedger)((0, thread_1.dirThread)(carregado.raiz, threadId)), opcoes.agoraMs ?? Date.now());
401
+ if (prova)
402
+ return observarSessaoResolvida(carregado, sessionId, { thread, sessao }, { ...opcoes, taskCompleteFixado: prova });
403
+ }
404
+ }
392
405
  if (threadId)
393
406
  tentarRegistrarErroWatcher(carregado.raiz, threadId, sessionId, e);
394
407
  throw e;
@@ -542,7 +555,8 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
542
555
  return desfazer();
543
556
  if (sessao.runtime === 'claude-bg') {
544
557
  const r = comLockWatcher(pasta, () => (0, session_watcher_claude_1.observarClaudeSobLock)({ raiz: carregado.raiz,
545
- thread, sessao, dir, pasta, chave, agoraMs: now, limiteMs: exports.LIMITE_MORTE_MS, consulta: opcoes.consultaClaude, carregado })) ?? { ...base, ocupado: true };
558
+ thread, sessao, dir, pasta, chave, agoraMs: now, limiteMs: exports.LIMITE_MORTE_MS, consulta: opcoes.consultaClaude,
559
+ tela: opcoes.telaClaude, carregado })) ?? { ...base, ocupado: true };
546
560
  return (0, ledger_1.threadPresente)(dir) ? r : desfazer();
547
561
  }
548
562
  // RM-067 (F2): o grok tem observador próprio (stream, recibo do supervisor e pasta da sessão), no mesmo lock.
@@ -565,6 +579,7 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
565
579
  const perfil = (0, runtime_profiles_1.perfilDoRegistro)(eventos, sessao);
566
580
  // Fonte do controller validada ANTES de ler rollout ou escrever cursor.
567
581
  let fonteControlador = null, snapshot = null;
582
+ let taskCompleteFixado = opcoes.taskCompleteFixado ?? null;
568
583
  let cwdDespacho;
569
584
  if (registro?.controlador) {
570
585
  const despacho = eventos.filter(e => e.tipo === 'phase_dispatch' && e.sessionId === sessionId).at(-1);
@@ -580,7 +595,16 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
580
595
  vinculo: { thread: thread.id, fase: sessao.fase, promptSha256: sessao.promptSha256 }
581
596
  });
582
597
  // Relógio injetado vale como está; o real é lido pelo sensor depois do state.json.
583
- snapshot = (0, codex_controller_sensor_1.lerSnapshotController)(fonteControlador, opcoes.agoraMs === undefined ? {} : { agoraMs: now });
598
+ try {
599
+ snapshot = (0, codex_controller_sensor_1.lerSnapshotController)(fonteControlador, opcoes.agoraMs === undefined ? {} : { agoraMs: now });
600
+ }
601
+ catch (e) {
602
+ // A varredura integral e excepcional: no fluxo saudável o cursor incremental
603
+ // reconhece task_complete sem reler todo o rollout a cada polling.
604
+ taskCompleteFixado ??= (0, codex_task_complete_1.taskCompleteFixadoDaFonte)(fonteControlador, now);
605
+ if (!taskCompleteFixado)
606
+ throw e;
607
+ }
584
608
  }
585
609
  const file = path.join(pasta, 'cursor.json');
586
610
  const cursor = lerJson(file) ?? { schema: 'ork.session-cursor/v1', sessionId,
@@ -590,7 +614,7 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
590
614
  // Cursor anterior media bytes, nao progresso: migracao conservadora desde o despacho.
591
615
  cursor.progressoEm ??= Date.parse(sessao.despachadaEm);
592
616
  // Transporte do controller não admite descoberta nem override de rollout.
593
- const fontes = { stream: registro?.logPath, rollout: snapshot ? snapshot.rollout
617
+ const fontes = { stream: registro?.logPath, rollout: fonteControlador ? fonteControlador.rollout
594
618
  : opcoes.rollout === undefined ? (0, codex_1.acharRollout)(sessionId, 3, (0, codex_1.casaDoCodex)(perfil)) : opcoes.rollout };
595
619
  let bytesLidos = 0, temMais = false, cresceu = false;
596
620
  for (const [nome, fonte] of Object.entries(fontes)) {
@@ -673,7 +697,10 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
673
697
  return tempo || (y.numeroTurno ?? 0) - (x.numeroTurno ?? 0) ||
674
698
  ordem[x.classificacao] - ordem[y.classificacao] || a.nome.localeCompare(b.nome);
675
699
  });
676
- let terminal = terminais[0]?.terminal;
700
+ const taskCompleteDoCursor = fonteControlador ? terminais.find(({ nome, terminal: atual }) => nome === 'rollout' && atual.fonte === 'rollout.task_complete')?.terminal : undefined;
701
+ let terminal = taskCompleteFixado?.terminal ?? taskCompleteDoCursor ?? terminais[0]?.terminal;
702
+ const taskCompleteAutenticado = terminal !== undefined &&
703
+ (terminal === taskCompleteFixado?.terminal || terminal === taskCompleteDoCursor);
677
704
  const fontesAtuais = Object.values(cursor.fontes);
678
705
  const aberto = fontesAtuais.some(f => {
679
706
  if (!f.parser.turnoAberto)
@@ -700,22 +727,23 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
700
727
  const invalido = fontesAtuais.some(f => f.parser.invalido);
701
728
  // Conclusão nativa é medida em todo terminal, independente da política de retry:
702
729
  // bloqueio sem retry preserva o motivo, mas não prova que o turno do runtime terminou.
703
- const provaNativa = snapshot && terminal
730
+ const provaNativa = taskCompleteAutenticado ? null : snapshot && terminal
704
731
  ? semProvaNativa(snapshot, terminal, terminais[0]?.nome, fechamento, sessionId, cwdDespacho)
705
732
  : 'terminal nativo do controller ausente';
706
733
  // Parecer negativo também exige conclusão nativa inteira: texto claims.failed de
707
734
  // turno interrompido não pode virar rejeição autenticada de um objetivo.
708
- const semProva = snapshot && terminal && terminal.classificacao !== 'human.pending' && !bloqueioPreservado(terminal)
735
+ const semProva = !taskCompleteAutenticado && snapshot && terminal && terminal.classificacao !== 'human.pending' && !bloqueioPreservado(terminal)
709
736
  ? provaNativa : null;
710
737
  const descorrelacionado = !!semProva;
711
- const incompleto = semCloseNoLimite && (!!terminal || estado !== 'ausente' || aberto || parcial || invalido) || !backlog && (((parcial || aberto || invalido || descorrelacionado || !terminal) && !!fechamento) ||
738
+ const observacaoInvalida = !taskCompleteAutenticado && (invalido || aberto || descorrelacionado);
739
+ const incompleto = !taskCompleteAutenticado && (semCloseNoLimite && (!!terminal || estado !== 'ausente' || aberto || parcial || invalido) || !backlog && (((parcial || aberto || invalido || descorrelacionado || !terminal) && !!fechamento) ||
712
740
  (estavel && (parcial || (aberto && estado === 'ausente') ||
713
- (supervisionado && estado === 'desconhecido'))));
741
+ (supervisionado && estado === 'desconhecido')))));
714
742
  const morte = estado === 'ausente' && estavel && !temMais;
715
743
  // Fechamento supervisionado precisa existir antes de associar o código de saída ao turno.
716
- const podeTerminar = !esperaHumana && (incompleto || (!temMais && !aberto &&
744
+ const podeTerminar = taskCompleteAutenticado || !esperaHumana && (incompleto || (!temMais && !aberto &&
717
745
  ((terminal && (fechamento || !supervisionado)) || morte)));
718
- const conta = falhaDeContaDoTerminal(terminal, !invalido && !aberto, snapshot, now);
746
+ const conta = falhaDeContaDoTerminal(terminal, taskCompleteAutenticado || !invalido && !aberto, snapshot, now);
719
747
  (0, codex_runner_1.gravarAtomico)(file, cursor);
720
748
  return (0, session_events_1.comLockDeSessao)(dir, () => {
721
749
  const atuais = (0, ledger_1.lerLedger)(dir);
@@ -735,9 +763,9 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
735
763
  const pedido = pedidoDaConducao(atuais, sessionId, sessao.despachadaEm);
736
764
  const semTerminal = !terminal;
737
765
  // Fato registrado para a autoridade de revisão: `null` só com terminal nativo correlacionado.
738
- const conclusaoNativa = incompleto || invalido || aberto || descorrelacionado
766
+ const conclusaoNativa = incompleto || observacaoInvalida
739
767
  ? provaNativa ?? 'observação incompleta ou inválida' : provaNativa;
740
- if (incompleto || invalido || aberto || descorrelacionado)
768
+ if (incompleto || observacaoInvalida)
741
769
  terminal = { classificacao: 'gate_blocked', motivo: 'runtime.unavailable',
742
770
  timestamp: new Date(now).toISOString(), turnId: null, fonte: semProva ?? 'observação incompleta ou inválida',
743
771
  tokens: new codex_events_1.ParserCodex().estado.tokens, duracaoMs: null };
@@ -745,12 +773,12 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
745
773
  terminal = { classificacao: 'gate_blocked', motivo: 'runtime.silencio',
746
774
  timestamp: new Date(now).toISOString(), turnId: null, fonte: 'processo ausente e arquivos estáveis',
747
775
  tokens: new codex_events_1.ParserCodex().estado.tokens, duracaoMs: null };
748
- if (pedido && (terminal.classificacao === 'fase_concluida' ||
776
+ if (pedido && (taskCompleteAutenticado || terminal.classificacao === 'fase_concluida' ||
749
777
  terminal.classificacao === 'gate_blocked' && ['runtime.unavailable', 'runtime.silencio'].includes(terminal.motivo ?? ''))) {
750
778
  terminal = { ...terminal, classificacao: 'gate_blocked', motivo: pedido.motivo,
751
779
  fonte: `mcp.request_conduction: ${pedido.detalhe}` };
752
780
  }
753
- if (fechamento && (fechamento.exitCode !== 0 || fechamento.signal || fechamento.erro) &&
781
+ if (!taskCompleteAutenticado && fechamento && (fechamento.exitCode !== 0 || fechamento.signal || fechamento.erro) &&
754
782
  terminal.classificacao !== 'human.pending' && !bloqueioPreservado(terminal)) {
755
783
  terminal = { ...terminal, classificacao: 'gate_blocked', motivo: 'runtime.unavailable' };
756
784
  }
@@ -761,16 +789,19 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
761
789
  fonte: `${conta.fonte}: ${conta.falha.motivo} (${conta.falha.trecho})` };
762
790
  const respostaFinal = terminal.respostaFinal
763
791
  ? (0, trabalho_thread_1.gravarRespostaFinalDaSessao)(carregado.raiz, thread.id, sessionId, terminal.respostaFinal) : null;
792
+ const taskCompleteBloqueado = taskCompleteAutenticado && terminal.classificacao !== 'fase_concluida';
764
793
  const dados = { fase: sessao.fase, sessionId, despachoEm: sessao.despachadaEm, sensorResultId: resultadoId,
765
794
  classificacao: terminal.classificacao, motivo: terminal.motivo, runtime: 'codex', fonte: terminal.fonte,
766
- fonteVencedora: pedido || incompleto || invalido || aberto || descorrelacionado ? null : terminais[0]?.nome ?? null,
795
+ fonteVencedora: pedido ? null : taskCompleteAutenticado ? 'rollout'
796
+ : incompleto || invalido || aberto || descorrelacionado ? null : terminais[0]?.nome ?? null,
767
797
  turnId: terminal.turnId, numeroTurno: terminal.numeroTurno ?? null,
768
798
  tokens: terminal.tokens, exitCode: fechamento?.exitCode ?? null, exitCodeFonte: fechamento?.fonte ?? 'unavailable',
769
799
  signal: fechamento?.signal ?? null, duracaoMs: fechamento?.duracaoMs ?? terminal.duracaoMs,
770
800
  duracaoFonte: fechamento?.fonte ?? (terminal.duracaoMs === null ? 'unavailable' : terminal.fonte),
771
- ok: terminal.classificacao === 'fase_concluida' && fechamento?.exitCode === 0,
772
- conclusaoNativa: conclusaoNativa === null,
773
- conclusaoNativaAusente: conclusaoNativa,
801
+ ok: terminal.classificacao === 'fase_concluida' && (taskCompleteAutenticado || fechamento?.exitCode === 0),
802
+ conclusaoNativa: conclusaoNativa === null && !taskCompleteBloqueado,
803
+ conclusaoNativaAusente: taskCompleteBloqueado
804
+ ? 'task_complete encerrou o turno com resultado bloqueado' : conclusaoNativa,
774
805
  estado: terminal.classificacao === 'fase_concluida' ? 'concluida' : 'bloqueada',
775
806
  observadoEm: terminal.timestamp, gate: 'phase.dispatch', origem: 'sessions.watch',
776
807
  morteLatenciaMs: morte ? now - cursor.progressoEm : null, ...(perfil ? { perfil } : {}),
@@ -877,6 +908,61 @@ function prepararFonteWatcher(raiz, sessionId, fonte) {
877
908
  throw e;
878
909
  }
879
910
  }
911
+ class ErroDoApertoWatcher extends Error {
912
+ encerrarOutro;
913
+ constructor(message, encerrarOutro) {
914
+ super(message);
915
+ this.encerrarOutro = encerrarOutro;
916
+ }
917
+ }
918
+ const TETO_ABSOLUTO_APERTO_WATCHER_MS = 60000;
919
+ const PAUSA_APERTO_WATCHER = new Int32Array(new SharedArrayBuffer(4));
920
+ function conferirTetoDoAperto(tetoMs) {
921
+ if (!Number.isFinite(tetoMs) || tetoMs <= 0)
922
+ throw new Error('teto invalido para o aperto de mao do watcher');
923
+ }
924
+ /** Lado do dispatcher: lentidao nao encerra um watcher cuja identidade continua viva. */
925
+ function esperarProntidaoWatcher(lerSinal, identidade, opcoes = {}) {
926
+ const agora = opcoes.agora ?? Date.now;
927
+ const estado = opcoes.estado ?? codex_runner_1.estadoProcesso;
928
+ const pausar = opcoes.pausar ?? ((ms) => { Atomics.wait(PAUSA_APERTO_WATCHER, 0, 0, ms); });
929
+ const tetoProntidaoMs = opcoes.tetoMs ?? TETO_ABSOLUTO_APERTO_WATCHER_MS;
930
+ conferirTetoDoAperto(tetoProntidaoMs);
931
+ const inicio = agora();
932
+ for (;;) {
933
+ if (agora() - inicio >= tetoProntidaoMs)
934
+ throw new ErroDoApertoWatcher('watcher sem prontidao confirmada', true);
935
+ const sinal = lerSinal();
936
+ if (sinal === 'confirmado')
937
+ return;
938
+ if (sinal === 'invalido')
939
+ throw new ErroDoApertoWatcher('watcher com prontidao invalida', true);
940
+ if (estado(identidade) === 'ausente')
941
+ throw new ErroDoApertoWatcher('watcher sem prontidao confirmada', false);
942
+ const restante = tetoProntidaoMs - (agora() - inicio);
943
+ pausar(Math.min(10, Math.max(1, restante)));
944
+ }
945
+ }
946
+ /** Lado do watcher: o ack continua pendente somente enquanto o pai capturado na partida nao saiu. */
947
+ async function esperarConfirmacaoDoPaiWatcher(lerSinal, identidadePai, opcoes = {}) {
948
+ const agora = opcoes.agora ?? Date.now;
949
+ const estado = opcoes.estado ?? codex_runner_1.estadoProcesso;
950
+ const pausar = opcoes.pausar ?? ((ms) => new Promise(resolve => setTimeout(resolve, ms)));
951
+ const tetoConfirmacaoMs = opcoes.tetoMs ?? TETO_ABSOLUTO_APERTO_WATCHER_MS;
952
+ conferirTetoDoAperto(tetoConfirmacaoMs);
953
+ const inicio = agora();
954
+ for (;;) {
955
+ if (agora() - inicio >= tetoConfirmacaoMs)
956
+ throw new ErroDoApertoWatcher('watcher sem confirmacao do pai', false);
957
+ const sinal = lerSinal();
958
+ if (sinal === 'confirmado')
959
+ return;
960
+ if (sinal === 'invalido' || estado(identidadePai) === 'ausente')
961
+ throw new ErroDoApertoWatcher('watcher sem confirmacao do pai', false);
962
+ const restante = tetoConfirmacaoMs - (agora() - inicio);
963
+ await pausar(Math.min(10, Math.max(1, restante)));
964
+ }
965
+ }
880
966
  function iniciarWatcher(raiz, sessionId) {
881
967
  const { thread, sessao: registrada } = (0, session_events_1.resolverSessao)(raiz, sessionId);
882
968
  const sessao = (0, session_events_1.exigirSessaoDespachada)(registrada, sessionId);
@@ -918,27 +1004,29 @@ function iniciarWatcher(raiz, sessionId) {
918
1004
  try {
919
1005
  if (!identidade)
920
1006
  throw new Error('watcher sem identidade de processo');
921
- const deadline = Date.now() + 5000;
922
- const pausa = new Int32Array(new SharedArrayBuffer(4));
923
- while (Date.now() < deadline) {
924
- const ready = lerJson(readyPath);
925
- if (ready && ready.token === token && ready.sessionId === sessionId && ready.despachoEm === sessao.despachadaEm &&
926
- JSON.stringify(ready.identidade) === JSON.stringify(identidade) && (0, codex_runner_1.estadoProcesso)(identidade) === 'vivo') {
927
- (0, ledger_1.registrar)(dir, thread.id, 'session_watcher_started', { fase: sessao.fase, sessionId,
928
- despachoEm: sessao.despachadaEm, pid: identidade.pid, identidade, readyPath, token });
929
- (0, codex_runner_1.gravarAtomico)(ackPath, { token });
930
- return identidade.pid;
1007
+ esperarProntidaoWatcher(() => {
1008
+ if (!fs.existsSync(readyPath))
1009
+ return 'pendente';
1010
+ let ready;
1011
+ try {
1012
+ ready = lerJson(readyPath);
931
1013
  }
932
- if ((0, codex_runner_1.estadoProcesso)(identidade) === 'ausente')
933
- break;
934
- Atomics.wait(pausa, 0, 0, 10);
935
- }
936
- throw new Error('watcher sem prontidao confirmada');
1014
+ catch {
1015
+ return 'invalido';
1016
+ }
1017
+ return ready && ready.token === token && ready.sessionId === sessionId && ready.despachoEm === sessao.despachadaEm &&
1018
+ JSON.stringify(ready.identidade) === JSON.stringify(identidade) && (0, codex_runner_1.estadoProcesso)(identidade) === 'vivo'
1019
+ ? 'confirmado' : 'invalido';
1020
+ }, identidade);
1021
+ (0, ledger_1.registrar)(dir, thread.id, 'session_watcher_started', { fase: sessao.fase, sessionId,
1022
+ despachoEm: sessao.despachadaEm, pid: identidade.pid, identidade, readyPath, token });
1023
+ (0, codex_runner_1.gravarAtomico)(ackPath, { token });
1024
+ return identidade.pid;
937
1025
  }
938
1026
  catch (e) {
939
1027
  falha();
940
- // Encerrar apenas o filho cuja identidade continua sendo a capturada no spawn.
941
- if (identidade && (0, codex_runner_1.estadoProcesso)(identidade) === 'vivo')
1028
+ // Lentidao dentro do teto nao encerra o filho; so teto ou prontidao invalida autorizam o sinal.
1029
+ if (e instanceof ErroDoApertoWatcher && e.encerrarOutro && identidade && (0, codex_runner_1.estadoProcesso)(identidade) === 'vivo')
942
1030
  child.kill('SIGTERM');
943
1031
  throw e;
944
1032
  }
@@ -949,18 +1037,30 @@ async function acompanhar() {
949
1037
  try {
950
1038
  const readyPath = process.argv[5], token = process.argv[6];
951
1039
  if (readyPath && token) {
1040
+ const identidadePai = (0, codex_runner_1.identidadeProcesso)(process.ppid);
1041
+ if (!identidadePai)
1042
+ throw new Error('watcher sem confirmacao do pai');
952
1043
  validarRegistroWatcher(carregado.raiz, sessionId);
953
1044
  const sessao = (0, session_events_1.exigirSessaoDespachada)((0, session_events_1.resolverSessao)(carregado.raiz, sessionId, threadId).sessao, sessionId);
954
1045
  const identidade = (0, codex_runner_1.identidadeProcesso)(process.pid);
955
1046
  if (!identidade)
956
1047
  throw new Error('watcher sem identidade propria');
957
1048
  (0, codex_runner_1.gravarAtomico)(readyPath, { token, sessionId, despachoEm: sessao.despachadaEm, identidade });
958
- const deadline = Date.now() + 5000;
959
- while (lerJson(readyPath + '.ack')?.token !== token) {
960
- if (Date.now() >= deadline)
961
- throw new Error('watcher sem confirmacao do pai');
962
- await new Promise(r => setTimeout(r, 10));
963
- }
1049
+ await esperarConfirmacaoDoPaiWatcher(() => {
1050
+ if (!(0, ledger_1.threadPresente)((0, thread_1.dirThread)(carregado.raiz, threadId)))
1051
+ return 'invalido';
1052
+ const ackPath = readyPath + '.ack';
1053
+ if (!fs.existsSync(ackPath))
1054
+ return 'pendente';
1055
+ let ack;
1056
+ try {
1057
+ ack = lerJson(ackPath);
1058
+ }
1059
+ catch {
1060
+ return 'invalido';
1061
+ }
1062
+ return ack?.token === token ? 'confirmado' : 'invalido';
1063
+ }, identidadePai);
964
1064
  }
965
1065
  await acompanharSessao(carregado, sessionId, { threadId });
966
1066
  }
package/dist/ship.js CHANGED
@@ -51,6 +51,7 @@ var __importStar = (this && this.__importStar) || (function () {
51
51
  })();
52
52
  Object.defineProperty(exports, "__esModule", { value: true });
53
53
  exports.MODOS_QUE_AUTORIZAM_PUSH = void 0;
54
+ exports.verificacaoCompletaParaShip = verificacaoCompletaParaShip;
54
55
  exports.commitQueIncorporou = commitQueIncorporou;
55
56
  exports.autorizacaoDePush = autorizacaoDePush;
56
57
  exports.arvoreComBranch = arvoreComBranch;
@@ -88,6 +89,10 @@ const mcp_ship_transporte_1 = require("./mcp-ship-transporte");
88
89
  const branch_de_estado_1 = require("./branch-de-estado");
89
90
  const locale_1 = require("./locale");
90
91
  const noctum_guarda_1 = require("./noctum-guarda");
92
+ /** RM-081 F3: `ok` nunca basta quando a rodada nao cobriu a suite inteira. */
93
+ function verificacaoCompletaParaShip(verificacao) {
94
+ return verificacao.ok && !verificacao.parcial;
95
+ }
91
96
  /**
92
97
  * RM-037 (fatia 5, A1): o commit de primeiro pai da base que incorporou o `shaDe`: o merge que trouxe a branch, ou
93
98
  * o proprio `shaDe` quando a base avancou por fast-forward ate ele. Com a branch ja incorporada, a ponta da base e
@@ -556,9 +561,10 @@ function ship(carregado, threadId, opcoes) {
556
561
  const verificacao = (0, verify_1.verificar)(carregado, threadId, { executor: opcoes.executorVerify,
557
562
  conducao: { filaPesada: { ...opcoes.avisoDaFilaPesada, esperarMs: opcoes.esperarFilaPesadaMs } } });
558
563
  r.verificacao = verificacao;
559
- if (!verificacao.ok) {
560
- const motivo = verificacao.motivos.find((m) => m !== 'claims.unverifiable');
561
- return bloquear(motivo, (0, locale_1.textoDoCatalogo)((m) => m.ship.verificacaoReprovou(verificacao.commit, verificacao.motivos.join(', '))), (0, locale_1.textoDoCatalogo)((m) => m.ship.rodeVerify(threadId)));
564
+ if (!verificacaoCompletaParaShip(verificacao)) {
565
+ const motivo = (verificacao.parcial ? 'verify.failed'
566
+ : verificacao.motivos.find((m) => m !== 'claims.unverifiable'));
567
+ return bloquear(motivo, (0, locale_1.textoDoCatalogo)((m) => m.ship.verificacaoReprovou(verificacao.commit, verificacao.parcial ? 'verify.parcial' : verificacao.motivos.join(', '))), (0, locale_1.textoDoCatalogo)((m) => m.ship.rodeVerify(threadId)));
562
568
  }
563
569
  // Referencias locais, resolvidas antes de tomar o lease.
564
570
  const shaDe = shaDaRef(raiz, `refs/heads/${de}`);