@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,357 @@
|
|
|
1
|
+
"""Transporte de fabrica: OrkMind instalado, stdin JSON, sem provider ou embedder."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import asyncio
|
|
4
|
+
import contextlib
|
|
5
|
+
import hashlib
|
|
6
|
+
import io
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LegacyProvenanceCollision(ValueError):
|
|
14
|
+
"""Mesmo conteudo sem identidade comprovada: preservar e exigir tratamento."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ProspectiveMarkerInvalid(ValueError):
|
|
18
|
+
"""Integridade do marcador recusada; nao e falha de transporte."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NativeSchemaMismatch(ValueError):
|
|
22
|
+
"""Biblioteca instalada divergiu do contrato compartilhado; atualizacao exige revisao."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def native_schema_fields():
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from orkmind.core.models import MemoryEntry
|
|
28
|
+
contract = json.loads(Path(__file__).with_name('orkmind-native-schema.json').read_text())
|
|
29
|
+
fields = contract.get('fields', [])
|
|
30
|
+
if (contract.get('schema') != 'ork.native-entry-fields/v1'
|
|
31
|
+
or len(fields) != len(set(fields)) or set(fields) != set(MemoryEntry.model_fields)):
|
|
32
|
+
raise NativeSchemaMismatch('memory.native.schema-mismatch')
|
|
33
|
+
return set(fields)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def prospective_agent_valid(value):
|
|
37
|
+
import re
|
|
38
|
+
return (isinstance(value, str) and re.fullmatch('[a-z0-9.-]+:[a-z0-9.-]+', value) is not None
|
|
39
|
+
and 'human' not in value.lower())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class QueryError(ValueError):
|
|
43
|
+
def __init__(self, code):
|
|
44
|
+
self.code = code
|
|
45
|
+
super().__init__(code)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def error_code(error):
|
|
49
|
+
return (error.code if isinstance(error, QueryError)
|
|
50
|
+
else 'memory.native.schema-mismatch' if isinstance(error, NativeSchemaMismatch)
|
|
51
|
+
else 'memory.prospective.marker-invalid' if isinstance(error, ProspectiveMarkerInvalid)
|
|
52
|
+
else 'memory.legacy.provenance-collision' if isinstance(error, LegacyProvenanceCollision)
|
|
53
|
+
else 'memory.schema.absent' if type(error).__name__ == 'UndefinedTable' else 'memory.bridge.failed')
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def encoded(value):
|
|
57
|
+
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(',', ':'))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
PHASES = {'GOAL', 'PLAN', 'GO', 'CHECK', 'SHIP', 'MASTER'}
|
|
61
|
+
FACTORY_KEYS = {'orkastery_identity', 'orkastery_identity_version'}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def prospective_compatible(old, entry):
|
|
65
|
+
"""Recibo in-band integral preservado por duplicate sem update; nao autentica o passado."""
|
|
66
|
+
import re
|
|
67
|
+
fields = native_schema_fields()
|
|
68
|
+
try:
|
|
69
|
+
p = old.metadata['orkastery_prospective']
|
|
70
|
+
if (p['schema'] != 'ork.prospective-origin/v1' or p['historicalOrigin'] != 'unknown'
|
|
71
|
+
or p['source'] != 'agent' or p['entryId'] != old.id
|
|
72
|
+
or not re.fullmatch('[a-f0-9]{64}', p['receiptSha256'])
|
|
73
|
+
or not re.fullmatch('[a-f0-9]{40}', p['revision'])
|
|
74
|
+
or not prospective_agent_valid(p['agent'])
|
|
75
|
+
or hashlib.sha256(p['originalJson'].encode()).hexdigest() != p['originalSha256']):
|
|
76
|
+
return False
|
|
77
|
+
original = json.loads(p['originalJson'])
|
|
78
|
+
if set(original) != fields:
|
|
79
|
+
return False
|
|
80
|
+
tags = {k: sorted(set(original['tags'].get(k, [])) | set(entry.tags.get(k, [])))
|
|
81
|
+
for k in set(original['tags']) | set(entry.tags)}
|
|
82
|
+
return (old.collection == entry.collection == 'rule' and old.source == entry.source == 'agent'
|
|
83
|
+
and old.priority == entry.priority == 'high' and old.mandatory is False and old.protected is False
|
|
84
|
+
and old.visibility == original['visibility'] == 'private' and original['id'] == old.id
|
|
85
|
+
and original['content'] == old.content == entry.content and original['source'] == 'agent'
|
|
86
|
+
and original['priority'] == 'high' and original['mandatory'] is False
|
|
87
|
+
and original['protected'] is False and old.author_id == original['author_id']
|
|
88
|
+
and old.scope == original['scope'] == entry.scope and old.tags == tags
|
|
89
|
+
and old.metadata == {**entry.metadata, 'orkastery_prospective': p})
|
|
90
|
+
except (KeyError, TypeError, ValueError):
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def fixed_tags(tags):
|
|
95
|
+
"""Fase e runtime sao observacoes; tenant e demais classificacoes sao identidade."""
|
|
96
|
+
fixed = {}
|
|
97
|
+
for key, values in tags.items():
|
|
98
|
+
if key == 'agent':
|
|
99
|
+
continue
|
|
100
|
+
keep = [v for v in values if not (key == 'skill' and v in PHASES)
|
|
101
|
+
and not (key == 'situation' and v.startswith('fase:') and v[5:] in PHASES)]
|
|
102
|
+
if keep:
|
|
103
|
+
fixed[key] = sorted(set(keep))
|
|
104
|
+
return fixed
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def legacy_factory_identity(entry):
|
|
108
|
+
"""Reconhece somente fingerprints comprovaveis da ponte T5, sem adotar legado."""
|
|
109
|
+
if 'orkastery_identity_version' in entry.metadata:
|
|
110
|
+
return False
|
|
111
|
+
original = {'collection': entry.collection, 'content': entry.content, 'tags': entry.tags,
|
|
112
|
+
'priority': entry.priority,
|
|
113
|
+
'metadata': {k: v for k, v in entry.metadata.items() if k not in FACTORY_KEYS}}
|
|
114
|
+
for source in (False, True):
|
|
115
|
+
for mandatory in (False, True):
|
|
116
|
+
candidate = dict(original)
|
|
117
|
+
if source:
|
|
118
|
+
candidate['source'] = entry.source
|
|
119
|
+
if mandatory:
|
|
120
|
+
candidate['mandatory'] = False
|
|
121
|
+
if hashlib.sha256(encoded(candidate).encode()).hexdigest() == entry.metadata.get('orkastery_identity'):
|
|
122
|
+
return True
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def governance(entry):
|
|
127
|
+
if 'scope' in entry and entry['scope'] != 'project':
|
|
128
|
+
raise ValueError('scope outside project contract')
|
|
129
|
+
if entry.get('mandatory') or entry.get('source', 'agent') not in ('agent', 'human'):
|
|
130
|
+
raise ValueError('governance')
|
|
131
|
+
if entry['collection'] not in ('decision', 'handoff', 'rule', 'learning', 'roadmap'):
|
|
132
|
+
raise ValueError('collection')
|
|
133
|
+
if entry.get('priority') == 'critical' and entry['collection'] in ('rule', 'instruction'):
|
|
134
|
+
raise ValueError('critical')
|
|
135
|
+
if entry.get('source') == 'human':
|
|
136
|
+
p = entry.get('metadata', {}).get('proveniencia', {})
|
|
137
|
+
if (entry['collection'] != 'decision' or entry.get('priority') == 'critical'
|
|
138
|
+
or p.get('tipo') != 'human_gate' or p.get('source') != 'human'
|
|
139
|
+
or p.get('confirmado') is not True
|
|
140
|
+
or not all(isinstance(p.get(k), str) and p[k] for k in ('autor', 'evento', 'evidencia', 'sha256'))):
|
|
141
|
+
raise ValueError('human provenance')
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
async def readback(store, p, handoff):
|
|
145
|
+
package = await store.retrieve(handoff.metadata['package_entry_id'])
|
|
146
|
+
session = await store.retrieve(handoff.parent_id)
|
|
147
|
+
if not package or not session:
|
|
148
|
+
raise ValueError('incomplete chain')
|
|
149
|
+
if (session.collection != 'session' or package.collection != 'semantic_log'
|
|
150
|
+
or package.parent_id != session.id or handoff.parent_id != session.id
|
|
151
|
+
or session.metadata['session_id'] != p['sessionId']
|
|
152
|
+
or handoff.metadata['package_id'] != package.metadata['package_id']
|
|
153
|
+
or handoff.metadata['origin'] != p['origem']
|
|
154
|
+
or handoff.metadata['destination'] != p['destino']
|
|
155
|
+
or json.loads(package.content) != p['payload']):
|
|
156
|
+
raise ValueError('readback mismatch')
|
|
157
|
+
if not all(set(values) <= set(session.tags.get(k, [])) for k, values in p['tags'].items()):
|
|
158
|
+
raise ValueError('session tags mismatch')
|
|
159
|
+
for entry in (handoff, package):
|
|
160
|
+
if entry.metadata['orkastery_identity'] != p['identidade']:
|
|
161
|
+
raise ValueError('identity mismatch')
|
|
162
|
+
if not all(set(values) <= set(entry.tags.get(k, [])) for k, values in p['tags'].items()):
|
|
163
|
+
raise ValueError('tags mismatch')
|
|
164
|
+
if not all(entry.metadata.get(k) == v for k, v in p['metadata'].items()):
|
|
165
|
+
raise ValueError('metadata mismatch')
|
|
166
|
+
return {'ok': True, 'id': handoff.id, 'collection': 'handoff', 'readback': True,
|
|
167
|
+
'session_entry_id': session.id, 'package_entry_id': package.id,
|
|
168
|
+
'package_id': package.metadata['package_id'], 'detalhe': 'G3 e readback confirmados'}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def query_contract(request):
|
|
172
|
+
"""Somente fronteiras de leitura. Nao cria requester, ACL ou filtro extra."""
|
|
173
|
+
import re
|
|
174
|
+
def text(value, maximum):
|
|
175
|
+
return (isinstance(value, str) and 0 < len(value) <= maximum
|
|
176
|
+
and re.search(r'[\x00-\x1f\x7f]', value) is None)
|
|
177
|
+
valid = isinstance(request, dict) and set(request) == {'op', 'collection', 'tags', 'limit'}
|
|
178
|
+
tags = request.get('tags') if valid else None
|
|
179
|
+
limit = request.get('limit') if valid else None
|
|
180
|
+
if (not valid or request['op'] != 'query'
|
|
181
|
+
or request['collection'] not in ('decision', 'handoff', 'rule', 'learning', 'roadmap')
|
|
182
|
+
or type(limit) is not int or not 1 <= limit <= 1000
|
|
183
|
+
or not isinstance(tags, dict) or set(tags) != {'project', 'situation'}
|
|
184
|
+
or any(not isinstance(v, list) or len(v) != 1 for v in tags.values())
|
|
185
|
+
or not text(tags['project'][0], 128) or not text(tags['situation'][0], 135)
|
|
186
|
+
or re.fullmatch(r'thread:[a-z0-9][a-z0-9_-]{0,127}', tags['situation'][0]) is None):
|
|
187
|
+
raise QueryError('memory.query.invalid')
|
|
188
|
+
return {k: list(v) for k, v in tags.items()}, request['collection'], limit
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
async def execute(request, store):
|
|
192
|
+
from orkmind.core.models import MemoryEntry
|
|
193
|
+
from orkmind.core.semantic_layer import SemanticLayer
|
|
194
|
+
from orkmind.guardrails.handoff import submeter_handoff
|
|
195
|
+
layer = SemanticLayer(store, embedder=None, semantic_enabled=False)
|
|
196
|
+
op = request['op']
|
|
197
|
+
if op == 'stats':
|
|
198
|
+
return {c: await store.count(c) for c in await store.list_collections()}
|
|
199
|
+
if op == 'query':
|
|
200
|
+
tags, collection, limit = query_contract(request)
|
|
201
|
+
# GovernedStore conserva validade, anti-injection e visibilidade existentes.
|
|
202
|
+
# O transporte desta ponte usa exclusivamente pgvector; nenhum acesso inner.
|
|
203
|
+
entries = await store.search_by_tags(tags, collection=collection, limit=limit)
|
|
204
|
+
if len(entries) >= limit:
|
|
205
|
+
raise QueryError('memory.query.window-saturated')
|
|
206
|
+
if any(e.collection != collection or any(
|
|
207
|
+
not set(values) <= set(e.tags.get(k, [])) for k, values in tags.items())
|
|
208
|
+
for e in entries):
|
|
209
|
+
raise QueryError('memory.query.scope-violation')
|
|
210
|
+
return [e.model_dump(mode='json') for e in entries]
|
|
211
|
+
if op == 'export':
|
|
212
|
+
collection = request['collection']
|
|
213
|
+
count = await store.count(collection)
|
|
214
|
+
entries = await store.search_by_tags({}, collection=collection, limit=count + 1)
|
|
215
|
+
return [e.model_dump(mode='json') for e in entries]
|
|
216
|
+
if op == 'get':
|
|
217
|
+
entry = await store.retrieve(request['id'])
|
|
218
|
+
if entry and entry.collection != request['collection']:
|
|
219
|
+
raise ValueError('collection mismatch')
|
|
220
|
+
return entry.model_dump(mode='json') if entry else None
|
|
221
|
+
if op == 'add':
|
|
222
|
+
data = request['entrada']
|
|
223
|
+
governance(data)
|
|
224
|
+
if set(data['metadata']) & (FACTORY_KEYS | {'orkastery_prospective'}):
|
|
225
|
+
raise ValueError('reserved factory metadata')
|
|
226
|
+
evolving = data['collection'] == 'decision' and data.get('source', 'agent') == 'agent'
|
|
227
|
+
identity_data = data
|
|
228
|
+
if evolving:
|
|
229
|
+
if len(data['tags'].get('project', [])) != 1 or not data['tags']['project'][0]:
|
|
230
|
+
raise ValueError('decision tenant')
|
|
231
|
+
identity_data = {k: data[k] for k in ('collection', 'content', 'priority', 'metadata')}
|
|
232
|
+
identity_data.update(source='agent', tags=fixed_tags(data['tags']))
|
|
233
|
+
identity = hashlib.sha256(encoded(identity_data).encode()).hexdigest()
|
|
234
|
+
metadata = {**data['metadata'], 'orkastery_identity': identity}
|
|
235
|
+
if evolving:
|
|
236
|
+
metadata['orkastery_identity_version'] = 2
|
|
237
|
+
entry = MemoryEntry(content=data['content'], collection=data['collection'], tags=data['tags'],
|
|
238
|
+
priority=data['priority'], source=data.get('source', 'agent'),
|
|
239
|
+
scope='project', mandatory=False, visibility='public',
|
|
240
|
+
metadata=metadata)
|
|
241
|
+
# A native content collision is accepted only when provenance and source agree.
|
|
242
|
+
from orkmind.core.injection import compute_content_hash
|
|
243
|
+
old = await store.find_by_content_hash(compute_content_hash(entry.content), entry.collection)
|
|
244
|
+
if old:
|
|
245
|
+
if 'orkastery_prospective' in old.metadata:
|
|
246
|
+
if not prospective_compatible(old, entry):
|
|
247
|
+
raise ProspectiveMarkerInvalid('prospective receipt mismatch')
|
|
248
|
+
return {'ok': True, 'id': old.id, 'duplicada': True, 'collection': old.collection,
|
|
249
|
+
'detalhe': 'duplicate; historical-origin-unknown'}
|
|
250
|
+
if not old.metadata.get('orkastery_identity'):
|
|
251
|
+
raise LegacyProvenanceCollision('unproven legacy identity')
|
|
252
|
+
if (old.content != entry.content or old.source != entry.source or old.priority != entry.priority
|
|
253
|
+
or old.mandatory or old.scope != entry.scope or old.visibility != entry.visibility):
|
|
254
|
+
raise ValueError('existing identity mismatch')
|
|
255
|
+
if evolving:
|
|
256
|
+
provenance = {k: v for k, v in old.metadata.items() if k not in FACTORY_KEYS}
|
|
257
|
+
recognized = ((old.metadata.get('orkastery_identity_version') == 2
|
|
258
|
+
and old.metadata.get('orkastery_identity') == identity)
|
|
259
|
+
or legacy_factory_identity(old))
|
|
260
|
+
if not recognized or provenance != data['metadata'] or fixed_tags(old.tags) != fixed_tags(entry.tags):
|
|
261
|
+
raise ValueError('existing decision provenance mismatch')
|
|
262
|
+
tags = {k: sorted(set(old.tags.get(k, [])) | set(entry.tags.get(k, [])))
|
|
263
|
+
for k in set(old.tags) | set(entry.tags)}
|
|
264
|
+
if old.tags != tags or old.metadata != metadata:
|
|
265
|
+
updated = old.model_copy(update={'tags': tags, 'metadata': metadata})
|
|
266
|
+
if not await store.update(old.id, updated):
|
|
267
|
+
raise ValueError('decision update rejected')
|
|
268
|
+
saved = await store.retrieve(old.id)
|
|
269
|
+
if (not saved or saved.tags != tags or saved.metadata != metadata or saved.content != old.content
|
|
270
|
+
or saved.source != old.source or saved.priority != old.priority or saved.mandatory):
|
|
271
|
+
raise ValueError('decision readback')
|
|
272
|
+
elif old.metadata != entry.metadata or old.tags != entry.tags:
|
|
273
|
+
raise ValueError('existing identity mismatch')
|
|
274
|
+
return {'ok': True, 'id': old.id, 'duplicada': True, 'collection': old.collection, 'detalhe': 'duplicate'}
|
|
275
|
+
entry_id, _ = await layer.add_memory(entry)
|
|
276
|
+
saved = await store.retrieve(entry_id)
|
|
277
|
+
if not saved or saved.metadata != entry.metadata or saved.source != entry.source:
|
|
278
|
+
raise ValueError('add readback')
|
|
279
|
+
return {'ok': True, 'id': entry_id, 'duplicada': False, 'collection': entry.collection, 'detalhe': 'created'}
|
|
280
|
+
if op != 'handoff':
|
|
281
|
+
raise ValueError('operation')
|
|
282
|
+
p = request['pedido']
|
|
283
|
+
if set(p['metadata']) & {'package_id', 'session_id', 'origin', 'destination', 'package_entry_id', 'handoff_entry_id', 'orkastery_identity'}:
|
|
284
|
+
raise ValueError('reserved G3 metadata')
|
|
285
|
+
if p['tags'].get('project') != [p['tenant']] or not p['identidade'] or not p['sessionId']:
|
|
286
|
+
raise ValueError('tenant/identity')
|
|
287
|
+
# Native lookup includes retained packages hidden from context search. The payload
|
|
288
|
+
# carries the stable factory identity, so two distinct origins cannot collide.
|
|
289
|
+
from orkmind.core.injection import compute_content_hash
|
|
290
|
+
if p['payload'].get('orkastery_identity') != p['identidade']:
|
|
291
|
+
raise ValueError('payload identity')
|
|
292
|
+
package_hash = compute_content_hash(json.dumps(p['payload'], ensure_ascii=False, indent=2, default=str))
|
|
293
|
+
existing_package = await store.find_by_content_hash(package_hash, 'semantic_log')
|
|
294
|
+
if existing_package:
|
|
295
|
+
existing = await store.retrieve(existing_package.metadata['handoff_entry_id'])
|
|
296
|
+
return {**await readback(store, p, existing), 'duplicada': True}
|
|
297
|
+
result = await submeter_handoff(layer, session_id=p['sessionId'], payload=p['payload'],
|
|
298
|
+
origin=p['origem'], destination=p['destino'], refacao=0)
|
|
299
|
+
if result['status'] != 'armazenado':
|
|
300
|
+
raise ValueError('G3 rejected')
|
|
301
|
+
# G3 owns validation and persistence. Only enrich entries it has just created.
|
|
302
|
+
session = await store.retrieve(result['session_entry_id'])
|
|
303
|
+
session.tags = {**session.tags, **{k: sorted(set(session.tags.get(k, [])) | set(v)) for k, v in p['tags'].items()}}
|
|
304
|
+
session.metadata = {**session.metadata, 'tenant': p['tenant']}
|
|
305
|
+
if not await store.update(session.id, session):
|
|
306
|
+
raise ValueError('session enrichment')
|
|
307
|
+
summary = await store.retrieve(result['handoff_id'])
|
|
308
|
+
package = await store.retrieve(result['package_entry_id'])
|
|
309
|
+
for entry in (summary, package):
|
|
310
|
+
entry.tags = {**entry.tags, **p['tags']}
|
|
311
|
+
entry.metadata = {**entry.metadata, **p['metadata'], 'orkastery_identity': p['identidade'],
|
|
312
|
+
'package_entry_id': package.id, 'handoff_entry_id': summary.id}
|
|
313
|
+
entry.scope = 'project'
|
|
314
|
+
if not await store.update(entry.id, entry):
|
|
315
|
+
raise ValueError('G3 enrichment')
|
|
316
|
+
summary = await store.retrieve(summary.id)
|
|
317
|
+
return {**await readback(store, p, summary), 'duplicada': False}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
async def main(request):
|
|
321
|
+
if isinstance(request, dict) and request.get('op') == 'brain':
|
|
322
|
+
if set(request) != {'op','request'}:
|
|
323
|
+
return {'schema':'orkmind.company-brain-api/v1','state':'conflict','error':'brain.api.invalid'}
|
|
324
|
+
from orkmind.cli.company_brain import service_request
|
|
325
|
+
return service_request(request['request'])
|
|
326
|
+
if isinstance(request, dict) and request.get('op') == 'query':
|
|
327
|
+
query_contract(request)
|
|
328
|
+
native_schema_fields()
|
|
329
|
+
from orkmind.core.config import OrkMindConfig
|
|
330
|
+
from orkmind.store.factory import create_store
|
|
331
|
+
dsn = os.environ.get('ORKMIND_DATABASE_URL', '')
|
|
332
|
+
if not dsn.strip():
|
|
333
|
+
raise ValueError('missing tenant environment')
|
|
334
|
+
store = create_store(OrkMindConfig(database_url=dsn, store_backend='pgvector', embedding_provider=''))
|
|
335
|
+
try:
|
|
336
|
+
if request['op'] in ('add', 'handoff'):
|
|
337
|
+
conn = await store.inner._get_conn()
|
|
338
|
+
async with conn.transaction():
|
|
339
|
+
await conn.execute('SELECT pg_advisory_xact_lock(1869769581)')
|
|
340
|
+
return await execute(request, store)
|
|
341
|
+
return await execute(request, store)
|
|
342
|
+
finally:
|
|
343
|
+
await store.close()
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
if __name__ == '__main__':
|
|
347
|
+
logging.disable(logging.CRITICAL)
|
|
348
|
+
try:
|
|
349
|
+
request = json.load(sys.stdin)
|
|
350
|
+
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
|
|
351
|
+
result = asyncio.run(main(request))
|
|
352
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
353
|
+
except Exception as error:
|
|
354
|
+
# The factory requires an initialized tenant schema; health never provisions it.
|
|
355
|
+
code = error_code(error)
|
|
356
|
+
print(json.dumps({'error': code}))
|
|
357
|
+
sys.exit(1)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Contrato de fixture externa. Apenas socket sintetico atestado; nunca DSN do ambiente."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import re
|
|
6
|
+
import stat
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def receipt_file(filename):
|
|
11
|
+
try:
|
|
12
|
+
path = Path(filename)
|
|
13
|
+
root = path.parent
|
|
14
|
+
if (not path.is_absolute() or not re.fullmatch(r'receipt(?:-[a-f0-9]+)?\.json', path.name)
|
|
15
|
+
or not re.fullmatch(r'/tmp/ork-prospective-fixture-[a-zA-Z0-9-]+', str(root))
|
|
16
|
+
or root.resolve() != root or root.is_symlink()):
|
|
17
|
+
raise ValueError()
|
|
18
|
+
st = root.stat()
|
|
19
|
+
if st.st_uid != os.getuid() or stat.S_IMODE(st.st_mode) != 0o700:
|
|
20
|
+
raise ValueError()
|
|
21
|
+
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
|
|
22
|
+
try:
|
|
23
|
+
st = os.fstat(fd)
|
|
24
|
+
if not stat.S_ISREG(st.st_mode) or st.st_uid != os.getuid() or stat.S_IMODE(st.st_mode) != 0o600:
|
|
25
|
+
raise ValueError()
|
|
26
|
+
with os.fdopen(fd, closefd=False) as f:
|
|
27
|
+
receipt = json.load(f)
|
|
28
|
+
finally:
|
|
29
|
+
os.close(fd)
|
|
30
|
+
if (receipt['schema'] != 'ork.native-fixture/v1' or receipt['synthetic'] is not True
|
|
31
|
+
or receipt['ownerUid'] != os.getuid() or not receipt['preparedBy'].strip()
|
|
32
|
+
or receipt['socket'] != str(root) or receipt['database'] != 'ork_i06_txn_fixture'
|
|
33
|
+
or receipt['tenant'] != 'synthetic' or receipt['expiresAt'] <= time.time()
|
|
34
|
+
or not re.fullmatch('[a-f0-9]{64}', receipt['container'])
|
|
35
|
+
or not re.fullmatch('sha256:[a-f0-9]{64}', receipt['image'])
|
|
36
|
+
or not re.fullmatch('[a-f0-9]{64}', receipt['identity'])):
|
|
37
|
+
raise ValueError()
|
|
38
|
+
socket = root / '.s.PGSQL.5432'
|
|
39
|
+
st = socket.lstat()
|
|
40
|
+
if not stat.S_ISSOCK(st.st_mode) or st.st_uid != os.getuid():
|
|
41
|
+
raise ValueError()
|
|
42
|
+
return receipt
|
|
43
|
+
except (OSError, ValueError, KeyError, TypeError):
|
|
44
|
+
raise PermissionError('runtime.fixture.invalid') from None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def fixture_dsn(receipt):
|
|
48
|
+
# Values are validated, and credentials/operational connection strings are not inputs.
|
|
49
|
+
from psycopg.conninfo import make_conninfo
|
|
50
|
+
return make_conninfo(dbname=receipt['database'], user='postgres', host=receipt['socket'])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def connect_fixture(filename):
|
|
54
|
+
import psycopg
|
|
55
|
+
receipt = receipt_file(filename)
|
|
56
|
+
try:
|
|
57
|
+
conn = await psycopg.AsyncConnection.connect(fixture_dsn(receipt), autocommit=True)
|
|
58
|
+
except psycopg.OperationalError:
|
|
59
|
+
raise RuntimeError('runtime.unavailable: external fixture connection') from None
|
|
60
|
+
try:
|
|
61
|
+
row = await (await conn.execute('SELECT identity, tenant FROM public.ork_fixture_identity')).fetchone()
|
|
62
|
+
if row != (receipt['identity'], receipt['tenant']):
|
|
63
|
+
raise PermissionError('runtime.fixture.identity-mismatch')
|
|
64
|
+
except Exception:
|
|
65
|
+
await conn.close()
|
|
66
|
+
raise PermissionError('runtime.fixture.identity-mismatch') from None
|
|
67
|
+
return conn, receipt
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Ensaio transacional de preparacao. Sem CLI/apply e restrito a PostgreSQL sintetico.
|
|
2
|
+
|
|
3
|
+
GovernedStore e responsavel por toda escrita; SQL abaixo serve somente aos locks.
|
|
4
|
+
O caminho operacional depende de autoridade nativa para orfas e decisao do condutor.
|
|
5
|
+
"""
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import uuid
|
|
9
|
+
import re
|
|
10
|
+
from orkmind_fixture import receipt_file
|
|
11
|
+
from orkmind_bridge import prospective_agent_valid, native_schema_fields
|
|
12
|
+
from orkmind.core.models import MemoryEntry
|
|
13
|
+
from orkmind.store.governance import assert_nao_protegida, assert_pode_escrever
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def digest(value):
|
|
17
|
+
return hashlib.sha256(json.dumps(value, sort_keys=True, ensure_ascii=False, separators=(',', ':')).encode()).hexdigest()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def dump(entry):
|
|
21
|
+
return entry.model_dump(mode='json')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def same_payload(a, b):
|
|
25
|
+
return {k: v for k, v in a.items() if k not in ('version', 'updated_at')} == {
|
|
26
|
+
k: v for k, v in b.items() if k not in ('version', 'updated_at')}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def rehearse(native_store, plan, requester, *, fixture_receipt=None, rollback_receipt=None, fail_after=None, locked_probe=None):
|
|
30
|
+
"""Lote e compensacao EXATOS, executaveis somente em fixture; requester sempre nomeado."""
|
|
31
|
+
if not isinstance(requester, str) or not requester.strip():
|
|
32
|
+
raise PermissionError('memory.prospective.requester-required')
|
|
33
|
+
if not isinstance(fixture_receipt, str):
|
|
34
|
+
raise PermissionError('runtime.fixture.receipt-required')
|
|
35
|
+
fixture = receipt_file(fixture_receipt)
|
|
36
|
+
native_schema_fields()
|
|
37
|
+
conn = await native_store.inner._get_conn()
|
|
38
|
+
if conn.info.dbname != fixture['database'] or conn.info.host != fixture['socket']:
|
|
39
|
+
raise PermissionError('memory.prospective.operational-unavailable')
|
|
40
|
+
identity = await (await conn.execute('SELECT identity, tenant FROM public.ork_fixture_identity')).fetchone()
|
|
41
|
+
if identity != {'identity': fixture['identity'], 'tenant': fixture['tenant']}:
|
|
42
|
+
raise PermissionError('runtime.fixture.identity-mismatch')
|
|
43
|
+
items = plan['entradas']
|
|
44
|
+
ids = sorted(item['id'] for item in items)
|
|
45
|
+
if len(ids) != 3 or len(set(ids)) != 3:
|
|
46
|
+
raise ValueError('memory.prospective.scope')
|
|
47
|
+
items = sorted(items, key=lambda e: e['id'])
|
|
48
|
+
phase = 'rollback' if rollback_receipt else 'prepare-rehearsal'
|
|
49
|
+
operation_hash = digest({'plan': plan, 'requester': requester, 'rollback': rollback_receipt})
|
|
50
|
+
receipt_id = str(uuid.uuid5(uuid.NAMESPACE_URL, operation_hash + ':' + phase))
|
|
51
|
+
async with conn.transaction():
|
|
52
|
+
# ORDER BY precede o lock; todos os tres IDs sao bloqueados antes de retrieve/update.
|
|
53
|
+
rows = await (await conn.execute('SELECT id FROM memories WHERE id=ANY(%s) ORDER BY id FOR UPDATE', (ids,))).fetchall()
|
|
54
|
+
if [r['id'] for r in rows] != ids:
|
|
55
|
+
raise ValueError('memory.prospective.scope-changed')
|
|
56
|
+
if locked_probe:
|
|
57
|
+
await locked_probe(ids)
|
|
58
|
+
current = [await native_store.retrieve(i) for i in ids]
|
|
59
|
+
identities = await native_store._identidades(requester)
|
|
60
|
+
for entry in current:
|
|
61
|
+
assert_nao_protegida(entry, 'agent', 'update')
|
|
62
|
+
assert_pode_escrever(entry, requester, identities)
|
|
63
|
+
if (entry.collection != 'rule' or entry.source != 'agent' or entry.priority != 'high'
|
|
64
|
+
or entry.mandatory or entry.visibility != 'private'):
|
|
65
|
+
raise PermissionError('memory.prospective.governance')
|
|
66
|
+
expected = [item['antes'] for item in items]
|
|
67
|
+
desired = [item['proposta'] for item in items]
|
|
68
|
+
if rollback_receipt:
|
|
69
|
+
applied = await native_store.retrieve(rollback_receipt)
|
|
70
|
+
if not applied:
|
|
71
|
+
raise ValueError('memory.prospective.receipt-missing')
|
|
72
|
+
previous = json.loads(applied.content)
|
|
73
|
+
if (previous.get('schema') != 'ork.prospective-transaction/v1'
|
|
74
|
+
or previous.get('phase') != 'prepare-rehearsal' or previous.get('rollbackOf') is not None
|
|
75
|
+
or previous.get('planHash') != digest(plan) or previous.get('requester') != requester):
|
|
76
|
+
raise ValueError('memory.prospective.receipt-conflict')
|
|
77
|
+
expected, desired = previous['after'], previous['before']
|
|
78
|
+
receipt = await native_store.retrieve(receipt_id)
|
|
79
|
+
if receipt:
|
|
80
|
+
previous = json.loads(receipt.content)
|
|
81
|
+
if (previous['operationHash'] != operation_hash or previous['requester'] != requester
|
|
82
|
+
or previous['after'] != [dump(e) for e in current]):
|
|
83
|
+
raise ValueError('memory.prospective.replay-conflict')
|
|
84
|
+
return {'receiptId': receipt_id, 'replay': True, 'updated': 0}
|
|
85
|
+
if [dump(e) for e in current] != expected:
|
|
86
|
+
raise ValueError('memory.prospective.snapshot-conflict')
|
|
87
|
+
# Validacao de TODO o lote antes da primeira escrita; preservar todas as colunas.
|
|
88
|
+
targets = []
|
|
89
|
+
for old, target in zip(current, desired):
|
|
90
|
+
if set(target) != set(dump(old)):
|
|
91
|
+
raise ValueError('memory.prospective.snapshot-incomplete')
|
|
92
|
+
changed = {k for k in target if target[k] != dump(old)[k]}
|
|
93
|
+
permitted = {'tags', 'metadata'} | ({'version', 'updated_at'} if rollback_receipt else set())
|
|
94
|
+
if changed - permitted:
|
|
95
|
+
raise ValueError('memory.prospective.field-change')
|
|
96
|
+
if target['tags'].get('editors', []) != old.tags.get('editors', []):
|
|
97
|
+
raise PermissionError('memory.prospective.acl-change')
|
|
98
|
+
if not rollback_receipt:
|
|
99
|
+
try:
|
|
100
|
+
marker = target['metadata']['orkastery_prospective']
|
|
101
|
+
original = marker['originalJson']
|
|
102
|
+
if (set(marker) != {'schema', 'historicalOrigin', 'source', 'entryId', 'agent', 'revision',
|
|
103
|
+
'receiptSha256', 'originalJson', 'originalSha256'}
|
|
104
|
+
or marker['schema'] != 'ork.prospective-origin/v1'
|
|
105
|
+
or marker['historicalOrigin'] != 'unknown' or marker['source'] != 'agent'
|
|
106
|
+
or not prospective_agent_valid(marker['agent'])
|
|
107
|
+
or not re.fullmatch('[a-f0-9]{40}', marker['revision'])
|
|
108
|
+
or not re.fullmatch('[a-f0-9]{64}', marker['receiptSha256'])
|
|
109
|
+
or marker['entryId'] != old.id or json.loads(original) != dump(old)
|
|
110
|
+
or hashlib.sha256(original.encode()).hexdigest() != marker['originalSha256']):
|
|
111
|
+
raise ValueError()
|
|
112
|
+
except (KeyError, ValueError, TypeError, AttributeError):
|
|
113
|
+
raise ValueError('memory.prospective.marker-invalid') from None
|
|
114
|
+
targets.append(MemoryEntry.model_validate({**target, 'version': old.version, 'updated_at': dump(old)['updated_at']}))
|
|
115
|
+
for n, (old, target) in enumerate(zip(current, targets), 1):
|
|
116
|
+
if await native_store.inner._get_conn() is not conn:
|
|
117
|
+
raise RuntimeError('memory.prospective.connection-changed')
|
|
118
|
+
if not await native_store.update(old.id, target, requester_id=requester):
|
|
119
|
+
raise ValueError('memory.prospective.update-refused')
|
|
120
|
+
saved = await native_store.retrieve(old.id)
|
|
121
|
+
if saved.version != old.version + 1 or not same_payload(dump(saved), dump(target)):
|
|
122
|
+
raise ValueError('memory.prospective.readback')
|
|
123
|
+
if fail_after == n:
|
|
124
|
+
raise RuntimeError('fixture.injected-failure')
|
|
125
|
+
after = [dump(await native_store.retrieve(i)) for i in ids]
|
|
126
|
+
body = {'schema': 'ork.prospective-transaction/v1', 'phase': phase, 'operationHash': operation_hash,
|
|
127
|
+
'planHash': digest(plan), 'requester': requester, 'before': expected, 'after': after,
|
|
128
|
+
'rollbackOf': rollback_receipt, 'historicalOrigin': 'unknown'}
|
|
129
|
+
# Sidecar nativo integral, privado e duravel na MESMA transacao; nao memory_versions parcial.
|
|
130
|
+
receipt = MemoryEntry(id=receipt_id, collection='semantic_log', content=json.dumps(body, ensure_ascii=False),
|
|
131
|
+
source='agent', priority='high', mandatory=False, visibility='private',
|
|
132
|
+
scope='project', author_id=requester, tags={'project': current[0].tags['project']},
|
|
133
|
+
metadata={'operationHash': operation_hash, 'historicalOrigin': 'unknown'})
|
|
134
|
+
if await native_store.store(receipt) != receipt_id:
|
|
135
|
+
raise ValueError('memory.prospective.receipt-write')
|
|
136
|
+
if json.loads((await native_store.retrieve(receipt_id)).content) != body:
|
|
137
|
+
raise ValueError('memory.prospective.receipt-readback')
|
|
138
|
+
return {'receiptId': receipt_id, 'replay': False, 'updated': 3}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "ork.reference-tariffs/v1",
|
|
3
|
+
"verifiedAt": "2026-09-08T00:00:00.000Z",
|
|
4
|
+
"currency": "USD",
|
|
5
|
+
"unitTokens": 1000000,
|
|
6
|
+
"nature": "estimated_reference",
|
|
7
|
+
"models": {
|
|
8
|
+
"gpt-6-astra": {
|
|
9
|
+
"product": "Codex Enterprise reference",
|
|
10
|
+
"input": 10,
|
|
11
|
+
"cachedInput": 1,
|
|
12
|
+
"output": 50,
|
|
13
|
+
"source": "https://openai.com/business/codex-pricing/"
|
|
14
|
+
},
|
|
15
|
+
"claude-opus-5": {
|
|
16
|
+
"product": "Claude API reference",
|
|
17
|
+
"input": 5,
|
|
18
|
+
"cachedInput": 0.5,
|
|
19
|
+
"output": 25,
|
|
20
|
+
"source": "https://docs.anthropic.com/en/docs/about-claude/pricing"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|