@orkastery/cli 0.2.0 → 0.4.1

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.
Files changed (257) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +86 -65
  3. package/adapters/README.md +30 -15
  4. package/adapters/claude-code/.claude-plugin/plugin.json +11 -5
  5. package/adapters/claude-code/README.md +110 -8
  6. package/adapters/claude-code/commands/check.md +4 -3
  7. package/adapters/claude-code/commands/goal.md +3 -2
  8. package/adapters/claude-code/commands/master.md +3 -2
  9. package/adapters/claude-code/commands/onboarding.md +21 -0
  10. package/adapters/claude-code/commands/ork.md +99 -27
  11. package/adapters/claude-code/hooks/hooks.json +6 -1
  12. package/adapters/claude-code/hooks/ork-guard.js +1 -1
  13. package/adapters/claude-code/hooks/ork-sensor.js +66 -0
  14. package/adapters/codex/skills/ork/SKILL.md +57 -0
  15. package/adapters/hermes/README.md +161 -0
  16. package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
  17. package/adapters/hermes/bin/ork-brain.sh +4 -0
  18. package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
  19. package/adapters/hermes/bin/ork-maestro.sh +6 -0
  20. package/adapters/hermes/bin/ork-master-enviar.py +84 -0
  21. package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
  22. package/adapters/hermes/hermes.plugin.json +24 -4
  23. package/adapters/hermes/hitl-ingress/__init__.py +356 -0
  24. package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
  25. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
  26. package/adapters/openclaw/README.md +136 -27
  27. package/adapters/openclaw/bin/ork-brain.sh +4 -0
  28. package/adapters/openclaw/construir.sh +19 -0
  29. package/adapters/openclaw/dist/hitl-ingress.js +199 -0
  30. package/adapters/openclaw/dist/index.js +437 -0
  31. package/adapters/openclaw/openclaw.plugin.json +39 -134
  32. package/adapters/openclaw/package.json +26 -0
  33. package/adapters/openclaw/src/hitl-ingress.ts +180 -0
  34. package/adapters/openclaw/src/index.ts +460 -0
  35. package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
  36. package/adapters/openclaw/tsconfig.json +15 -0
  37. package/assets/docs/markdownlint-cli2.jsonc +22 -0
  38. package/assets/docs/padroes/documentacao-de-produto.md +133 -0
  39. package/assets/docs/padroes/roadmap-de-produto.md +98 -0
  40. package/assets/docs/produto/README.md +14 -0
  41. package/assets/docs/produto/_modelo-feature.md +59 -0
  42. package/assets/docs/roadmap/README.md +14 -0
  43. package/assets/docs/roadmap/_modelo-item.md +73 -0
  44. package/assets/orkmind-native-schema.json +32 -0
  45. package/assets/orkmind_bridge.py +357 -0
  46. package/assets/orkmind_fixture.py +67 -0
  47. package/assets/orkmind_prospective.py +138 -0
  48. package/assets/reference-tariffs-i07.json +23 -0
  49. package/dist/adapters/claude-bg.js +488 -39
  50. package/dist/adapters/codex-controller-sensor.js +449 -0
  51. package/dist/adapters/codex-controller-worker.js +361 -0
  52. package/dist/adapters/codex-controller.js +252 -0
  53. package/dist/adapters/codex-events.js +205 -0
  54. package/dist/adapters/codex-question.js +26 -0
  55. package/dist/adapters/codex-runner.js +133 -0
  56. package/dist/adapters/codex.js +394 -0
  57. package/dist/agents-md.js +84 -0
  58. package/dist/auditoria.js +12 -9
  59. package/dist/auditrun.js +5 -4
  60. package/dist/board.js +174 -23
  61. package/dist/branch-de-estado.js +136 -0
  62. package/dist/canarios-hitl.js +177 -0
  63. package/dist/canarios-i43.js +543 -0
  64. package/dist/canarios-pulse.js +147 -0
  65. package/dist/canarios-sensores.js +129 -0
  66. package/dist/canarios.js +111 -2
  67. package/dist/catalogo.js +9 -0
  68. package/dist/ci.js +223 -0
  69. package/dist/ciclos.js +2 -1
  70. package/dist/claim-lint.js +64 -0
  71. package/dist/claims.js +60 -0
  72. package/dist/company-brain-capture.js +195 -0
  73. package/dist/company-brain-cli.js +123 -0
  74. package/dist/company-brain-client.js +61 -0
  75. package/dist/company-brain-contract.js +166 -0
  76. package/dist/company-brain-journal.js +192 -0
  77. package/dist/company-brain-mcp.js +33 -0
  78. package/dist/company-brain-migration.js +60 -0
  79. package/dist/company-brain-source.js +177 -0
  80. package/dist/company-brain-worker.js +18 -0
  81. package/dist/conducao-texto.js +61 -0
  82. package/dist/conducao.js +876 -0
  83. package/dist/contrato-publico.js +39 -0
  84. package/dist/creation-operation-store.js +251 -0
  85. package/dist/creation-operation.js +148 -0
  86. package/dist/decisao-autonoma.js +183 -0
  87. package/dist/delegation.js +79 -0
  88. package/dist/demo.js +120 -0
  89. package/dist/docs.js +828 -0
  90. package/dist/doctor.js +184 -25
  91. package/dist/entrega-pr.js +117 -0
  92. package/dist/escopo-escrita.js +71 -0
  93. package/dist/estado-thread.js +222 -0
  94. package/dist/evalrunner.js +12 -0
  95. package/dist/fabrica-estado.js +329 -0
  96. package/dist/fabrica-publicar.js +76 -0
  97. package/dist/fix.js +22 -1
  98. package/dist/gates.js +59 -11
  99. package/dist/handoff.js +39 -23
  100. package/dist/hitl-canais.js +333 -0
  101. package/dist/hitl-classificacao.js +131 -0
  102. package/dist/hitl-contract.js +465 -0
  103. package/dist/hitl-estado.js +136 -0
  104. package/dist/hitl-gates.js +554 -0
  105. package/dist/hitl-ingress-receipt.js +382 -0
  106. package/dist/hitl-local-atestado.js +57 -0
  107. package/dist/hitl-local-receipt.js +328 -0
  108. package/dist/hitl-local.js +143 -0
  109. package/dist/hitl-lock.js +139 -0
  110. package/dist/hitl-lote.js +223 -0
  111. package/dist/hitl-native-offer.js +97 -0
  112. package/dist/hitl-native.js +65 -0
  113. package/dist/hitl-presentation.js +209 -0
  114. package/dist/hitl-public-receipt.js +176 -0
  115. package/dist/hitl-resumo.js +209 -0
  116. package/dist/hitl-sessions.js +475 -0
  117. package/dist/hitl.js +711 -0
  118. package/dist/horario.js +269 -0
  119. package/dist/hosts.js +198 -22
  120. package/dist/index.js +1741 -94
  121. package/dist/indice.js +99 -0
  122. package/dist/init.js +28 -3
  123. package/dist/integracoes-locais.js +17 -0
  124. package/dist/leases.js +65 -21
  125. package/dist/ledger-stats.js +272 -0
  126. package/dist/ledger.js +109 -2
  127. package/dist/licoes.js +221 -0
  128. package/dist/liveness.js +218 -0
  129. package/dist/maestro-actions.js +51 -0
  130. package/dist/maestro-authority.js +152 -0
  131. package/dist/maestro-cli.js +97 -0
  132. package/dist/maestro-contract.js +55 -0
  133. package/dist/maestro-discovery.js +132 -0
  134. package/dist/maestro-runtime.js +268 -0
  135. package/dist/maestro-snapshot.js +86 -0
  136. package/dist/maestro-sources.js +224 -0
  137. package/dist/manifest.js +165 -6
  138. package/dist/maquina.js +102 -0
  139. package/dist/master-audit.js +101 -0
  140. package/dist/master-batch.js +43 -0
  141. package/dist/master-digest.js +149 -0
  142. package/dist/master-migracao.js +155 -0
  143. package/dist/master.js +361 -77
  144. package/dist/mcp-artifacts.js +234 -0
  145. package/dist/mcp-git.js +433 -0
  146. package/dist/mcp-install.js +310 -0
  147. package/dist/mcp-maestro.js +25 -0
  148. package/dist/mcp-server.js +469 -0
  149. package/dist/mcp-ship.js +436 -0
  150. package/dist/mcp-verify.js +173 -0
  151. package/dist/memoria-humana.js +207 -0
  152. package/dist/memoria.js +314 -78
  153. package/dist/memory-migration.js +279 -0
  154. package/dist/memory-prospective.js +148 -0
  155. package/dist/modos-migracao.js +191 -0
  156. package/dist/modos.js +151 -15
  157. package/dist/monitor-lock.js +110 -0
  158. package/dist/objective.js +449 -0
  159. package/dist/ocupacao.js +267 -0
  160. package/dist/onboarding.js +303 -0
  161. package/dist/orkmind.js +389 -90
  162. package/dist/orquestracao.js +96 -69
  163. package/dist/phase.js +612 -151
  164. package/dist/playbook-capabilities.js +150 -0
  165. package/dist/playbook-contracts.js +188 -0
  166. package/dist/policies.js +72 -0
  167. package/dist/portfolio-context.js +61 -0
  168. package/dist/portfolio.js +180 -0
  169. package/dist/preflight.js +207 -0
  170. package/dist/process-audit.js +112 -0
  171. package/dist/project-state.js +93 -0
  172. package/dist/prompts.js +25 -5
  173. package/dist/prova-minima.js +112 -0
  174. package/dist/pulse-cadencia.js +163 -0
  175. package/dist/pulse-consentimento.js +260 -0
  176. package/dist/pulse-delivery.js +323 -0
  177. package/dist/pulse-resposta.js +542 -0
  178. package/dist/pulse.js +262 -0
  179. package/dist/ratelimit.js +35 -2
  180. package/dist/recall.js +80 -3
  181. package/dist/redacao-saida.js +45 -0
  182. package/dist/redacao-url.js +66 -0
  183. package/dist/retry.js +640 -118
  184. package/dist/roadmap-reservas.js +243 -0
  185. package/dist/runtime-ambiente.js +38 -0
  186. package/dist/runtime-context.js +215 -0
  187. package/dist/runtime-profiles.js +869 -0
  188. package/dist/runtimes.js +103 -0
  189. package/dist/sandbox.js +9 -0
  190. package/dist/session-events.js +203 -0
  191. package/dist/session-watcher-claude.js +637 -0
  192. package/dist/session-watcher.js +700 -0
  193. package/dist/sessoes-adopt.js +154 -0
  194. package/dist/sessoes-inventario.js +159 -0
  195. package/dist/sessoes.js +16 -35
  196. package/dist/setup.js +691 -0
  197. package/dist/ship.js +168 -19
  198. package/dist/slug.js +1 -1
  199. package/dist/thread-close.js +115 -0
  200. package/dist/thread.js +142 -28
  201. package/dist/tokens.js +1 -1
  202. package/dist/util.js +4 -2
  203. package/dist/verify-sandbox.js +265 -0
  204. package/dist/verify.js +270 -25
  205. package/dist/versao.js +60 -0
  206. package/dist/worktree.js +31 -2
  207. package/dist/write-activation.js +291 -0
  208. package/dist/yaml.js +5 -1
  209. package/eval/casos/master-metrics.json +1 -1
  210. package/eval/casos/onboarding.json +102 -0
  211. package/eval/casos/orkastery-bootstrap.json +37 -3
  212. package/eval/casos/scope-check-capability-map.json +45 -7
  213. package/eval/casos/ship-release.json +3 -3
  214. package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
  215. package/eval/fixtures/b2-master-log/caso.json +1 -1
  216. package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
  217. package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
  218. package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
  219. package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
  220. package/eval/fixtures/fx-codex-dry/caso.json +18 -0
  221. package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
  222. package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
  223. package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
  224. package/eval/fixtures/fx-happy/caso.json +6 -4
  225. package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
  226. package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
  227. package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
  228. package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
  229. package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
  230. package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
  231. package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
  232. package/eval/fixtures/fx-omnicanal/caso.json +17 -0
  233. package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
  234. package/monitor/company-brain.cjs +17 -0
  235. package/monitor/pulse-scope.cjs +45 -0
  236. package/monitor/pulse.cron +18 -0
  237. package/monitor/varredura-pulse.sh +27 -0
  238. package/package.json +28 -7
  239. package/references/definition-of-done.md +2 -2
  240. package/schemas/claims.schema.json +25 -0
  241. package/schemas/company-brain.schema.json +1158 -0
  242. package/schemas/creation-operation.schema.json +365 -0
  243. package/schemas/maestro-snapshot.schema.json +2716 -0
  244. package/skills/README.md +3 -3
  245. package/skills/core/onboarding/SKILL.md +50 -0
  246. package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
  247. package/skills/core/thread-state/SKILL.md +3 -1
  248. package/skills/governance/decision-triage/SKILL.md +4 -4
  249. package/skills/governance/narrative-guardian/SKILL.md +1 -1
  250. package/skills/governance/roadmap-keeper/SKILL.md +1 -1
  251. package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
  252. package/skills/observability/thread-tracing/SKILL.md +1 -1
  253. package/skills/phases/check-quality/SKILL.md +2 -2
  254. package/skills/phases/goal-definition/SKILL.md +1 -1
  255. package/skills/phases/master-metrics/SKILL.md +9 -7
  256. package/skills/phases/plan-specification/SKILL.md +1 -1
  257. package/skills/phases/ship-release/SKILL.md +1 -1
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ /**
3
+ * I-42 (D7): fronteira dura de contrato publico.
4
+ *
5
+ * O `#Fast` e a edicao rapida, sem GOAL, PLAN nem CHECK. Mudar um contrato publico (as unioes
6
+ * `Fase`, `Modo` e `MotivoGate`, os contratos versionados `ork.*`, o formato do ledger e os
7
+ * esquemas MCP e JSON) e iniciativa por definicao: pede plano e verificacao independente. Por
8
+ * isso uma thread `fast` nao commita nenhum destes caminhos.
9
+ *
10
+ * A regra e por caminho, nao por simbolo, e grossa de proposito: quando erra, erra mandando a
11
+ * mudanca para um modo com PLAN e CHECK. A lista e contrato sobre contratos: qualquer item que
12
+ * entre ou saia dela precisa de ratificacao.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.PATHS_DE_CONTRATO_PUBLICO = void 0;
16
+ exports.ehContratoPublico = ehContratoPublico;
17
+ exports.contratosTocados = contratosTocados;
18
+ /** Domicilio unico da lista. Diretorio termina em `/`. */
19
+ exports.PATHS_DE_CONTRATO_PUBLICO = [
20
+ 'core/src/types.ts',
21
+ 'core/src/modos.ts',
22
+ 'core/src/ledger.ts',
23
+ 'core/src/hitl-contract.ts',
24
+ 'core/src/pulse.ts',
25
+ 'core/src/master.ts',
26
+ 'core/src/setup.ts',
27
+ 'core/src/mcp-server.ts',
28
+ 'core/src/contrato-publico.ts',
29
+ 'core/schemas/',
30
+ ];
31
+ /** O caminho (relativo a raiz do repositorio) e contrato publico? */
32
+ function ehContratoPublico(caminho) {
33
+ const normalizado = caminho.replace(/\\/g, '/').replace(/^\.\//, '');
34
+ return exports.PATHS_DE_CONTRATO_PUBLICO.some((p) => (p.endsWith('/') ? normalizado.startsWith(p) : normalizado === p));
35
+ }
36
+ /** Os caminhos do commit que uma thread `fast` nao pode tocar. */
37
+ function contratosTocados(caminhos) {
38
+ return caminhos.filter(ehContratoPublico);
39
+ }
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.creationOperationSchema = exports.creationStates = exports.creationRequestSchema = void 0;
37
+ exports.withCreationLock = withCreationLock;
38
+ exports.writeCreationJson = writeCreationJson;
39
+ exports.readCreationOperation = readCreationOperation;
40
+ exports.listCreationOperations = listCreationOperations;
41
+ exports.receiveCreationOperation = receiveCreationOperation;
42
+ exports.advanceCreationOperation = advanceCreationOperation;
43
+ /** Durable intent journal. No portfolio, thread or runtime effects happen in this store. */
44
+ const fs = __importStar(require("node:fs"));
45
+ const path = __importStar(require("node:path"));
46
+ const node_crypto_1 = require("node:crypto");
47
+ const node_child_process_1 = require("node:child_process");
48
+ const zod_1 = require("zod");
49
+ const manifest_1 = require("./manifest");
50
+ const modos_1 = require("./modos");
51
+ const entityId = zod_1.z.string().regex(/^(prod|proj|init)-[a-z0-9][a-z0-9-]{2,47}$/);
52
+ const version = zod_1.z.number().int().positive();
53
+ const entity = zod_1.z.object({
54
+ kind: zod_1.z.enum(['project', 'initiative']), id: entityId,
55
+ parentId: entityId, title: zod_1.z.string().trim().min(1).max(160),
56
+ description: zod_1.z.string().max(10000).default(''),
57
+ acceptanceCriteria: zod_1.z.array(zod_1.z.string().trim().min(1).max(1000)).max(100),
58
+ }).strict();
59
+ exports.creationRequestSchema = zod_1.z.object({
60
+ key: zod_1.z.string().min(8).max(200),
61
+ action: zod_1.z.enum(['create_entity', 'create_entity_with_ticket', 'open_ticket']),
62
+ entity: entity.optional(), entityId: entityId.optional(),
63
+ expectedParentVersion: version.optional(), expectedEntityVersion: version.optional(),
64
+ request: zod_1.z.string().trim().min(1).max(10000),
65
+ doneWhen: zod_1.z.array(zod_1.z.string().trim().min(1).max(1000)).min(1).max(100),
66
+ workspaceIds: zod_1.z.array(zod_1.z.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/)).max(30),
67
+ // Leitor: operacao de criacao ja persistida em modo aposentado continua parseando.
68
+ mode: zod_1.z.enum(modos_1.MODOS_LEGADOS),
69
+ scope: zod_1.z.object({ projectId: entityId, delivery: zod_1.z.enum(['project', 'initiatives']), initiativeIds: zod_1.z.array(entityId) }).strict().optional(),
70
+ }).strict().superRefine((value, ctx) => {
71
+ const existing = value.action === 'open_ticket';
72
+ if (existing ? (!value.entityId || value.entity || !value.expectedEntityVersion) : (!value.entity || value.entityId || !value.expectedParentVersion)) {
73
+ ctx.addIssue({ code: 'custom', message: 'creation.input: entidade e versões incompatíveis com a ação' });
74
+ }
75
+ });
76
+ exports.creationStates = ['received', 'entity_persisted', 'ticket_persisted', 'completed', 'recoverable_failure', 'compensating', 'compensation_failed', 'compensated'];
77
+ const state = zod_1.z.enum(exports.creationStates);
78
+ const event = zod_1.z.object({ sequence: version, at: zod_1.z.string().datetime(), origin: zod_1.z.literal('ork.creation'), state, reason: zod_1.z.string().max(200) }).strict();
79
+ exports.creationOperationSchema = zod_1.z.object({
80
+ schema: zod_1.z.literal('ork.creation-operation/v1'), operationId: zod_1.z.string().regex(/^cop-[a-f0-9]{32}$/),
81
+ version, principal: zod_1.z.string().min(1).max(200), requestHash: zod_1.z.string().regex(/^[a-f0-9]{64}$/),
82
+ request: exports.creationRequestSchema, state,
83
+ createdAt: zod_1.z.string().datetime(), updatedAt: zod_1.z.string().datetime(),
84
+ reserved: zod_1.z.object({ entityId, ticketId: zod_1.z.string().regex(/^obj-cop-[a-f0-9]{32}$/).nullable(), threadIds: zod_1.z.array(zod_1.z.string().regex(/^[a-z0-9]+-[a-z0-9]{1,12}$/)).max(8) }).strict(),
85
+ confirmed: zod_1.z.object({ entityVersion: version.nullable(), ticket: zod_1.z.boolean() }).strict(),
86
+ nextAction: zod_1.z.enum(['resume', 'compensate', 'none', 'manual']),
87
+ error: zod_1.z.object({ code: zod_1.z.string().regex(/^creation\.[a-z_]+$/), message: zod_1.z.string().max(300) }).strict().nullable(),
88
+ receipts: zod_1.z.array(zod_1.z.string().max(300)), events: zod_1.z.array(event).min(1),
89
+ }).strict().superRefine((value, ctx) => {
90
+ if (value.events.length !== value.version || value.events.some((e, i) => e.sequence !== i + 1)
91
+ || value.events.at(-1)?.state !== value.state)
92
+ ctx.addIssue({ code: 'custom', message: 'creation.corrupt: sequência inválida' });
93
+ if (hash(value.request) !== value.requestHash || operationId(value.principal, value.request.key) !== value.operationId) {
94
+ ctx.addIssue({ code: 'custom', message: 'creation.corrupt: identidade ou intenção alterada' });
95
+ }
96
+ if (value.reserved.entityId !== (value.request.entity?.id ?? value.request.entityId)
97
+ || value.reserved.ticketId !== (value.request.action === 'create_entity' ? null : 'obj-' + value.operationId)
98
+ || (value.state === 'completed' && (!value.confirmed.entityVersion || (value.reserved.ticketId && !value.confirmed.ticket)))
99
+ || (['completed', 'compensated'].includes(value.state) && value.nextAction !== 'none')) {
100
+ ctx.addIssue({ code: 'custom', message: 'creation.corrupt: vínculos ou resultado incompatíveis' });
101
+ }
102
+ });
103
+ function canonical(value) {
104
+ if (Array.isArray(value))
105
+ return '[' + value.map(canonical).join(',') + ']';
106
+ if (value && typeof value === 'object')
107
+ return '{' + Object.entries(value).filter(([, v]) => v !== undefined)
108
+ .sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => JSON.stringify(k) + ':' + canonical(v)).join(',') + '}';
109
+ return JSON.stringify(value);
110
+ }
111
+ function hash(value) { return (0, node_crypto_1.createHash)('sha256').update(canonical(value)).digest('hex'); }
112
+ function operationId(principal, key) { return 'cop-' + hash([principal, key]).slice(0, 32); }
113
+ function directory(root) { return path.join((0, manifest_1.dirEstado)(root), 'creation-operations'); }
114
+ function file(root, id) {
115
+ if (!/^cop-[a-f0-9]{32}$/.test(id))
116
+ throw new Error('creation.id: identidade inválida');
117
+ return path.join(directory(root), id + '.json');
118
+ }
119
+ function syncDirectory(dir) {
120
+ const fd = fs.openSync(dir, 'r');
121
+ try {
122
+ fs.fsyncSync(fd);
123
+ }
124
+ finally {
125
+ fs.closeSync(fd);
126
+ }
127
+ }
128
+ function ensureDirectory(dir) {
129
+ if (fs.existsSync(dir))
130
+ return;
131
+ const parent = path.dirname(dir);
132
+ ensureDirectory(parent);
133
+ try {
134
+ fs.mkdirSync(dir, { mode: 0o700 });
135
+ }
136
+ catch (e) {
137
+ if (e.code !== 'EEXIST')
138
+ throw e;
139
+ }
140
+ syncDirectory(parent);
141
+ }
142
+ /** Stable inode and kernel ownership: process death releases the lock without a TTL race. */
143
+ function withCreationLock(root, name, run) {
144
+ if (!/^[a-z0-9-]+$/.test(name))
145
+ throw new Error('creation.lock: nome inválido');
146
+ if (process.platform !== 'linux')
147
+ throw new Error('runtime.unavailable: journal exige flock Linux');
148
+ const dir = path.join(directory(root), 'locks');
149
+ ensureDirectory(dir);
150
+ const target = path.join(dir, name);
151
+ const fd = fs.openSync(target, fs.constants.O_RDWR | fs.constants.O_CREAT | fs.constants.O_NOFOLLOW, 0o600);
152
+ try {
153
+ const stat = fs.fstatSync(fd);
154
+ if (!stat.isFile() || stat.nlink !== 1 || stat.uid !== process.getuid?.() || stat.mode & 0o077)
155
+ throw new Error('creation.lock: arquivo inseguro');
156
+ const result = (0, node_child_process_1.spawnSync)('/usr/bin/flock', ['--exclusive', '--timeout', '5', '3'], { stdio: ['ignore', 'pipe', 'pipe', fd], timeout: 6000 });
157
+ if (result.error || result.status !== 0)
158
+ throw new Error('creation.busy: exclusão do kernel indisponível');
159
+ const current = fs.lstatSync(target);
160
+ if (current.ino !== stat.ino || current.dev !== stat.dev || current.isSymbolicLink())
161
+ throw new Error('creation.lock: inode alterado');
162
+ return run();
163
+ }
164
+ finally {
165
+ fs.closeSync(fd);
166
+ }
167
+ }
168
+ /** File sync before rename, directory sync after rename. Readers see one complete revision. */
169
+ function writeCreationJson(target, value) {
170
+ const dir = path.dirname(target);
171
+ ensureDirectory(dir);
172
+ const tmp = path.join(dir, '.' + path.basename(target) + '.' + (0, node_crypto_1.randomUUID)() + '.tmp');
173
+ try {
174
+ const fd = fs.openSync(tmp, 'wx', 0o600);
175
+ try {
176
+ fs.writeFileSync(fd, JSON.stringify(value, null, 2) + '\n');
177
+ fs.fsyncSync(fd);
178
+ }
179
+ finally {
180
+ fs.closeSync(fd);
181
+ }
182
+ fs.renameSync(tmp, target);
183
+ syncDirectory(dir);
184
+ }
185
+ finally {
186
+ if (fs.existsSync(tmp))
187
+ fs.unlinkSync(tmp);
188
+ }
189
+ }
190
+ function readCreationOperation(root, id, principal) {
191
+ const value = exports.creationOperationSchema.parse(JSON.parse(fs.readFileSync(file(root, id), 'utf8')));
192
+ if (value.principal !== principal)
193
+ throw new Error('creation.unavailable: operação indisponível');
194
+ return value;
195
+ }
196
+ function listCreationOperations(root, principal) {
197
+ if (!fs.existsSync(directory(root)))
198
+ return [];
199
+ return fs.readdirSync(directory(root)).filter((name) => /^cop-[a-f0-9]{32}\.json$/.test(name))
200
+ .map((name) => exports.creationOperationSchema.parse(JSON.parse(fs.readFileSync(path.join(directory(root), name), 'utf8'))))
201
+ .filter((op) => op.principal === principal).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt) || a.operationId.localeCompare(b.operationId));
202
+ }
203
+ /** Caller supplies the authenticated principal out of band; a payload cannot choose it. */
204
+ function receiveCreationOperation(root, principal, raw, abbrev) {
205
+ zod_1.z.string().min(1).max(200).parse(principal);
206
+ if (!/^[a-z][a-z0-9]{0,11}$/.test(abbrev))
207
+ throw new Error('creation.input: abbrev inválido');
208
+ const request = exports.creationRequestSchema.parse(raw);
209
+ const id = operationId(principal, request.key);
210
+ return withCreationLock(root, id, () => {
211
+ if (fs.existsSync(file(root, id))) {
212
+ const prior = readCreationOperation(root, id, principal);
213
+ if (prior.requestHash !== hash(request))
214
+ throw new Error('creation.conflict: chave reutilizada com outra intenção');
215
+ return prior;
216
+ }
217
+ const now = new Date().toISOString();
218
+ const ticket = request.action !== 'create_entity';
219
+ const value = {
220
+ schema: 'ork.creation-operation/v1', operationId: id, version: 1, principal, requestHash: hash(request), request,
221
+ state: 'received', createdAt: now, updatedAt: now,
222
+ reserved: { entityId: request.entity?.id ?? request.entityId, ticketId: ticket ? 'obj-' + id : null,
223
+ threadIds: ticket ? [1, 2, 3].map((n) => `${abbrev}-c${id.slice(4, 14)}${n}`) : [] },
224
+ confirmed: { entityVersion: null, ticket: false }, nextAction: 'resume', error: null, receipts: [],
225
+ events: [{ sequence: 1, at: now, origin: 'ork.creation', state: 'received', reason: 'intent.persisted' }],
226
+ };
227
+ writeCreationJson(file(root, id), exports.creationOperationSchema.parse(value));
228
+ return readCreationOperation(root, id, principal);
229
+ });
230
+ }
231
+ const transitions = {
232
+ received: ['entity_persisted', 'recoverable_failure', 'compensating'],
233
+ entity_persisted: ['ticket_persisted', 'completed', 'recoverable_failure', 'compensating'],
234
+ ticket_persisted: ['completed', 'recoverable_failure', 'compensating'],
235
+ completed: ['compensating'], recoverable_failure: ['entity_persisted', 'ticket_persisted', 'completed', 'recoverable_failure', 'compensating'],
236
+ compensating: ['compensated', 'compensation_failed'], compensation_failed: ['compensating'], compensated: [],
237
+ };
238
+ function advanceCreationOperation(root, id, principal, expectedVersion, change, reason) {
239
+ return withCreationLock(root, id, () => {
240
+ const prior = readCreationOperation(root, id, principal);
241
+ if (prior.version !== expectedVersion)
242
+ throw new Error('creation.conflict: revisão alterada');
243
+ if (!transitions[prior.state].includes(change.state))
244
+ throw new Error('creation.transition: transição inválida');
245
+ const now = new Date().toISOString();
246
+ const value = exports.creationOperationSchema.parse({ ...prior, ...change, version: prior.version + 1, updatedAt: now,
247
+ events: [...prior.events, { sequence: prior.version + 1, at: now, origin: 'ork.creation', state: change.state, reason }] });
248
+ writeCreationJson(file(root, id), value);
249
+ return readCreationOperation(root, id, principal);
250
+ });
251
+ }
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startCreation = startCreation;
4
+ exports.resumeCreation = resumeCreation;
5
+ exports.compensateCreation = compensateCreation;
6
+ const creation_operation_store_1 = require("./creation-operation-store");
7
+ const portfolio_1 = require("./portfolio");
8
+ const objective_1 = require("./objective");
9
+ const thread_1 = require("./thread");
10
+ const modos_1 = require("./modos");
11
+ function authorize(loaded, actor, request, action) {
12
+ actor.authorize(request, action);
13
+ if (!loaded.manifesto.conduction.allowed_modes.includes(request.mode))
14
+ throw new Error('creation.mode: modo não autorizado pelo manifesto');
15
+ }
16
+ function entityConfirmed(root, op) {
17
+ const entity = (0, portfolio_1.findEntity)(root, op.reserved.entityId);
18
+ if (!entity)
19
+ throw new Error('creation.conflict: entidade indisponível');
20
+ const expected = op.request.action === 'open_ticket' ? op.request.expectedEntityVersion : 1;
21
+ if (entity.version !== expected || (op.request.action !== 'open_ticket' && entity.creationOperationId !== op.operationId)) {
22
+ throw new Error('creation.conflict: entidade alterada ou pertencente a outra operação');
23
+ }
24
+ return entity;
25
+ }
26
+ function scopeFor(root, op, entity) {
27
+ if (entity.kind === 'product')
28
+ throw new Error('creation.scope: ticket exige projeto ou iniciativa');
29
+ const projectId = entity.kind === 'project' ? entity.id : entity.projectId;
30
+ const scope = op.request.scope ?? { projectId, delivery: entity.kind === 'project' ? 'project' : 'initiatives', initiativeIds: entity.kind === 'initiative' ? [entity.id] : [] };
31
+ if (scope.projectId !== projectId || (entity.kind === 'initiative' && (scope.delivery !== 'initiatives' || !scope.initiativeIds.includes(entity.id)))) {
32
+ throw new Error('creation.scope: escopo não contém a entidade alvo');
33
+ }
34
+ const validated = (0, portfolio_1.assertCycleScope)(root, scope);
35
+ const project = (0, portfolio_1.findEntity)(root, projectId);
36
+ if (!project || project.kind !== 'project')
37
+ throw new Error('creation.scope: projeto indisponível');
38
+ if (op.request.workspaceIds.some((id) => !project.workspaceIds.includes(id)))
39
+ throw new Error('creation.scope: workspace fora do projeto');
40
+ return { productId: project.productId, ...validated };
41
+ }
42
+ function confirmTicket(root, op) {
43
+ const ticket = (0, objective_1.readObjective)(root, op.reserved.ticketId);
44
+ if (ticket.creationOrigin?.operationId !== op.operationId || ticket.creationOrigin?.requestHash !== op.requestHash
45
+ || ticket.creationOrigin?.ticketId !== ticket.id || ticket.envelope.request !== op.request.request
46
+ || JSON.stringify(ticket.threads.map((t) => t.id)) !== JSON.stringify(op.reserved.threadIds))
47
+ throw new Error('creation.conflict: ticket sem origem confirmada');
48
+ for (const id of op.reserved.threadIds) {
49
+ const thread = (0, thread_1.lerThread)(root, id);
50
+ if (thread.creationOrigin?.operationId !== op.operationId || thread.creationOrigin?.requestHash !== op.requestHash)
51
+ throw new Error('creation.conflict: thread sem origem confirmada');
52
+ }
53
+ return `objective:${ticket.id}:envelope:${ticket.envelope.hash}`;
54
+ }
55
+ function fail(root, op, error, compensation = false) {
56
+ const detail = error instanceof Error ? error.message : '';
57
+ const known = detail.match(/^creation\.(conflict|scope|mode|started|unavailable|busy):/)?.[1];
58
+ const code = `creation.${known ?? 'effect_failed'}`;
59
+ return (0, creation_operation_store_1.advanceCreationOperation)(root, op.operationId, op.principal, op.version, {
60
+ state: compensation ? 'compensation_failed' : 'recoverable_failure', confirmed: op.confirmed,
61
+ nextAction: known === 'started' ? 'manual' : compensation ? 'compensate' : 'resume',
62
+ error: { code, message: known === 'started' ? 'Execução ou alteração detectada; use a condução operacional.' : 'A operação foi preservada. Revalide permissões, versões e escopo antes de retomar.' }, receipts: op.receipts,
63
+ }, code);
64
+ }
65
+ function startCreation(loaded, actor, raw) {
66
+ const request = creation_operation_store_1.creationRequestSchema.parse(raw);
67
+ authorize(loaded, actor, request, 'create');
68
+ const op = (0, creation_operation_store_1.receiveCreationOperation)(loaded.raiz, actor.principal, request, loaded.manifesto.project.abbrev);
69
+ // Repetição de uma intenção que já avançou devolve o journal; recuperação é explícita.
70
+ return op.version > 1 ? op : resumeCreation(loaded, actor, op.operationId, op.version);
71
+ }
72
+ function resumeCreation(loaded, actor, id, expectedVersion) {
73
+ const root = loaded.raiz;
74
+ return (0, creation_operation_store_1.withCreationLock)(root, `effects-${id}`, () => {
75
+ let op = (0, creation_operation_store_1.readCreationOperation)(root, id, actor.principal);
76
+ authorize(loaded, actor, op.request, 'resume');
77
+ if (op.version !== expectedVersion)
78
+ throw new Error('creation.conflict: revisão alterada');
79
+ if (['completed', 'compensated'].includes(op.state))
80
+ return op;
81
+ if (['compensating', 'compensation_failed'].includes(op.state))
82
+ throw new Error('creation.conflict: retome a compensação');
83
+ const advance = (state, entityVersion, ticket, receipts) => {
84
+ op = (0, creation_operation_store_1.advanceCreationOperation)(root, id, actor.principal, op.version, { state, confirmed: { entityVersion, ticket }, nextAction: state === 'completed' ? 'none' : 'resume', error: null, receipts }, `${state}.confirmed`);
85
+ };
86
+ try {
87
+ if (op.request.action !== 'open_ticket')
88
+ (0, portfolio_1.createOperationEntity)(root, id, actor.principal);
89
+ let entity = entityConfirmed(root, op);
90
+ if (op.state === 'received' || op.state === 'recoverable_failure')
91
+ advance('entity_persisted', entity.version, op.confirmed.ticket, op.receipts);
92
+ if (op.reserved.ticketId) {
93
+ authorize(loaded, actor, op.request, 'resume');
94
+ entity = entityConfirmed(root, op);
95
+ const scope = scopeFor(root, op, entity);
96
+ if (op.state !== 'ticket_persisted') {
97
+ // Leitor: a operacao durável pode ter sido gravada em modo aposentado; ela
98
+ // continua parseando, e so nao pode mais ABRIR thread nova nesse modo.
99
+ const modoVivo = (0, modos_1.parseModo)(op.request.mode);
100
+ if (!modoVivo)
101
+ throw new Error(`creation.mode: modo aposentado "${op.request.mode}" nao abre thread nova`);
102
+ (0, objective_1.createReservedObjective)(loaded, { title: entity.title, request: op.request.request, doneWhen: op.request.doneWhen, mode: modoVivo, ...scope }, { operationId: id, principal: actor.principal });
103
+ const receipt = confirmTicket(root, op);
104
+ advance('ticket_persisted', entity.version, true, [...new Set([...op.receipts, receipt])]);
105
+ }
106
+ confirmTicket(root, op);
107
+ }
108
+ entity = entityConfirmed(root, op);
109
+ advance('completed', entity.version, Boolean(op.reserved.ticketId), op.receipts);
110
+ return (0, creation_operation_store_1.readCreationOperation)(root, id, actor.principal);
111
+ }
112
+ catch (error) {
113
+ // A gravação pode ter sucedido antes da perda da resposta; releia a revisão real.
114
+ op = (0, creation_operation_store_1.readCreationOperation)(root, id, actor.principal);
115
+ if (op.state === 'completed')
116
+ return op;
117
+ return fail(root, op, error);
118
+ }
119
+ });
120
+ }
121
+ function compensateCreation(loaded, actor, id, expectedVersion) {
122
+ const root = loaded.raiz;
123
+ return (0, creation_operation_store_1.withCreationLock)(root, `effects-${id}`, () => {
124
+ let op = (0, creation_operation_store_1.readCreationOperation)(root, id, actor.principal);
125
+ authorize(loaded, actor, op.request, 'compensate');
126
+ if (op.version !== expectedVersion)
127
+ throw new Error('creation.conflict: revisão alterada');
128
+ if (op.state === 'compensated')
129
+ return op;
130
+ if (op.state !== 'compensating')
131
+ op = (0, creation_operation_store_1.advanceCreationOperation)(root, id, actor.principal, op.version, { state: 'compensating', confirmed: op.confirmed, nextAction: 'compensate', error: null, receipts: op.receipts }, 'compensation.requested');
132
+ try {
133
+ // Conserva entidade preexistente e efeitos próprios; nunca remove catálogo.
134
+ const entity = (0, portfolio_1.findEntity)(root, op.reserved.entityId);
135
+ if (entity)
136
+ entityConfirmed(root, op);
137
+ (0, objective_1.compensateReservedObjective)(root, id, actor.principal);
138
+ op = (0, creation_operation_store_1.advanceCreationOperation)(root, id, actor.principal, op.version, { state: 'compensated', confirmed: op.confirmed, nextAction: 'none', error: null, receipts: op.receipts }, 'compensation.confirmed');
139
+ return (0, creation_operation_store_1.readCreationOperation)(root, id, actor.principal);
140
+ }
141
+ catch (error) {
142
+ op = (0, creation_operation_store_1.readCreationOperation)(root, id, actor.principal);
143
+ if (op.state === 'compensated')
144
+ return op;
145
+ return fail(root, op, error, true);
146
+ }
147
+ });
148
+ }
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NOMES_HISTORICOS_DE_EVIDENCIA = exports.NOMES_HISTORICOS_DE_RAZAO = exports.NOMES_HISTORICOS_DE_QUEM = exports.LIMIAR_DE_DECISOES_POR_FASE = exports.CONTRATO_DECISAO_AUTONOMA = void 0;
4
+ exports.rastroDaDecisao = rastroDaDecisao;
5
+ exports.registrarDecisao = registrarDecisao;
6
+ exports.placarDaThread = placarDaThread;
7
+ exports.taxaDeReversao = taxaDeReversao;
8
+ exports.decisoesParaODono = decisoesParaODono;
9
+ /**
10
+ * I-41 (GO-FIX 1, B4): a metade que presta contas.
11
+ *
12
+ * Das cinco camadas que o GOAL prometeu contra "obvia virar desculpa para decidir tudo em
13
+ * silencio" (R1), tres estavam de pe (criterio resolvivel, preco declarado e porta fechada para o
14
+ * irreversivel) e duas nao existiam: o RASTRO TIPADO e o PLACAR. E nada emitia `classe: decidido`.
15
+ * A fabrica ja decide sessenta vezes para cada vez que pergunta (M7 do PLAN), e o dono nao via
16
+ * nenhuma dessas decisoes. A cobranca dele foi literal: "eu devo ser informado da decisao pra
17
+ * poder ajustar". Trocar burocracia por silencio seria pior que o problema original.
18
+ *
19
+ * Este modulo e o domicilio das duas camadas que faltavam, e do emissor:
20
+ *
21
+ * RASTRO (D11). Toda `autonomous_decision` nova carrega `quemDecidiu`, `evidencia` e `razao`, e
22
+ * o registro recusa o evento sem os tres (`ledger.ts`). Os nomes historicos continuam lidos,
23
+ * normalizados na leitura; nenhuma linha do ledger e reescrita.
24
+ *
25
+ * EMISSOR. `registrarDecisao` grava a decisao informada, `classe: decidido` com os quatro campos
26
+ * (o que, o porque, como mudar, custo de reverter agora e depois) e o criterio que RESOLVE, no
27
+ * mesmo evento do rastro. Ela nao segura fase, nao tem prazo e nao pede resposta: chega ao dono
28
+ * no resumo do pulse, para ele saber e poder ajustar.
29
+ *
30
+ * PLACAR. Por fase: decididas contra perguntas, quantas decisoes o dono mandou reverter, e
31
+ * quantas vieram sem rastro. O limiar de revisao e 13 decisoes por fase, o p90 das 155 fases
32
+ * medidas no PLAN (M7). Ele dispara revisao no CHECK, nunca recusa: ha fases legitimas com 81.
33
+ * A taxa de reversao e o sinal que o limiar sozinho nao ve: trinta decisoes certas e trinta
34
+ * erradas fazem o mesmo placar, e so a reversao diz qual das duas "obvia" era para quem le.
35
+ */
36
+ const node_crypto_1 = require("node:crypto");
37
+ const hitl_contract_1 = require("./hitl-contract");
38
+ const hitl_gates_1 = require("./hitl-gates");
39
+ const ledger_1 = require("./ledger");
40
+ const thread_1 = require("./thread");
41
+ const modos_1 = require("./modos");
42
+ exports.CONTRATO_DECISAO_AUTONOMA = 'ork.decisao-autonoma/v1';
43
+ /** D3/M7: o p90 das 155 fases ja medidas. Acima disso a fase esta no decil mais alto do projeto. */
44
+ exports.LIMIAR_DE_DECISOES_POR_FASE = 13;
45
+ /** Os nomes que o rastro teve antes de ser tipado (M6), na ordem em que valem na leitura. */
46
+ exports.NOMES_HISTORICOS_DE_QUEM = ['quemDecidiu', 'autorizadoPor', 'quem', 'por', 'decididoPor', 'decisor',
47
+ 'decididaPor', 'decidiuQuem'];
48
+ exports.NOMES_HISTORICOS_DE_RAZAO = ['razao', 'porque', 'motivo', 'justificativa'];
49
+ exports.NOMES_HISTORICOS_DE_EVIDENCIA = ['evidencia', 'evidencias', 'evidenciaSha256', 'comandos', 'sha256'];
50
+ const valor = (e, nomes) => {
51
+ for (const nome of nomes) {
52
+ const v = e[nome];
53
+ const texto = Array.isArray(v) ? v.join('; ') : typeof v === 'string' || typeof v === 'number' ? String(v) : '';
54
+ if (texto.trim())
55
+ return texto.trim();
56
+ }
57
+ return undefined;
58
+ };
59
+ /**
60
+ * O rastro de uma decisao, com os nomes historicos normalizados. `tipada` diz se o evento ja nasceu
61
+ * com os tres campos de nome unico; um evento antigo pode ser completo pelos nomes velhos sem ser
62
+ * tipado, e a auditoria precisa enxergar as duas coisas.
63
+ */
64
+ function rastroDaDecisao(e) {
65
+ const bruto = e;
66
+ const quemDecidiu = valor(bruto, exports.NOMES_HISTORICOS_DE_QUEM);
67
+ const razao = valor(bruto, exports.NOMES_HISTORICOS_DE_RAZAO);
68
+ const evidencia = valor(bruto, exports.NOMES_HISTORICOS_DE_EVIDENCIA);
69
+ const tipada = ['quemDecidiu', 'evidencia', 'razao'].every(c => typeof bruto[c] === 'string' && !!String(bruto[c]).trim());
70
+ return { quemDecidiu, razao, evidencia, tipada, completa: !!(quemDecidiu && razao && evidencia) };
71
+ }
72
+ /**
73
+ * O emissor de `classe: decidido`.
74
+ *
75
+ * A decisao so existe se passar pelo contrato v2 (os quatro campos, sem campo de gate, nunca
76
+ * irreversivel) e se o criterio RESOLVER: manifesto e ledger sao lidos de verdade, medicao tem a
77
+ * forma conferida e a execucao fica com o CHECK. O rastro e gravado no mesmo evento, sob o lock da
78
+ * thread: nao existe decisao informada sem rastro, nem rastro de uma decisao que nao chegou ao dono.
79
+ */
80
+ function registrarDecisao(raiz, threadId, entrada) {
81
+ const t = (0, thread_1.lerThread)(raiz, threadId);
82
+ if (t.status === 'fechada')
83
+ throw new Error('decisão informada: thread fechada não decide mais nada');
84
+ const quando = entrada.quando ?? new Date().toISOString();
85
+ // I-43: thread em modo aposentado e lida para sempre, mas nao escreve pedido novo.
86
+ const modo = (0, modos_1.exigirModoVivo)(t.modo);
87
+ const pedido = {
88
+ contrato: hitl_contract_1.CONTRATO_HITL_V2, classe: 'decidido', id: (0, node_crypto_1.randomUUID)(), thread: t.id, fase: t.faseAtual, modo,
89
+ criadoEm: quando, profundidade: (0, hitl_contract_1.profundidadeDoPedido)(t.faseAtual, modo),
90
+ decidido: entrada.decidido, porque: entrada.porque, comoMudar: entrada.comoMudar,
91
+ custoDeReverter: { agora: entrada.custoDeReverter.agora, depois: entrada.custoDeReverter.depois },
92
+ criterio: { tipo: entrada.criterio.tipo, referencia: entrada.criterio.referencia },
93
+ };
94
+ (0, hitl_contract_1.validarPedidoHitlV2)(pedido);
95
+ // R1, camada 1: criterio que aponta para nada vale o mesmo que criterio ausente.
96
+ const { resolve, detalhe } = (0, hitl_gates_1.resolverCriterio)(raiz, pedido);
97
+ if (!resolve)
98
+ throw new Error(`decisão informada: critério não resolve (${detalhe})`);
99
+ const rastro = { quemDecidiu: entrada.quemDecidiu, evidencia: entrada.evidencia, razao: entrada.razao ?? entrada.porque };
100
+ // O registro confere os tres de novo; conferir aqui evita tomar o lock para recusar.
101
+ (0, ledger_1.exigirRastroDeDecisaoAutonoma)(rastro);
102
+ return (0, hitl_gates_1.comLockHitl)(raiz, t.id, () => {
103
+ const agora = (0, thread_1.lerThread)(raiz, t.id), dir = (0, thread_1.dirThread)(raiz, t.id);
104
+ if (agora.status === 'fechada' || agora.faseAtual !== pedido.fase)
105
+ throw new Error('decisão informada: a fase mudou enquanto ela era registrada');
106
+ if (entrada.reverte && !(0, ledger_1.lerLedger)(dir).some(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.decisaoAutonoma &&
107
+ e.pedido?.id === entrada.reverte)) {
108
+ throw new Error('decisão informada: a decisão revertida não existe nesta thread');
109
+ }
110
+ const evento = (0, ledger_1.registrar)(dir, t.id, ledger_1.TIPOS_DE_EVENTO.decisaoAutonoma, {
111
+ contratoDecisao: exports.CONTRATO_DECISAO_AUTONOMA, fase: pedido.fase, decisao: pedido.decidido, ...rastro,
112
+ pedido, ...(entrada.reverte ? { reverte: entrada.reverte } : {}),
113
+ });
114
+ return { pedido, evento };
115
+ });
116
+ }
117
+ function placarDaThread(eventos) {
118
+ const fases = new Map();
119
+ const da = (fase) => {
120
+ const chave = typeof fase === 'string' && fase ? fase : 'sem-fase';
121
+ if (!fases.has(chave))
122
+ fases.set(chave, { fase: chave, decididas: 0, informadas: 0, perguntas: 0, revertidas: 0, semRastro: 0, acimaDoLimiar: false });
123
+ return fases.get(chave);
124
+ };
125
+ const revertidos = new Set(eventos.filter(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.decisaoAutonoma && typeof e.reverte === 'string')
126
+ .map(e => String(e.reverte)));
127
+ for (const e of eventos) {
128
+ if (e.tipo === ledger_1.TIPOS_DE_EVENTO.decisaoAutonoma) {
129
+ const p = da(e.fase);
130
+ p.decididas++;
131
+ const id = e.pedido?.id;
132
+ if (id)
133
+ p.informadas++;
134
+ if (id && revertidos.has(id))
135
+ p.revertidas++;
136
+ if (!rastroDaDecisao(e).completa)
137
+ p.semRastro++;
138
+ }
139
+ else if (e.tipo === 'hitl_requested' && e.pedido && (0, hitl_contract_1.alvoDoPedido)(e.pedido)) {
140
+ da(e.fase ?? e.pedido.fase).perguntas++;
141
+ }
142
+ }
143
+ for (const p of fases.values())
144
+ p.acimaDoLimiar = p.decididas > exports.LIMIAR_DE_DECISOES_POR_FASE;
145
+ return [...fases.values()];
146
+ }
147
+ /** A taxa de reversao das decisoes informadas: o sinal de "obvia" que nao era obvia para o dono. */
148
+ function taxaDeReversao(placar) {
149
+ const informadas = placar.reduce((n, p) => n + p.informadas, 0);
150
+ return informadas ? placar.reduce((n, p) => n + p.revertidas, 0) / informadas : null;
151
+ }
152
+ /**
153
+ * As decisoes informadas recentes de todas as threads abertas, e as fases que passaram do
154
+ * limiar. Somente leitura. Thread fechada nao registra decisao nova, entao nao precisa ser lida.
155
+ */
156
+ function decisoesParaODono(raiz, desde) {
157
+ const decisoes = [], acimaDoLimiar = [];
158
+ for (const id of (0, thread_1.listarIds)(raiz)) {
159
+ let t;
160
+ try {
161
+ t = (0, thread_1.lerThread)(raiz, id);
162
+ }
163
+ catch {
164
+ continue;
165
+ }
166
+ if (t.status === 'fechada')
167
+ continue;
168
+ const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id));
169
+ for (const e of eventos) {
170
+ const p = e.pedido;
171
+ if (e.tipo !== ledger_1.TIPOS_DE_EVENTO.decisaoAutonoma || p?.classe !== 'decidido' || e.ts < desde)
172
+ continue;
173
+ decisoes.push({ id: p.id, thread: id, fase: p.fase, em: e.ts, decidido: p.decidido, porque: p.porque,
174
+ comoMudar: p.comoMudar, custoDeReverter: p.custoDeReverter, quemDecidiu: String(e.quemDecidiu ?? ''),
175
+ ...(typeof e.reverte === 'string' ? { reverte: e.reverte } : {}) });
176
+ }
177
+ const atual = placarDaThread(eventos).find(f => f.fase === t.faseAtual);
178
+ if (atual?.acimaDoLimiar)
179
+ acimaDoLimiar.push({ thread: id, fase: atual.fase, decididas: atual.decididas });
180
+ }
181
+ decisoes.sort((a, b) => a.em.localeCompare(b.em));
182
+ return { decisoes, acimaDoLimiar };
183
+ }