@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.
- package/LICENSE +1 -1
- package/README.md +86 -65
- package/adapters/README.md +30 -15
- package/adapters/claude-code/.claude-plugin/plugin.json +11 -5
- package/adapters/claude-code/README.md +110 -8
- package/adapters/claude-code/commands/check.md +4 -3
- package/adapters/claude-code/commands/goal.md +3 -2
- package/adapters/claude-code/commands/master.md +3 -2
- package/adapters/claude-code/commands/onboarding.md +21 -0
- package/adapters/claude-code/commands/ork.md +99 -27
- package/adapters/claude-code/hooks/hooks.json +6 -1
- package/adapters/claude-code/hooks/ork-guard.js +1 -1
- package/adapters/claude-code/hooks/ork-sensor.js +66 -0
- package/adapters/codex/skills/ork/SKILL.md +57 -0
- package/adapters/hermes/README.md +161 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
- package/adapters/hermes/bin/ork-brain.sh +4 -0
- package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
- package/adapters/hermes/bin/ork-maestro.sh +6 -0
- package/adapters/hermes/bin/ork-master-enviar.py +84 -0
- package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
- package/adapters/hermes/hermes.plugin.json +24 -4
- package/adapters/hermes/hitl-ingress/__init__.py +356 -0
- package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
- package/adapters/openclaw/README.md +136 -27
- package/adapters/openclaw/bin/ork-brain.sh +4 -0
- package/adapters/openclaw/construir.sh +19 -0
- package/adapters/openclaw/dist/hitl-ingress.js +199 -0
- package/adapters/openclaw/dist/index.js +437 -0
- package/adapters/openclaw/openclaw.plugin.json +39 -134
- package/adapters/openclaw/package.json +26 -0
- package/adapters/openclaw/src/hitl-ingress.ts +180 -0
- package/adapters/openclaw/src/index.ts +460 -0
- package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
- package/adapters/openclaw/tsconfig.json +15 -0
- package/assets/docs/markdownlint-cli2.jsonc +22 -0
- package/assets/docs/padroes/documentacao-de-produto.md +133 -0
- package/assets/docs/padroes/roadmap-de-produto.md +98 -0
- package/assets/docs/produto/README.md +14 -0
- package/assets/docs/produto/_modelo-feature.md +59 -0
- package/assets/docs/roadmap/README.md +14 -0
- package/assets/docs/roadmap/_modelo-item.md +73 -0
- package/assets/orkmind-native-schema.json +32 -0
- package/assets/orkmind_bridge.py +357 -0
- package/assets/orkmind_fixture.py +67 -0
- package/assets/orkmind_prospective.py +138 -0
- package/assets/reference-tariffs-i07.json +23 -0
- package/dist/adapters/claude-bg.js +488 -39
- package/dist/adapters/codex-controller-sensor.js +449 -0
- package/dist/adapters/codex-controller-worker.js +361 -0
- package/dist/adapters/codex-controller.js +252 -0
- package/dist/adapters/codex-events.js +205 -0
- package/dist/adapters/codex-question.js +26 -0
- package/dist/adapters/codex-runner.js +133 -0
- package/dist/adapters/codex.js +394 -0
- package/dist/agents-md.js +84 -0
- package/dist/auditoria.js +12 -9
- package/dist/auditrun.js +5 -4
- package/dist/board.js +174 -23
- package/dist/branch-de-estado.js +136 -0
- package/dist/canarios-hitl.js +177 -0
- package/dist/canarios-i43.js +543 -0
- package/dist/canarios-pulse.js +147 -0
- package/dist/canarios-sensores.js +129 -0
- package/dist/canarios.js +111 -2
- package/dist/catalogo.js +9 -0
- package/dist/ci.js +223 -0
- package/dist/ciclos.js +2 -1
- package/dist/claim-lint.js +64 -0
- package/dist/claims.js +60 -0
- package/dist/company-brain-capture.js +195 -0
- package/dist/company-brain-cli.js +123 -0
- package/dist/company-brain-client.js +61 -0
- package/dist/company-brain-contract.js +166 -0
- package/dist/company-brain-journal.js +192 -0
- package/dist/company-brain-mcp.js +33 -0
- package/dist/company-brain-migration.js +60 -0
- package/dist/company-brain-source.js +177 -0
- package/dist/company-brain-worker.js +18 -0
- package/dist/conducao-texto.js +61 -0
- package/dist/conducao.js +876 -0
- package/dist/contrato-publico.js +39 -0
- package/dist/creation-operation-store.js +251 -0
- package/dist/creation-operation.js +148 -0
- package/dist/decisao-autonoma.js +183 -0
- package/dist/delegation.js +79 -0
- package/dist/demo.js +120 -0
- package/dist/docs.js +828 -0
- package/dist/doctor.js +184 -25
- package/dist/entrega-pr.js +117 -0
- package/dist/escopo-escrita.js +71 -0
- package/dist/estado-thread.js +222 -0
- package/dist/evalrunner.js +12 -0
- package/dist/fabrica-estado.js +329 -0
- package/dist/fabrica-publicar.js +76 -0
- package/dist/fix.js +22 -1
- package/dist/gates.js +59 -11
- package/dist/handoff.js +39 -23
- package/dist/hitl-canais.js +333 -0
- package/dist/hitl-classificacao.js +131 -0
- package/dist/hitl-contract.js +465 -0
- package/dist/hitl-estado.js +136 -0
- package/dist/hitl-gates.js +554 -0
- package/dist/hitl-ingress-receipt.js +382 -0
- package/dist/hitl-local-atestado.js +57 -0
- package/dist/hitl-local-receipt.js +328 -0
- package/dist/hitl-local.js +143 -0
- package/dist/hitl-lock.js +139 -0
- package/dist/hitl-lote.js +223 -0
- package/dist/hitl-native-offer.js +97 -0
- package/dist/hitl-native.js +65 -0
- package/dist/hitl-presentation.js +209 -0
- package/dist/hitl-public-receipt.js +176 -0
- package/dist/hitl-resumo.js +209 -0
- package/dist/hitl-sessions.js +475 -0
- package/dist/hitl.js +711 -0
- package/dist/horario.js +269 -0
- package/dist/hosts.js +198 -22
- package/dist/index.js +1741 -94
- package/dist/indice.js +99 -0
- package/dist/init.js +28 -3
- package/dist/integracoes-locais.js +17 -0
- package/dist/leases.js +65 -21
- package/dist/ledger-stats.js +272 -0
- package/dist/ledger.js +109 -2
- package/dist/licoes.js +221 -0
- package/dist/liveness.js +218 -0
- package/dist/maestro-actions.js +51 -0
- package/dist/maestro-authority.js +152 -0
- package/dist/maestro-cli.js +97 -0
- package/dist/maestro-contract.js +55 -0
- package/dist/maestro-discovery.js +132 -0
- package/dist/maestro-runtime.js +268 -0
- package/dist/maestro-snapshot.js +86 -0
- package/dist/maestro-sources.js +224 -0
- package/dist/manifest.js +165 -6
- package/dist/maquina.js +102 -0
- package/dist/master-audit.js +101 -0
- package/dist/master-batch.js +43 -0
- package/dist/master-digest.js +149 -0
- package/dist/master-migracao.js +155 -0
- package/dist/master.js +361 -77
- package/dist/mcp-artifacts.js +234 -0
- package/dist/mcp-git.js +433 -0
- package/dist/mcp-install.js +310 -0
- package/dist/mcp-maestro.js +25 -0
- package/dist/mcp-server.js +469 -0
- package/dist/mcp-ship.js +436 -0
- package/dist/mcp-verify.js +173 -0
- package/dist/memoria-humana.js +207 -0
- package/dist/memoria.js +314 -78
- package/dist/memory-migration.js +279 -0
- package/dist/memory-prospective.js +148 -0
- package/dist/modos-migracao.js +191 -0
- package/dist/modos.js +151 -15
- package/dist/monitor-lock.js +110 -0
- package/dist/objective.js +449 -0
- package/dist/ocupacao.js +267 -0
- package/dist/onboarding.js +303 -0
- package/dist/orkmind.js +389 -90
- package/dist/orquestracao.js +96 -69
- package/dist/phase.js +612 -151
- package/dist/playbook-capabilities.js +150 -0
- package/dist/playbook-contracts.js +188 -0
- package/dist/policies.js +72 -0
- package/dist/portfolio-context.js +61 -0
- package/dist/portfolio.js +180 -0
- package/dist/preflight.js +207 -0
- package/dist/process-audit.js +112 -0
- package/dist/project-state.js +93 -0
- package/dist/prompts.js +25 -5
- package/dist/prova-minima.js +112 -0
- package/dist/pulse-cadencia.js +163 -0
- package/dist/pulse-consentimento.js +260 -0
- package/dist/pulse-delivery.js +323 -0
- package/dist/pulse-resposta.js +542 -0
- package/dist/pulse.js +262 -0
- package/dist/ratelimit.js +35 -2
- package/dist/recall.js +80 -3
- package/dist/redacao-saida.js +45 -0
- package/dist/redacao-url.js +66 -0
- package/dist/retry.js +640 -118
- package/dist/roadmap-reservas.js +243 -0
- package/dist/runtime-ambiente.js +38 -0
- package/dist/runtime-context.js +215 -0
- package/dist/runtime-profiles.js +869 -0
- package/dist/runtimes.js +103 -0
- package/dist/sandbox.js +9 -0
- package/dist/session-events.js +203 -0
- package/dist/session-watcher-claude.js +637 -0
- package/dist/session-watcher.js +700 -0
- package/dist/sessoes-adopt.js +154 -0
- package/dist/sessoes-inventario.js +159 -0
- package/dist/sessoes.js +16 -35
- package/dist/setup.js +691 -0
- package/dist/ship.js +168 -19
- package/dist/slug.js +1 -1
- package/dist/thread-close.js +115 -0
- package/dist/thread.js +142 -28
- package/dist/tokens.js +1 -1
- package/dist/util.js +4 -2
- package/dist/verify-sandbox.js +265 -0
- package/dist/verify.js +270 -25
- package/dist/versao.js +60 -0
- package/dist/worktree.js +31 -2
- package/dist/write-activation.js +291 -0
- package/dist/yaml.js +5 -1
- package/eval/casos/master-metrics.json +1 -1
- package/eval/casos/onboarding.json +102 -0
- package/eval/casos/orkastery-bootstrap.json +37 -3
- package/eval/casos/scope-check-capability-map.json +45 -7
- package/eval/casos/ship-release.json +3 -3
- package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
- package/eval/fixtures/b2-master-log/caso.json +1 -1
- package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
- package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
- package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
- package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
- package/eval/fixtures/fx-codex-dry/caso.json +18 -0
- package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
- package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
- package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
- package/eval/fixtures/fx-happy/caso.json +6 -4
- package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
- package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
- package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
- package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
- package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
- package/eval/fixtures/fx-omnicanal/caso.json +17 -0
- package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
- package/monitor/company-brain.cjs +17 -0
- package/monitor/pulse-scope.cjs +45 -0
- package/monitor/pulse.cron +18 -0
- package/monitor/varredura-pulse.sh +27 -0
- package/package.json +28 -7
- package/references/definition-of-done.md +2 -2
- package/schemas/claims.schema.json +25 -0
- package/schemas/company-brain.schema.json +1158 -0
- package/schemas/creation-operation.schema.json +365 -0
- package/schemas/maestro-snapshot.schema.json +2716 -0
- package/skills/README.md +3 -3
- package/skills/core/onboarding/SKILL.md +50 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
- package/skills/core/thread-state/SKILL.md +3 -1
- package/skills/governance/decision-triage/SKILL.md +4 -4
- package/skills/governance/narrative-guardian/SKILL.md +1 -1
- package/skills/governance/roadmap-keeper/SKILL.md +1 -1
- package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
- package/skills/observability/thread-tracing/SKILL.md +1 -1
- package/skills/phases/check-quality/SKILL.md +2 -2
- package/skills/phases/goal-definition/SKILL.md +1 -1
- package/skills/phases/master-metrics/SKILL.md +9 -7
- package/skills/phases/plan-specification/SKILL.md +1 -1
- package/skills/phases/ship-release/SKILL.md +1 -1
|
@@ -0,0 +1,123 @@
|
|
|
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.BRAIN_WRITE = exports.BRAIN_READ = void 0;
|
|
37
|
+
exports.runBrain = runBrain;
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const project_state_1 = require("./project-state");
|
|
41
|
+
const portfolio_1 = require("./portfolio");
|
|
42
|
+
const thread_1 = require("./thread");
|
|
43
|
+
const write_activation_1 = require("./write-activation");
|
|
44
|
+
const ledger_1 = require("./ledger");
|
|
45
|
+
const company_brain_client_1 = require("./company-brain-client");
|
|
46
|
+
const company_brain_capture_1 = require("./company-brain-capture");
|
|
47
|
+
const company_brain_source_1 = require("./company-brain-source");
|
|
48
|
+
const company_brain_migration_1 = require("./company-brain-migration");
|
|
49
|
+
const company_brain_journal_1 = require("./company-brain-journal");
|
|
50
|
+
const company_brain_contract_1 = require("./company-brain-contract");
|
|
51
|
+
exports.BRAIN_READ = ['status', 'inventory', 'get', 'query', 'receipts', 'reconcile'];
|
|
52
|
+
exports.BRAIN_WRITE = ['sync', 'apply', 'rollback', 'bind'];
|
|
53
|
+
function runBrain(c, sub, options, positionals = [], transport) {
|
|
54
|
+
const context = (0, project_state_1.memoryState)(c);
|
|
55
|
+
c = context.loaded;
|
|
56
|
+
const config = c.manifesto.memory;
|
|
57
|
+
if (![...exports.BRAIN_READ, ...exports.BRAIN_WRITE].includes(sub))
|
|
58
|
+
throw Error('brain.operation.invalid');
|
|
59
|
+
const allowed = new Set(['thread', 'json', 'id', 'ids', 'kinds', 'workspaces', 'limit', 'offset', 'batch', 'dry-run', 'plan', 'expected-sha256', 'project', 'initiatives']);
|
|
60
|
+
if (Object.keys(options).some(k => !allowed.has(k)))
|
|
61
|
+
throw Error('brain.argument.invalid');
|
|
62
|
+
const value = (name) => typeof options[name] === 'string' ? options[name] : undefined;
|
|
63
|
+
const thread = value('thread');
|
|
64
|
+
let scope = { tenant: config.tenant, instance: c.manifesto.project.name, thread: thread ?? '', aclRef: 'ork-factory' };
|
|
65
|
+
const client = transport ?? (0, company_brain_client_1.brainClient)(c);
|
|
66
|
+
if (sub === 'status')
|
|
67
|
+
return { ...client({ schema: company_brain_client_1.BRAIN_API, operation: 'capabilities' }), tenant: config.tenant, configSource: context.source };
|
|
68
|
+
if (sub === 'get' || sub === 'receipts')
|
|
69
|
+
return client({ schema: company_brain_client_1.BRAIN_API, operation: sub, payload: { tenant_id: config.tenant, [sub === 'get' ? 'id' : 'event_id']: value('id') ?? positionals[0] ?? '' } });
|
|
70
|
+
if (sub === 'query' || sub === 'inventory') {
|
|
71
|
+
const split = (name) => (value(name) ?? '').split(',').filter(Boolean);
|
|
72
|
+
return client({ schema: company_brain_client_1.BRAIN_API, operation: 'query', payload: (0, company_brain_contract_1.validateContract)({ schema: 'orkmind.company-brain-selection/v1', tenant_id: config.tenant,
|
|
73
|
+
facets: { ids: split('ids'), kinds: split('kinds'), workspace_ids: split('workspaces'), source_instances: [] }, mode: 'selection', limit: Number(value('limit') ?? 50), offset: Number(value('offset') ?? 0) }) });
|
|
74
|
+
}
|
|
75
|
+
if (!thread)
|
|
76
|
+
throw Error('brain.thread.required');
|
|
77
|
+
(0, thread_1.lerThread)(c.raiz, thread);
|
|
78
|
+
if (sub === 'reconcile') {
|
|
79
|
+
if (options['dry-run'] !== true)
|
|
80
|
+
throw Error('brain.migration.dry-run-required');
|
|
81
|
+
return { state: 'ok', ...(0, company_brain_migration_1.reconcileBrain)(c, scope, value('batch') ?? 'inventory-' + (0, company_brain_contract_1.digest)((0, portfolio_1.readPortfolio)(c.raiz)).slice(0, 20)) };
|
|
82
|
+
}
|
|
83
|
+
// Existing reviewed activation plus dedicated Brain DB grants: neither grants the other.
|
|
84
|
+
(0, write_activation_1.exigirAtivacao)(c, thread, 'memory');
|
|
85
|
+
const dir = (0, thread_1.dirThread)(c.raiz, thread);
|
|
86
|
+
if (sub === 'bind') {
|
|
87
|
+
const selection = (0, portfolio_1.assertCycleScope)(c.raiz, { projectId: value('project') ?? '', delivery: 'initiatives', initiativeIds: (value('initiatives') ?? '').split(',').filter(Boolean) });
|
|
88
|
+
const binding = { schema: 'ork.brain-cycle-scope/v1', version: 1, thread, ...selection };
|
|
89
|
+
const file = path.join(dir, 'brain-scope.json');
|
|
90
|
+
if (fs.existsSync(file)) {
|
|
91
|
+
if ((0, company_brain_contract_1.digest)(JSON.parse(fs.readFileSync(file, 'utf8'))) !== (0, company_brain_contract_1.digest)(binding))
|
|
92
|
+
throw Error('brain.scope.immutable');
|
|
93
|
+
return { state: 'ok', binding };
|
|
94
|
+
}
|
|
95
|
+
(0, company_brain_journal_1.atomicJson)(file, binding);
|
|
96
|
+
(0, ledger_1.registrar)(dir, thread, 'brain_scope_recorded', { binding, sha256: (0, company_brain_contract_1.digest)(binding) });
|
|
97
|
+
return { state: 'ok', binding };
|
|
98
|
+
}
|
|
99
|
+
if (sub === 'sync') {
|
|
100
|
+
const bindingFile = path.join(dir, 'brain-scope.json');
|
|
101
|
+
if (fs.existsSync(bindingFile)) {
|
|
102
|
+
const binding = JSON.parse(fs.readFileSync(bindingFile, 'utf8'));
|
|
103
|
+
if (binding.schema !== 'ork.brain-cycle-scope/v1' || binding.version !== 1 || binding.thread !== thread)
|
|
104
|
+
throw Error('brain.scope.invalid');
|
|
105
|
+
const selection = (0, portfolio_1.assertCycleScope)(c.raiz, { projectId: binding.projectId, delivery: binding.delivery, initiativeIds: binding.initiativeIds });
|
|
106
|
+
scope = { ...scope, projectId: selection.projectId, initiativeIds: selection.initiativeIds };
|
|
107
|
+
}
|
|
108
|
+
return (0, company_brain_capture_1.capture)(scope, path.join(dir, 'brain'), (0, portfolio_1.readPortfolio)(c.raiz), (0, company_brain_source_1.readSourceFile)(path.join(dir, 'ledger.jsonl')), client, undefined, (0, company_brain_journal_1.readJournal)((0, project_state_1.stateFile)(c.raiz, 'portfolio.json') + '.journal.jsonl'));
|
|
109
|
+
}
|
|
110
|
+
if (sub === 'apply') {
|
|
111
|
+
const filename = value('plan');
|
|
112
|
+
if (!filename)
|
|
113
|
+
throw Error('brain.migration.plan-required');
|
|
114
|
+
const file = path.resolve(c.raiz, filename);
|
|
115
|
+
if (!file.startsWith(path.resolve(c.raiz) + path.sep))
|
|
116
|
+
throw Error('brain.migration.path-invalid');
|
|
117
|
+
const plan = JSON.parse((0, company_brain_source_1.readSourceFile)(file, 2 * 1024 * 1024).toString('utf8'));
|
|
118
|
+
return (0, company_brain_migration_1.applyBrain)(c, plan, value('expected-sha256') ?? '');
|
|
119
|
+
}
|
|
120
|
+
if (sub === 'rollback')
|
|
121
|
+
return (0, company_brain_migration_1.brainAdmin)(c)('rollback', { batch_id: value('batch') ?? '' });
|
|
122
|
+
throw Error('brain.operation.invalid');
|
|
123
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BRAIN_API = void 0;
|
|
4
|
+
exports.validateRequest = validateRequest;
|
|
5
|
+
exports.brainClient = brainClient;
|
|
6
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
|
+
const project_state_1 = require("./project-state");
|
|
8
|
+
const company_brain_contract_1 = require("./company-brain-contract");
|
|
9
|
+
exports.BRAIN_API = 'orkmind.company-brain-api/v1';
|
|
10
|
+
function validateRequest(value) {
|
|
11
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || value.schema !== exports.BRAIN_API ||
|
|
12
|
+
Object.keys(value).some(k => !['schema', 'operation', 'payload'].includes(k)) ||
|
|
13
|
+
!['capabilities', 'ingest', 'get', 'query', 'receipts', 'head'].includes(value.operation))
|
|
14
|
+
throw Error('brain.api.invalid');
|
|
15
|
+
if (['ingest', 'query'].includes(value.operation)) {
|
|
16
|
+
const body = (0, company_brain_contract_1.validateContract)(value.payload);
|
|
17
|
+
if (body.schema !== `orkmind.company-brain-${value.operation === 'ingest' ? 'event' : 'selection'}/v1`)
|
|
18
|
+
throw Error('brain.api.invalid');
|
|
19
|
+
}
|
|
20
|
+
if (['get', 'receipts', 'head'].includes(value.operation)) {
|
|
21
|
+
const keys = ['tenant_id', value.operation === 'receipts' ? 'event_id' : 'id'];
|
|
22
|
+
if (!value.payload || Object.keys(value.payload).length !== 2 || keys.some(k => typeof value.payload[k] !== 'string' ||
|
|
23
|
+
!/^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,159}$/.test(value.payload[k])))
|
|
24
|
+
throw Error('brain.api.invalid');
|
|
25
|
+
}
|
|
26
|
+
return JSON.parse(JSON.stringify(value));
|
|
27
|
+
}
|
|
28
|
+
function brainClient(loaded, execute = node_child_process_1.spawnSync) {
|
|
29
|
+
const context = (0, project_state_1.memoryState)(loaded), config = context.loaded.manifesto.memory;
|
|
30
|
+
return raw => {
|
|
31
|
+
const request = validateRequest(raw);
|
|
32
|
+
const unavailable = (error) => ({ schema: exports.BRAIN_API, state: 'unavailable', error });
|
|
33
|
+
const dsn = config.database_url_env ? process.env[config.database_url_env] : undefined;
|
|
34
|
+
if (config.mode !== 'orkmind' || (!dsn && request.operation !== 'capabilities'))
|
|
35
|
+
return unavailable('brain.configuration.missing');
|
|
36
|
+
const payload = request.payload;
|
|
37
|
+
if (payload?.tenant_id !== undefined && payload.tenant_id !== config.tenant)
|
|
38
|
+
return { schema: exports.BRAIN_API, state: 'forbidden', error: 'brain.tenant.mismatch' };
|
|
39
|
+
const result = execute(config.cli, ['brain', 'request'], { cwd: loaded.raiz, encoding: 'utf8', timeout: Math.min(config.timeout_ms, 15000),
|
|
40
|
+
maxBuffer: 2 * 1024 * 1024, input: JSON.stringify(request), shell: false,
|
|
41
|
+
env: { PATH: process.env.PATH ?? '/usr/bin:/bin', PYTHONDONTWRITEBYTECODE: '1',
|
|
42
|
+
ORKMIND_DATABASE_URL: dsn ?? '', ORKMIND_BRAIN_TENANT: config.tenant } });
|
|
43
|
+
if (result.error || result.signal)
|
|
44
|
+
return unavailable('brain.transport.unavailable');
|
|
45
|
+
try {
|
|
46
|
+
const response = JSON.parse(String(result.stdout));
|
|
47
|
+
if (response.schema !== exports.BRAIN_API || !['ok', 'empty', 'unknown', 'unavailable', 'withheld', 'forbidden', 'conflict'].includes(response.state))
|
|
48
|
+
throw Error();
|
|
49
|
+
if (response.state === 'ok' && result.status !== 0)
|
|
50
|
+
throw Error();
|
|
51
|
+
if (request.operation === 'capabilities' && response.state === 'ok' && response.contract_hash !== company_brain_contract_1.CONTRACT_HASH)
|
|
52
|
+
return unavailable('brain.contract.mismatch');
|
|
53
|
+
if (response.error && !/^brain\.[a-z0-9.-]+$/.test(response.error))
|
|
54
|
+
return unavailable('brain.transport.invalid');
|
|
55
|
+
return response;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return unavailable('brain.transport.invalid');
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
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.digest = exports.canonical = exports.CONTRACT_HASH = void 0;
|
|
37
|
+
exports.validateContract = validateContract;
|
|
38
|
+
exports.validateCatalog = validateCatalog;
|
|
39
|
+
/** D6: consume o schema canônico OrkMind; transporte não concede autoridade. */
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
42
|
+
const node_crypto_1 = require("node:crypto");
|
|
43
|
+
const file = path.resolve(__dirname, __dirname.endsWith(`${path.sep}src`) ? '../../schemas/company-brain.schema.json' : '../schemas/company-brain.schema.json');
|
|
44
|
+
const schema = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
45
|
+
exports.CONTRACT_HASH = (0, node_crypto_1.createHash)('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
46
|
+
const canonical = (v) => Array.isArray(v) ? `[${v.map(exports.canonical).join(',')}]`
|
|
47
|
+
: v !== null && typeof v === 'object' ? `{${Object.keys(v).sort().map(k => `${JSON.stringify(k)}:${(0, exports.canonical)(v[k])}`).join(',')}}`
|
|
48
|
+
: JSON.stringify(v);
|
|
49
|
+
exports.canonical = canonical;
|
|
50
|
+
const digest = (v) => (0, node_crypto_1.createHash)('sha256').update((0, exports.canonical)(v)).digest('hex');
|
|
51
|
+
exports.digest = digest;
|
|
52
|
+
const fail = () => { throw Error('brain.contract.invalid'); };
|
|
53
|
+
/** Closed vocabulary emitted by Pydantic. Unknown validation keywords fail closed. */
|
|
54
|
+
function matches(v, rule, depth = 0) {
|
|
55
|
+
if (depth > 32)
|
|
56
|
+
return false;
|
|
57
|
+
const known = ['$ref', 'anyOf', 'type', 'const', 'enum', 'properties', 'required', 'additionalProperties', 'items',
|
|
58
|
+
'minItems', 'maxItems', 'minLength', 'maxLength', 'pattern', 'minimum', 'maximum', 'title', 'description'];
|
|
59
|
+
if (Object.keys(rule).some(k => !known.includes(k)))
|
|
60
|
+
return false;
|
|
61
|
+
if (rule.$ref)
|
|
62
|
+
return rule.$ref.startsWith('#/$defs/') && matches(v, schema.$defs[rule.$ref.slice(8)], depth + 1);
|
|
63
|
+
if (rule.anyOf)
|
|
64
|
+
return rule.anyOf.some((r) => matches(v, r, depth + 1));
|
|
65
|
+
if ('const' in rule && v !== rule.const || rule.enum && !rule.enum.includes(v))
|
|
66
|
+
return false;
|
|
67
|
+
switch (rule.type) {
|
|
68
|
+
case 'null': return v === null;
|
|
69
|
+
case 'boolean': return typeof v === 'boolean';
|
|
70
|
+
case 'integer': return Number.isSafeInteger(v) && (rule.minimum === undefined || v >= rule.minimum) && (rule.maximum === undefined || v <= rule.maximum);
|
|
71
|
+
case 'string': return typeof v === 'string' && (rule.minLength === undefined || [...v].length >= rule.minLength) &&
|
|
72
|
+
(rule.maxLength === undefined || [...v].length <= rule.maxLength) && (!rule.pattern || new RegExp(rule.pattern, 'u').test(v));
|
|
73
|
+
case 'array': return Array.isArray(v) && (rule.minItems === undefined || v.length >= rule.minItems) &&
|
|
74
|
+
(rule.maxItems === undefined || v.length <= rule.maxItems) && v.every(i => matches(i, rule.items, depth + 1));
|
|
75
|
+
case 'object': return v !== null && typeof v === 'object' && !Array.isArray(v) &&
|
|
76
|
+
(!rule.required || rule.required.every((k) => Object.hasOwn(v, k))) &&
|
|
77
|
+
Object.keys(v).every(k => Object.hasOwn(rule.properties ?? {}, k) ? matches(v[k], rule.properties[k], depth + 1) : rule.additionalProperties !== false);
|
|
78
|
+
default: return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function semantic(v) {
|
|
82
|
+
for (const [key, value] of Object.entries(v))
|
|
83
|
+
if ((key.endsWith('_at') || ['valid_from', 'valid_to'].includes(key)) && value !== null) {
|
|
84
|
+
const time = new Date(value);
|
|
85
|
+
if (!Number.isFinite(time.getTime()) || time.toISOString().slice(0, 19) !== value.slice(0, 19))
|
|
86
|
+
fail();
|
|
87
|
+
}
|
|
88
|
+
if (v.schema.endsWith('entity/v1')) {
|
|
89
|
+
if (!v.id.startsWith(v.kind + '-'))
|
|
90
|
+
fail();
|
|
91
|
+
if (v.kind === 'prod' ? v.parent_id !== null : !v.parent_id?.startsWith(v.kind === 'proj' ? 'prod-' : 'proj-'))
|
|
92
|
+
fail();
|
|
93
|
+
if (v.kind !== 'init' && v.depends_on.length || v.kind !== 'proj' && v.workspace_ids.length)
|
|
94
|
+
fail();
|
|
95
|
+
if (v.depends_on.some((d) => !d.startsWith('init-') || d === v.id))
|
|
96
|
+
fail();
|
|
97
|
+
if ((v.owner.raw === null) !== (v.owner.state === 'unknown'))
|
|
98
|
+
fail();
|
|
99
|
+
}
|
|
100
|
+
if (v.schema.endsWith('event/v1')) {
|
|
101
|
+
if ((0, exports.digest)(v.payload) !== v.payload_hash || (v.operation === 'tombstone') !== (v.payload === null))
|
|
102
|
+
fail();
|
|
103
|
+
if (v.payload !== null) {
|
|
104
|
+
validateContract(v.payload);
|
|
105
|
+
if (v.payload.tenant_id !== v.tenant_id || v.payload.id !== v.aggregate_id || v.payload.acl_ref !== v.acl_ref ||
|
|
106
|
+
(0, exports.canonical)(v.payload.source) !== (0, exports.canonical)(v.source) ||
|
|
107
|
+
v.operation !== (v.payload.schema.endsWith('entity/v1') ? 'upsert' : 'assert'))
|
|
108
|
+
fail();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (v.schema.endsWith('assertion/v1')) {
|
|
112
|
+
if (v.object_id !== null && v.value !== null || v.valid_from && v.valid_to && v.valid_from >= v.valid_to)
|
|
113
|
+
fail();
|
|
114
|
+
}
|
|
115
|
+
if (v.schema.endsWith('migration/v1'))
|
|
116
|
+
for (const op of v.operations) {
|
|
117
|
+
validateContract(op.after);
|
|
118
|
+
if (op.id !== op.after.id || op.after.tenant_id !== v.tenant_id)
|
|
119
|
+
fail();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function validateContract(value) {
|
|
123
|
+
try {
|
|
124
|
+
if (!schema.anyOf.some((r) => matches(value, r)))
|
|
125
|
+
fail();
|
|
126
|
+
semantic(value);
|
|
127
|
+
return JSON.parse(JSON.stringify(value));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return fail();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function validateCatalog(values, scope) {
|
|
134
|
+
const entities = values.map(v => validateContract(v));
|
|
135
|
+
if (entities.some(e => e.schema !== 'orkmind.company-brain-entity/v1'))
|
|
136
|
+
fail();
|
|
137
|
+
const ids = new Map(entities.map(e => [e.id, e]));
|
|
138
|
+
if (ids.size !== entities.length || new Set(entities.map(e => e.tenant_id)).size > 1)
|
|
139
|
+
throw Error('brain.catalog.conflict');
|
|
140
|
+
for (const e of entities) {
|
|
141
|
+
if (e.parent_id !== null && !ids.has(e.parent_id))
|
|
142
|
+
throw Error('brain.catalog.parent-missing');
|
|
143
|
+
if (e.depends_on.some(d => !ids.has(d) || ids.get(d).parent_id !== e.parent_id))
|
|
144
|
+
throw Error('brain.catalog.dependency-invalid');
|
|
145
|
+
}
|
|
146
|
+
const visiting = new Set(), done = new Set();
|
|
147
|
+
const visit = (id) => {
|
|
148
|
+
if (visiting.has(id))
|
|
149
|
+
throw Error('brain.catalog.cycle');
|
|
150
|
+
if (done.has(id))
|
|
151
|
+
return;
|
|
152
|
+
visiting.add(id);
|
|
153
|
+
ids.get(id).depends_on.forEach(visit);
|
|
154
|
+
visiting.delete(id);
|
|
155
|
+
done.add(id);
|
|
156
|
+
};
|
|
157
|
+
entities.forEach(e => visit(e.id));
|
|
158
|
+
if (scope) {
|
|
159
|
+
const project = ids.get(scope.project_id), selected = scope.initiative_ids;
|
|
160
|
+
if (project?.kind !== 'proj' || !Array.isArray(selected) || !['project', 'initiatives'].includes(scope.delivery) ||
|
|
161
|
+
(scope.delivery === 'project' ? selected.length !== 0 : selected.length === 0) ||
|
|
162
|
+
selected.some(id => ids.get(id)?.parent_id !== project.id))
|
|
163
|
+
throw Error('brain.scope.invalid');
|
|
164
|
+
}
|
|
165
|
+
return entities;
|
|
166
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
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.bytesHash = void 0;
|
|
37
|
+
exports.withProcessLock = withProcessLock;
|
|
38
|
+
exports.durableAppend = durableAppend;
|
|
39
|
+
exports.atomicJson = atomicJson;
|
|
40
|
+
exports.readJournal = readJournal;
|
|
41
|
+
exports.recoverPortfolio = recoverPortfolio;
|
|
42
|
+
exports.replaceWithJournal = replaceWithJournal;
|
|
43
|
+
exports.portfolioMutation = portfolioMutation;
|
|
44
|
+
/** D9: journal recuperável para fontes em arquivo, sem reescrever o ledger. */
|
|
45
|
+
const fs = __importStar(require("node:fs"));
|
|
46
|
+
const path = __importStar(require("node:path"));
|
|
47
|
+
const node_crypto_1 = require("node:crypto");
|
|
48
|
+
const bytesHash = (v) => (0, node_crypto_1.createHash)('sha256').update(v).digest('hex');
|
|
49
|
+
exports.bytesHash = bytesHash;
|
|
50
|
+
function processIdentity(pid) {
|
|
51
|
+
try {
|
|
52
|
+
const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
53
|
+
const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
|
|
54
|
+
if (fields[0] === 'Z')
|
|
55
|
+
return null;
|
|
56
|
+
return fs.readFileSync('/proc/sys/kernel/random/boot_id', 'utf8').trim() + ':' + fields[19];
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (error.code === 'ENOENT')
|
|
60
|
+
return null;
|
|
61
|
+
throw Error('brain.lock.identity-unavailable');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Bakery lock with unique contender files. Reaping never unlinks a reusable lock name.
|
|
65
|
+
* Atomic publication + ticket order exclude live writers; PID start/boot identity handles reuse.
|
|
66
|
+
* Linux process identity is required; unidentifiable legacy locks fail closed. */
|
|
67
|
+
function withProcessLock(dir, busy, run) {
|
|
68
|
+
if (fs.existsSync(dir) && !fs.lstatSync(dir).isDirectory())
|
|
69
|
+
throw Error('brain.lock.recovery-required');
|
|
70
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
71
|
+
const identity = processIdentity(process.pid);
|
|
72
|
+
if (!identity)
|
|
73
|
+
throw Error('brain.lock.identity-unavailable');
|
|
74
|
+
const name = (0, node_crypto_1.randomUUID)() + '.json', file = path.join(dir, name);
|
|
75
|
+
const owner = { pid: process.pid, identity, ticket: 0 };
|
|
76
|
+
const contenders = () => {
|
|
77
|
+
const result = [];
|
|
78
|
+
for (const entry of fs.readdirSync(dir).filter(n => n.endsWith('.json'))) {
|
|
79
|
+
let value;
|
|
80
|
+
try {
|
|
81
|
+
value = JSON.parse(fs.readFileSync(path.join(dir, entry), 'utf8'));
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error.code === 'ENOENT')
|
|
85
|
+
continue;
|
|
86
|
+
throw Error('brain.lock.corrupt');
|
|
87
|
+
}
|
|
88
|
+
if (!Number.isSafeInteger(value.pid) || value.pid < 1 || typeof value.identity !== 'string' ||
|
|
89
|
+
!Number.isSafeInteger(value.ticket) || value.ticket < 0)
|
|
90
|
+
throw Error('brain.lock.corrupt');
|
|
91
|
+
if (processIdentity(value.pid) !== value.identity) {
|
|
92
|
+
// UUID names are never reused: a concurrent reaper cannot remove a new owner.
|
|
93
|
+
try {
|
|
94
|
+
fs.unlinkSync(path.join(dir, entry));
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (error.code !== 'ENOENT')
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else
|
|
102
|
+
result.push({ name: entry, ticket: value.ticket });
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
};
|
|
106
|
+
atomicJson(file, owner);
|
|
107
|
+
try {
|
|
108
|
+
owner.ticket = Math.max(0, ...contenders().map(c => c.ticket)) + 1;
|
|
109
|
+
atomicJson(file, owner);
|
|
110
|
+
if (contenders().some(c => c.name !== name && (c.ticket === 0 || c.ticket < owner.ticket ||
|
|
111
|
+
c.ticket === owner.ticket && c.name < name)))
|
|
112
|
+
throw Error(busy);
|
|
113
|
+
return run();
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
fs.unlinkSync(file);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function durableAppend(file, value) {
|
|
120
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
121
|
+
const fd = fs.openSync(file, 'a', 0o600);
|
|
122
|
+
try {
|
|
123
|
+
fs.writeFileSync(fd, JSON.stringify(value) + '\n');
|
|
124
|
+
fs.fsyncSync(fd);
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
fs.closeSync(fd);
|
|
128
|
+
}
|
|
129
|
+
syncDirectory(path.dirname(file));
|
|
130
|
+
}
|
|
131
|
+
function syncDirectory(dir) {
|
|
132
|
+
const fd = fs.openSync(dir, 'r');
|
|
133
|
+
try {
|
|
134
|
+
fs.fsyncSync(fd);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
fs.closeSync(fd);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function atomicJson(file, value) {
|
|
141
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
142
|
+
const tmp = file + '.' + (0, node_crypto_1.randomUUID)() + '.tmp';
|
|
143
|
+
const fd = fs.openSync(tmp, 'wx', 0o600);
|
|
144
|
+
try {
|
|
145
|
+
fs.writeFileSync(fd, JSON.stringify(value, null, 2) + '\n');
|
|
146
|
+
fs.fsyncSync(fd);
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
fs.closeSync(fd);
|
|
150
|
+
}
|
|
151
|
+
fs.renameSync(tmp, file);
|
|
152
|
+
syncDirectory(path.dirname(file));
|
|
153
|
+
}
|
|
154
|
+
function readJournal(file) {
|
|
155
|
+
if (!fs.existsSync(file))
|
|
156
|
+
return [];
|
|
157
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
158
|
+
if (!text.endsWith('\n'))
|
|
159
|
+
throw Error('brain.journal.incomplete');
|
|
160
|
+
try {
|
|
161
|
+
return text.trimEnd().split('\n').map(l => JSON.parse(l));
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
throw Error('brain.journal.corrupt');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function recoverPortfolio(file) {
|
|
168
|
+
const journal = file + '.journal.jsonl', entries = readJournal(journal);
|
|
169
|
+
const finished = new Set(entries.filter(e => e.type === 'confirmed' || e.type === 'aborted').map(e => e.id));
|
|
170
|
+
for (const intent of entries.filter(e => e.type === 'intent' && !finished.has(e.id))) {
|
|
171
|
+
const current = fs.existsSync(file) ? (0, exports.bytesHash)(fs.readFileSync(file)) : null;
|
|
172
|
+
if (current !== intent.beforeHash && current !== intent.afterHash)
|
|
173
|
+
throw Error('brain.journal.source-conflict');
|
|
174
|
+
durableAppend(journal, { type: current === intent.afterHash ? 'confirmed' : 'aborted', id: intent.id, recovered: true });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function replaceWithJournal(file, value, fault = () => { }) {
|
|
178
|
+
const beforeValue = fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')) : null;
|
|
179
|
+
const beforeHash = fs.existsSync(file) ? (0, exports.bytesHash)(fs.readFileSync(file)) : null;
|
|
180
|
+
const afterHash = (0, exports.bytesHash)(JSON.stringify(value, null, 2) + '\n'), id = (0, node_crypto_1.randomUUID)();
|
|
181
|
+
durableAppend(file + '.journal.jsonl', { schema: 'ork.brain-source-intent/v1', type: 'intent', id,
|
|
182
|
+
observedAt: new Date().toISOString(), beforeHash, afterHash, beforeValue, value });
|
|
183
|
+
fault('intent');
|
|
184
|
+
atomicJson(file, value);
|
|
185
|
+
fault('source');
|
|
186
|
+
durableAppend(file + '.journal.jsonl', { type: 'confirmed', id });
|
|
187
|
+
fault('confirmed');
|
|
188
|
+
}
|
|
189
|
+
function portfolioMutation(file, run) {
|
|
190
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
191
|
+
return withProcessLock(file + '.lock', 'brain.journal.busy', () => { recoverPortfolio(file); return run(); });
|
|
192
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BRAIN_WRITE_TOOLS = exports.BRAIN_READ_TOOLS = void 0;
|
|
4
|
+
exports.registerBrainTools = registerBrainTools;
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const company_brain_cli_1 = require("./company-brain-cli");
|
|
7
|
+
exports.BRAIN_READ_TOOLS = ['ork_brain_status', 'ork_brain_inventory', 'ork_brain_get', 'ork_brain_query', 'ork_brain_receipts'];
|
|
8
|
+
exports.BRAIN_WRITE_TOOLS = ['ork_brain_sync', 'ork_brain_apply', 'ork_brain_rollback', 'ork_brain_bind'];
|
|
9
|
+
/** Installation supplies exact grants; presence of a tool is never activation or human identity. */
|
|
10
|
+
function registerBrainTools(register, load, checkThread, writeGrants = []) {
|
|
11
|
+
if (writeGrants.some(n => !exports.BRAIN_WRITE_TOOLS.includes(n)))
|
|
12
|
+
throw Error('brain.mcp.grant-invalid');
|
|
13
|
+
const id = zod_1.z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/), base = zod_1.z.object({ threadId: id }).strict();
|
|
14
|
+
const definitions = [['status', base], ['inventory', base], ['get', base.extend({ id })], ['receipts', base.extend({ id })],
|
|
15
|
+
['query', base.extend({ ids: zod_1.z.array(id).max(1000).optional(), kinds: zod_1.z.array(zod_1.z.enum(['prod', 'proj', 'init', 'assertion'])).optional(), limit: zod_1.z.number().int().min(1).max(1000).optional() })],
|
|
16
|
+
['sync', base], ['apply', base.extend({ plan: zod_1.z.string().min(1).max(512), expectedSha256: zod_1.z.string().regex(/^[a-f0-9]{64}$/) })],
|
|
17
|
+
['rollback', base.extend({ batch: id })], ['bind', base.extend({ project: id, initiatives: zod_1.z.array(id).min(1).max(1000) })]];
|
|
18
|
+
for (const [operation, inputSchema] of definitions) {
|
|
19
|
+
const name = 'ork_brain_' + operation, writing = exports.BRAIN_WRITE_TOOLS.includes(name);
|
|
20
|
+
if (writing && !writeGrants.includes(name))
|
|
21
|
+
continue;
|
|
22
|
+
register(name, { description: `Company Brain ${operation}: estado e fontes pelo contrato canônico; identidade humana nunca vem de argumento.`, inputSchema: inputSchema.strict(), annotations: { readOnlyHint: !writing, destructiveHint: writing } }, async (args) => {
|
|
23
|
+
const c = load();
|
|
24
|
+
checkThread(args.threadId);
|
|
25
|
+
const options = { thread: args.threadId, json: true };
|
|
26
|
+
for (const [key, value] of Object.entries(args))
|
|
27
|
+
if (key !== 'threadId')
|
|
28
|
+
options[key === 'expectedSha256' ? 'expected-sha256' : key] = Array.isArray(value) ? value.join(',') : String(value);
|
|
29
|
+
const result = (0, company_brain_cli_1.runBrain)(c, operation, options);
|
|
30
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }], ...(['unavailable', 'forbidden', 'conflict'].includes(result.state) ? { isError: true } : {}) };
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.brainAdmin = brainAdmin;
|
|
4
|
+
exports.reconcileBrain = reconcileBrain;
|
|
5
|
+
exports.applyBrain = applyBrain;
|
|
6
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
|
+
const project_state_1 = require("./project-state");
|
|
8
|
+
const portfolio_1 = require("./portfolio");
|
|
9
|
+
const company_brain_source_1 = require("./company-brain-source");
|
|
10
|
+
const company_brain_journal_1 = require("./company-brain-journal");
|
|
11
|
+
const company_brain_contract_1 = require("./company-brain-contract");
|
|
12
|
+
function brainAdmin(c) {
|
|
13
|
+
const config = (0, project_state_1.memoryState)(c).loaded.manifesto.memory;
|
|
14
|
+
return (operation, payload) => {
|
|
15
|
+
const dsn = config.database_url_env ? process.env[config.database_url_env] : undefined;
|
|
16
|
+
if (config.mode !== 'orkmind' || !dsn)
|
|
17
|
+
throw Error('brain.configuration.missing');
|
|
18
|
+
const r = (0, node_child_process_1.spawnSync)(config.cli, ['brain', 'migration'], { cwd: c.raiz, shell: false, encoding: 'utf8', timeout: Math.min(config.timeout_ms, 15000), maxBuffer: 2 * 1024 * 1024,
|
|
19
|
+
input: JSON.stringify({ schema: 'orkmind.company-brain-admin/v1', operation, payload }),
|
|
20
|
+
env: { PATH: process.env.PATH ?? '/usr/bin:/bin', PYTHONDONTWRITEBYTECODE: '1', ORKMIND_DATABASE_URL: dsn, ORKMIND_BRAIN_TENANT: config.tenant } });
|
|
21
|
+
try {
|
|
22
|
+
const response = JSON.parse(r.stdout);
|
|
23
|
+
if (r.status !== 0 || response.state !== 'ok')
|
|
24
|
+
throw Error();
|
|
25
|
+
return response.result;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw Error('brain.migration.unconfirmed');
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function reconcileBrain(c, scope, batch, transport = brainAdmin(c)) {
|
|
33
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/.test(batch))
|
|
34
|
+
throw Error('brain.migration.batch-invalid');
|
|
35
|
+
const file = (0, project_state_1.stateFile)(c.raiz, 'portfolio.json'), before = (0, company_brain_journal_1.bytesHash)((0, company_brain_source_1.readSourceFile)(file));
|
|
36
|
+
const catalog = (0, portfolio_1.readPortfolio)(c.raiz);
|
|
37
|
+
const latest = new Map();
|
|
38
|
+
for (const entity of (0, company_brain_source_1.portfolioHistory)(catalog, scope, (0, company_brain_journal_1.readJournal)(file + '.journal.jsonl')))
|
|
39
|
+
latest.set(entity.id, entity);
|
|
40
|
+
const currentIds = new Set([...catalog.products, ...catalog.projects, ...catalog.initiatives].map(e => e.id));
|
|
41
|
+
const entities = [...latest.values()].filter(e => currentIds.has(e.id));
|
|
42
|
+
if ((0, company_brain_journal_1.bytesHash)((0, company_brain_source_1.readSourceFile)(file)) !== before)
|
|
43
|
+
throw Error('brain.migration.source-changed');
|
|
44
|
+
const plan = (0, company_brain_contract_1.validateContract)(transport('plan', { entities, batch_id: batch, source_hash: before, observed_at: catalog.updatedAt }));
|
|
45
|
+
if (plan.schema !== 'orkmind.company-brain-migration/v1' || plan.source_hash !== before || plan.tenant_id !== scope.tenant ||
|
|
46
|
+
plan.operations.length !== entities.length || new Set(plan.operations.map((o) => o.id)).size !== entities.length ||
|
|
47
|
+
entities.some(e => !plan.operations.some((o) => o.id === e.id && (0, company_brain_contract_1.digest)(o.after) === (0, company_brain_contract_1.digest)(e))))
|
|
48
|
+
throw Error('brain.migration.plan-invalid');
|
|
49
|
+
return { plan, sha256: (0, company_brain_contract_1.digest)(plan) };
|
|
50
|
+
}
|
|
51
|
+
function applyBrain(c, plan, expected, transport = brainAdmin(c)) {
|
|
52
|
+
const validated = (0, company_brain_contract_1.validateContract)(plan);
|
|
53
|
+
const config = (0, project_state_1.memoryState)(c).loaded.manifesto.memory;
|
|
54
|
+
if (validated.schema !== 'orkmind.company-brain-migration/v1' || validated.tenant_id !== config.tenant || (0, company_brain_contract_1.digest)(plan) !== expected)
|
|
55
|
+
throw Error('brain.migration.stale');
|
|
56
|
+
const current = (0, company_brain_journal_1.bytesHash)((0, company_brain_source_1.readSourceFile)((0, project_state_1.stateFile)(c.raiz, 'portfolio.json')));
|
|
57
|
+
if (current !== validated.source_hash)
|
|
58
|
+
throw Error('brain.migration.source-changed');
|
|
59
|
+
return transport('apply', { plan: validated, expected_hash: expected, current_source_hash: current });
|
|
60
|
+
}
|