microcoreos 0.1.0__py3-none-any.whl
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.
- microcoreos/__init__.py +23 -0
- microcoreos/_template/.agent/skills/microcoreos-architecture/SKILL.md +24 -0
- microcoreos/_template/.agent/skills/microcoreos-architecture/agent.md +18 -0
- microcoreos/_template/.agent/workflows/feature-plan.md +111 -0
- microcoreos/_template/.agent/workflows/multi-domain-plan.md +96 -0
- microcoreos/_template/.agent/workflows/new-domain.md +201 -0
- microcoreos/_template/.agent/workflows/new-tool.md +72 -0
- microcoreos/_template/.dockerignore +23 -0
- microcoreos/_template/.env.example +96 -0
- microcoreos/_template/.gitignore +57 -0
- microcoreos/_template/AGENTS.md +175 -0
- microcoreos/_template/Dockerfile +35 -0
- microcoreos/_template/INSTRUCTIONS_FOR_AI.md +560 -0
- microcoreos/_template/dev_infra/cache_probe.py +176 -0
- microcoreos/_template/dev_infra/docker-compose.yml +137 -0
- microcoreos/_template/docs/CLI.md +187 -0
- microcoreos/_template/docs/CORE_INFRASTRUCTURE.md +372 -0
- microcoreos/_template/docs/ELASTIC_DEPLOYMENT.md +277 -0
- microcoreos/_template/docs/EVENT_BUS.md +288 -0
- microcoreos/_template/docs/HTTP_SERVER.md +98 -0
- microcoreos/_template/docs/INDEX.md +41 -0
- microcoreos/_template/docs/OBSERVABILITY.md +56 -0
- microcoreos/_template/docs/OBSERVABILITY_API.md +183 -0
- microcoreos/_template/docs/PARALLEL_DEVELOPMENT.md +494 -0
- microcoreos/_template/docs/PLAN_EVENT_LINTER.md +95 -0
- microcoreos/_template/docs/TECH_DEBT.md +388 -0
- microcoreos/_template/docs/translations/es/README.md +265 -0
- microcoreos/_template/domains/devtools/lint/plugin_sources.py +51 -0
- microcoreos/_template/domains/devtools/plugins/discovery_naming_linter_plugin.py +89 -0
- microcoreos/_template/domains/devtools/plugins/domain_isolation_linter_plugin.py +80 -0
- microcoreos/_template/domains/devtools/plugins/event_contract_linter_plugin.py +420 -0
- microcoreos/_template/domains/devtools/plugins/event_schemas_plugin.py +94 -0
- microcoreos/_template/domains/devtools/plugins/field_divergence_linter_plugin.py +178 -0
- microcoreos/_template/domains/devtools/plugins/plan_validator_plugin.py +581 -0
- microcoreos/_template/domains/devtools/plugins/route_collision_linter_plugin.py +51 -0
- microcoreos/_template/domains/devtools/plugins/table_ownership_linter_plugin.py +66 -0
- microcoreos/_template/domains/devtools/plugins/tool_doc_drift_linter_plugin.py +74 -0
- microcoreos/_template/domains/system/plugins/event_delivery_monitor_plugin.py +53 -0
- microcoreos/_template/domains/system/plugins/system_events_plugin.py +132 -0
- microcoreos/_template/domains/system/plugins/system_events_stream_plugin.py +46 -0
- microcoreos/_template/domains/system/plugins/system_logs_stream_plugin.py +47 -0
- microcoreos/_template/domains/system/plugins/system_metrics_plugin.py +85 -0
- microcoreos/_template/domains/system/plugins/system_status_plugin.py +66 -0
- microcoreos/_template/domains/system/plugins/system_traces_plugin.py +128 -0
- microcoreos/_template/domains/system/plugins/system_traces_stream_plugin.py +121 -0
- microcoreos/_template/domains/system/plugins/tool_health_plugin.py +53 -0
- microcoreos/_template/extras/available_domains/chaos/plugins/blocking_boot_plugin.py +19 -0
- microcoreos/_template/extras/available_domains/chaos/plugins/chaos_control_plugin.py +625 -0
- microcoreos/_template/extras/available_domains/chaos/plugins/failing_plugin.py +27 -0
- microcoreos/_template/extras/available_domains/chaos/plugins/stress_plugin.py +40 -0
- microcoreos/_template/extras/available_domains/ping/plugins/ping_plugin.py +36 -0
- microcoreos/_template/extras/available_domains/scheduler/migrations/001_scheduler_one_shots.sql +8 -0
- microcoreos/_template/extras/available_domains/scheduler/models/scheduler_one_shot.py +15 -0
- microcoreos/_template/extras/available_domains/scheduler/plugins/durable_one_shots_plugin.py +125 -0
- microcoreos/_template/extras/available_domains/users/migrations/001_create_users.sql +12 -0
- microcoreos/_template/extras/available_domains/users/models/user.py +31 -0
- microcoreos/_template/extras/available_domains/users/plugins/create_user_plugin.py +89 -0
- microcoreos/_template/extras/available_domains/users/plugins/get_me_plugin.py +63 -0
- microcoreos/_template/extras/available_domains/users/plugins/login_plugin.py +107 -0
- microcoreos/_template/extras/available_domains/users/plugins/logout_plugin.py +26 -0
- microcoreos/_template/extras/available_tools/auth/auth_tool.py +132 -0
- microcoreos/_template/extras/available_tools/chaos/chaos_tool.py +32 -0
- microcoreos/_template/extras/available_tools/kafka/kafka_driver.py +452 -0
- microcoreos/_template/extras/available_tools/postgresql/postgresql_tool.py +882 -0
- microcoreos/_template/extras/available_tools/rabbitmq/rabbitmq_driver.py +303 -0
- microcoreos/_template/extras/available_tools/redis_state/redis_state_tool.py +253 -0
- microcoreos/_template/extras/available_tools/s3/__init__.py +0 -0
- microcoreos/_template/extras/available_tools/s3/s3_tool.py +426 -0
- microcoreos/_template/extras/available_tools/scheduler/scheduler_tool.py +328 -0
- microcoreos/_template/main.py +9 -0
- microcoreos/_template/plans/PILOT.md +99 -0
- microcoreos/_template/plans/README.md +236 -0
- microcoreos/_template/plans/active_plan.md +25 -0
- microcoreos/_template/plans/active_plan.yaml +43 -0
- microcoreos/_template/tools/config/config_tool.py +110 -0
- microcoreos/_template/tools/context/authoring_guide.md +305 -0
- microcoreos/_template/tools/context/context_tool.py +161 -0
- microcoreos/_template/tools/context/renderers.py +118 -0
- microcoreos/_template/tools/context/scanners.py +267 -0
- microcoreos/_template/tools/event_bus/drivers.py +108 -0
- microcoreos/_template/tools/event_bus/envelope.py +55 -0
- microcoreos/_template/tools/event_bus/event_bus_tool.py +506 -0
- microcoreos/_template/tools/event_bus/redis_streams_driver.py +300 -0
- microcoreos/_template/tools/event_bus/sqlite_driver.py +316 -0
- microcoreos/_template/tools/http_server/context.py +120 -0
- microcoreos/_template/tools/http_server/http_server_tool.py +558 -0
- microcoreos/_template/tools/http_server/pipeline.py +267 -0
- microcoreos/_template/tools/logger/logger_tool.py +96 -0
- microcoreos/_template/tools/sqlite/errors.py +152 -0
- microcoreos/_template/tools/sqlite/migrations.py +140 -0
- microcoreos/_template/tools/sqlite/sqlite_tool.py +591 -0
- microcoreos/_template/tools/sqlite/transaction.py +189 -0
- microcoreos/_template/tools/state/state_tool.py +173 -0
- microcoreos/_template/tools/system/registry_tool.py +90 -0
- microcoreos/_template/tools/telemetry/__init__.py +0 -0
- microcoreos/_template/tools/telemetry/telemetry_tool.py +143 -0
- microcoreos/base_plugin.py +20 -0
- microcoreos/base_tool.py +48 -0
- microcoreos/catalog.py +268 -0
- microcoreos/cli.py +206 -0
- microcoreos/container.py +222 -0
- microcoreos/context.py +6 -0
- microcoreos/kernel.py +223 -0
- microcoreos/project_readme.md +107 -0
- microcoreos/registry.py +53 -0
- microcoreos/scaffold.py +218 -0
- microcoreos/upgrade.py +547 -0
- microcoreos-0.1.0.dist-info/METADATA +366 -0
- microcoreos-0.1.0.dist-info/RECORD +112 -0
- microcoreos-0.1.0.dist-info/WHEEL +4 -0
- microcoreos-0.1.0.dist-info/entry_points.txt +2 -0
- microcoreos-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Cache probe — proves whether an inference engine actually reuses the shared
|
|
2
|
+
prompt prefix (the "canonical executor prompt" of docs/PARALLEL_DEVELOPMENT.md).
|
|
3
|
+
|
|
4
|
+
It sends three requests and compares how many prompt tokens the engine REALLY
|
|
5
|
+
processed (not how many you sent):
|
|
6
|
+
|
|
7
|
+
1. COLD shared prefix + task A -> baseline (everything processed)
|
|
8
|
+
2. WARM identical prefix + task B -> should process ~task tokens only
|
|
9
|
+
3. BROKEN prefix with 1 byte changed + B -> back to baseline (negative control)
|
|
10
|
+
|
|
11
|
+
PASS = warm processed-tokens << cold, and broken ~= cold.
|
|
12
|
+
|
|
13
|
+
The shared prefix is your real workload: AI_CONTEXT.md + plans/active_plan.yaml.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
python dev_infra/cache_probe.py --backend ollama --url http://localhost:11434 --model llama3.1
|
|
17
|
+
python dev_infra/cache_probe.py --backend llamacpp --url http://localhost:8080
|
|
18
|
+
python dev_infra/cache_probe.py --backend openai --url http://localhost:8000 --model my-model # vLLM etc.
|
|
19
|
+
python dev_infra/cache_probe.py --backend anthropic --model claude-haiku-4-5 # needs ANTHROPIC_API_KEY
|
|
20
|
+
|
|
21
|
+
No dependencies beyond the standard library.
|
|
22
|
+
"""
|
|
23
|
+
import argparse
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import time
|
|
27
|
+
import urllib.request
|
|
28
|
+
|
|
29
|
+
TASK_A = "Implement feature CreateThingPlugin from the plan above. Reply with only the file path you would create."
|
|
30
|
+
TASK_B = "Implement feature DeleteThingPlugin from the plan above. Reply with only the file path you would create."
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def read_shared_prefix() -> str:
|
|
34
|
+
parts = []
|
|
35
|
+
for path in ("AI_CONTEXT.md", "plans/active_plan.yaml"):
|
|
36
|
+
try:
|
|
37
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
38
|
+
parts.append(f"===== {path} =====\n{f.read()}")
|
|
39
|
+
except FileNotFoundError:
|
|
40
|
+
parts.append(f"===== {path} ===== (missing)")
|
|
41
|
+
parts.append("===== RULES =====\nOne file = one feature. Follow the plan exactly.")
|
|
42
|
+
return "\n\n".join(parts)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def post_json(url: str, body: dict, headers: dict) -> dict:
|
|
46
|
+
req = urllib.request.Request(
|
|
47
|
+
url,
|
|
48
|
+
data=json.dumps(body).encode("utf-8"),
|
|
49
|
+
headers={"Content-Type": "application/json", **headers},
|
|
50
|
+
method="POST",
|
|
51
|
+
)
|
|
52
|
+
with urllib.request.urlopen(req, timeout=600) as resp:
|
|
53
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ── Backends ────────────────────────────────────────────────────────────────
|
|
57
|
+
# Each returns {"processed": int, "cached": int|None, "wall_s": float}
|
|
58
|
+
# "processed" = prompt tokens the engine actually evaluated this request.
|
|
59
|
+
|
|
60
|
+
def probe_ollama(url, model, prefix, task):
|
|
61
|
+
t0 = time.monotonic()
|
|
62
|
+
r = post_json(f"{url}/api/chat", {
|
|
63
|
+
"model": model, "stream": False,
|
|
64
|
+
"messages": [{"role": "user", "content": prefix + "\n\n" + task}],
|
|
65
|
+
"options": {"num_predict": 32},
|
|
66
|
+
}, {})
|
|
67
|
+
return {"processed": r.get("prompt_eval_count", -1), "cached": None,
|
|
68
|
+
"wall_s": time.monotonic() - t0}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def probe_llamacpp(url, model, prefix, task):
|
|
72
|
+
t0 = time.monotonic()
|
|
73
|
+
r = post_json(f"{url}/completion", {
|
|
74
|
+
"prompt": prefix + "\n\n" + task,
|
|
75
|
+
"n_predict": 32, "cache_prompt": True,
|
|
76
|
+
}, {})
|
|
77
|
+
timings = r.get("timings", {})
|
|
78
|
+
return {"processed": timings.get("prompt_n", -1), "cached": None,
|
|
79
|
+
"wall_s": time.monotonic() - t0}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def probe_openai(url, model, prefix, task):
|
|
83
|
+
"""OpenAI-compatible servers (vLLM with prefix caching, OpenAI itself)."""
|
|
84
|
+
t0 = time.monotonic()
|
|
85
|
+
headers = {}
|
|
86
|
+
if os.getenv("OPENAI_API_KEY"):
|
|
87
|
+
headers["Authorization"] = f"Bearer {os.environ['OPENAI_API_KEY']}"
|
|
88
|
+
r = post_json(f"{url}/v1/chat/completions", {
|
|
89
|
+
"model": model, "max_tokens": 32,
|
|
90
|
+
"messages": [{"role": "user", "content": prefix + "\n\n" + task}],
|
|
91
|
+
}, headers)
|
|
92
|
+
usage = r.get("usage", {})
|
|
93
|
+
total = usage.get("prompt_tokens", -1)
|
|
94
|
+
cached = (usage.get("prompt_tokens_details") or {}).get("cached_tokens", 0)
|
|
95
|
+
return {"processed": total - cached, "cached": cached,
|
|
96
|
+
"wall_s": time.monotonic() - t0}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _anthropic_headers():
|
|
100
|
+
h = {"anthropic-version": "2023-06-01"}
|
|
101
|
+
if os.getenv("ANTHROPIC_API_KEY"):
|
|
102
|
+
h["x-api-key"] = os.environ["ANTHROPIC_API_KEY"]
|
|
103
|
+
elif os.getenv("ANTHROPIC_AUTH_TOKEN"): # OAuth token (e.g. from ant auth / Claude Code)
|
|
104
|
+
h["Authorization"] = f"Bearer {os.environ['ANTHROPIC_AUTH_TOKEN']}"
|
|
105
|
+
h["anthropic-beta"] = "oauth-2025-04-20"
|
|
106
|
+
else:
|
|
107
|
+
raise SystemExit("Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN")
|
|
108
|
+
return h
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def probe_anthropic(url, model, prefix, task):
|
|
112
|
+
t0 = time.monotonic()
|
|
113
|
+
r = post_json(f"{url}/v1/messages", {
|
|
114
|
+
"model": model, "max_tokens": 32,
|
|
115
|
+
"system": [{"type": "text", "text": prefix,
|
|
116
|
+
"cache_control": {"type": "ephemeral"}}],
|
|
117
|
+
"messages": [{"role": "user", "content": task}],
|
|
118
|
+
}, _anthropic_headers())
|
|
119
|
+
u = r.get("usage", {})
|
|
120
|
+
processed = u.get("input_tokens", -1) + u.get("cache_creation_input_tokens", 0)
|
|
121
|
+
return {"processed": processed, "cached": u.get("cache_read_input_tokens", 0),
|
|
122
|
+
"wall_s": time.monotonic() - t0}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
BACKENDS = {
|
|
126
|
+
"ollama": (probe_ollama, "http://localhost:11434"),
|
|
127
|
+
"llamacpp": (probe_llamacpp, "http://localhost:8080"),
|
|
128
|
+
"openai": (probe_openai, "http://localhost:8000"),
|
|
129
|
+
"anthropic": (probe_anthropic, "https://api.anthropic.com"),
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def main():
|
|
134
|
+
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
135
|
+
ap.add_argument("--backend", choices=BACKENDS, required=True)
|
|
136
|
+
ap.add_argument("--model", default="")
|
|
137
|
+
ap.add_argument("--url", default="")
|
|
138
|
+
args = ap.parse_args()
|
|
139
|
+
|
|
140
|
+
probe, default_url = BACKENDS[args.backend]
|
|
141
|
+
url = (args.url or default_url).rstrip("/")
|
|
142
|
+
prefix = read_shared_prefix()
|
|
143
|
+
# Negative control: one byte changed at position 0 breaks the whole prefix.
|
|
144
|
+
broken_prefix = "!" + prefix[1:]
|
|
145
|
+
|
|
146
|
+
runs = [
|
|
147
|
+
("COLD (prefix + task A)", prefix, TASK_A),
|
|
148
|
+
("WARM (same prefix + task B)", prefix, TASK_B),
|
|
149
|
+
("BROKEN (1 byte changed + task B)", broken_prefix, TASK_B),
|
|
150
|
+
]
|
|
151
|
+
results = []
|
|
152
|
+
print(f"backend={args.backend} model={args.model or '(default)'} url={url}")
|
|
153
|
+
print(f"shared prefix: {len(prefix)} chars (~{len(prefix) // 4} tokens)\n")
|
|
154
|
+
for name, pfx, task in runs:
|
|
155
|
+
res = probe(url, args.model, pfx, task)
|
|
156
|
+
results.append(res)
|
|
157
|
+
cached = f" cached={res['cached']}" if res["cached"] is not None else ""
|
|
158
|
+
print(f"{name:38} processed={res['processed']:>7}{cached} wall={res['wall_s']:.2f}s")
|
|
159
|
+
|
|
160
|
+
cold, warm, broken = (r["processed"] for r in results)
|
|
161
|
+
print()
|
|
162
|
+
if 0 <= warm < cold * 0.5 and broken > cold * 0.5:
|
|
163
|
+
print(f"PASS — WARM processed only {warm}/{cold} tokens (cache hit), "
|
|
164
|
+
f"and BROKEN reprocessed {broken} (prefix rule confirmed).")
|
|
165
|
+
elif 0 <= warm < cold * 0.5:
|
|
166
|
+
print(f"PARTIAL — WARM hit the cache ({warm}/{cold}), but BROKEN also "
|
|
167
|
+
f"avoided reprocessing ({broken}) — check the negative control.")
|
|
168
|
+
else:
|
|
169
|
+
print(f"FAIL — WARM reprocessed {warm}/{cold} tokens. No prefix reuse: "
|
|
170
|
+
f"check that prefix caching is enabled on the server, the prefix "
|
|
171
|
+
f"is byte-identical, and (hosted) the prefix meets the minimum "
|
|
172
|
+
f"cacheable size for the model.")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
if __name__ == "__main__":
|
|
176
|
+
main()
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
2
|
+
# MicroCoreOS — Development Infrastructure
|
|
3
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
4
|
+
#
|
|
5
|
+
# Uso:
|
|
6
|
+
# docker compose -f dev_infra/docker-compose.yml up -d
|
|
7
|
+
# docker compose -f dev_infra/docker-compose.yml down
|
|
8
|
+
#
|
|
9
|
+
# Los datos persisten en volúmenes Docker entre reinicios.
|
|
10
|
+
# Para borrar todo y empezar limpio:
|
|
11
|
+
# docker compose -f dev_infra/docker-compose.yml down -v
|
|
12
|
+
#
|
|
13
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
14
|
+
|
|
15
|
+
services:
|
|
16
|
+
|
|
17
|
+
# ─── PostgreSQL ───────────────────────────────────────────
|
|
18
|
+
postgres:
|
|
19
|
+
image: postgres:17-alpine
|
|
20
|
+
container_name: microcoreos_postgres
|
|
21
|
+
restart: unless-stopped
|
|
22
|
+
ports:
|
|
23
|
+
- "5432:5432"
|
|
24
|
+
environment:
|
|
25
|
+
POSTGRES_USER: postgres
|
|
26
|
+
POSTGRES_PASSWORD: postgres
|
|
27
|
+
POSTGRES_DB: microcoreos
|
|
28
|
+
volumes:
|
|
29
|
+
- postgres_data:/var/lib/postgresql/data
|
|
30
|
+
healthcheck:
|
|
31
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
32
|
+
interval: 5s
|
|
33
|
+
timeout: 3s
|
|
34
|
+
retries: 5
|
|
35
|
+
|
|
36
|
+
# ─── Redis ────────────────────────────────────────────────
|
|
37
|
+
redis:
|
|
38
|
+
image: redis:7-alpine
|
|
39
|
+
container_name: microcoreos_redis
|
|
40
|
+
restart: unless-stopped
|
|
41
|
+
ports:
|
|
42
|
+
- "6379:6379"
|
|
43
|
+
volumes:
|
|
44
|
+
- redis_data:/data
|
|
45
|
+
healthcheck:
|
|
46
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
47
|
+
interval: 5s
|
|
48
|
+
timeout: 3s
|
|
49
|
+
retries: 5
|
|
50
|
+
|
|
51
|
+
# ─── RabbitMQ ─────────────────────────────────────────────
|
|
52
|
+
rabbitmq:
|
|
53
|
+
image: rabbitmq:3.13-alpine
|
|
54
|
+
container_name: microcoreos_rabbitmq
|
|
55
|
+
restart: unless-stopped
|
|
56
|
+
ports:
|
|
57
|
+
- "5672:5672"
|
|
58
|
+
- "15672:15672"
|
|
59
|
+
environment:
|
|
60
|
+
RABBITMQ_DEFAULT_USER: guest
|
|
61
|
+
RABBITMQ_DEFAULT_PASS: guest
|
|
62
|
+
volumes:
|
|
63
|
+
- rabbitmq_data:/var/lib/rabbitmq
|
|
64
|
+
healthcheck:
|
|
65
|
+
test: ["CMD", "rabbitmq-diagnostics", "ping"]
|
|
66
|
+
interval: 10s
|
|
67
|
+
timeout: 5s
|
|
68
|
+
retries: 5
|
|
69
|
+
|
|
70
|
+
# ─── Kafka (KRaft, single node) ───────────────────────────
|
|
71
|
+
kafka:
|
|
72
|
+
image: apache/kafka:3.8.0
|
|
73
|
+
container_name: microcoreos_kafka
|
|
74
|
+
restart: unless-stopped
|
|
75
|
+
ports:
|
|
76
|
+
- "9092:9092"
|
|
77
|
+
environment:
|
|
78
|
+
# Single-node KRaft: the container is broker AND controller.
|
|
79
|
+
KAFKA_NODE_ID: 1
|
|
80
|
+
KAFKA_PROCESS_ROLES: broker,controller
|
|
81
|
+
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093
|
|
82
|
+
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
|
|
83
|
+
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
|
|
84
|
+
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
|
85
|
+
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
|
86
|
+
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
|
|
87
|
+
volumes:
|
|
88
|
+
- kafka_data:/var/lib/kafka/data
|
|
89
|
+
healthcheck:
|
|
90
|
+
test: ["CMD-SHELL", "/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server localhost:9092 >/dev/null 2>&1"]
|
|
91
|
+
interval: 10s
|
|
92
|
+
timeout: 10s
|
|
93
|
+
retries: 5
|
|
94
|
+
|
|
95
|
+
# ─── RustFS (S3-compatible object storage) ────────────────
|
|
96
|
+
rustfs:
|
|
97
|
+
image: rustfs/rustfs:latest
|
|
98
|
+
container_name: microcoreos_rustfs
|
|
99
|
+
restart: unless-stopped
|
|
100
|
+
ports:
|
|
101
|
+
- "9000:9000"
|
|
102
|
+
- "9001:9001"
|
|
103
|
+
environment:
|
|
104
|
+
RUSTFS_ACCESS_KEY: minioadmin
|
|
105
|
+
RUSTFS_SECRET_KEY: minioadmin
|
|
106
|
+
volumes:
|
|
107
|
+
- rustfs_data:/data
|
|
108
|
+
command: server /data --console-address ":9001"
|
|
109
|
+
healthcheck:
|
|
110
|
+
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
|
111
|
+
interval: 30s
|
|
112
|
+
timeout: 10s
|
|
113
|
+
retries: 3
|
|
114
|
+
|
|
115
|
+
# ─── Jaeger (OpenTelemetry / OTLP tracing UI) ─────────────
|
|
116
|
+
# UI: http://localhost:16686
|
|
117
|
+
# App: en .env → OTEL_ENABLED=true y
|
|
118
|
+
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
|
|
119
|
+
# Requiere: uv add opentelemetry-sdk opentelemetry-exporter-otlp
|
|
120
|
+
# Almacenamiento en memoria (dev): las trazas se pierden al reiniciar.
|
|
121
|
+
jaeger:
|
|
122
|
+
image: jaegertracing/all-in-one:1.60
|
|
123
|
+
container_name: microcoreos_jaeger
|
|
124
|
+
restart: unless-stopped
|
|
125
|
+
ports:
|
|
126
|
+
- "16686:16686" # Web UI
|
|
127
|
+
- "4317:4317" # OTLP gRPC (el que usa el tool telemetry)
|
|
128
|
+
- "4318:4318" # OTLP HTTP
|
|
129
|
+
environment:
|
|
130
|
+
COLLECTOR_OTLP_ENABLED: "true"
|
|
131
|
+
|
|
132
|
+
volumes:
|
|
133
|
+
postgres_data:
|
|
134
|
+
redis_data:
|
|
135
|
+
rabbitmq_data:
|
|
136
|
+
kafka_data:
|
|
137
|
+
rustfs_data:
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# The `microcoreos` command
|
|
2
|
+
|
|
3
|
+
Installed by the package. Five commands: create a project, install an extra,
|
|
4
|
+
see what changed upstream, boot, boot with reload.
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
microcoreos new <path> [--force] [--no-ai-kit] Materialize a new project
|
|
8
|
+
microcoreos add <extra> [--no-install] Install an extra completely
|
|
9
|
+
microcoreos upgrade [--apply] Report/apply upstream changes
|
|
10
|
+
microcoreos [run] [--boot-tool <tool>] Boot the Kernel
|
|
11
|
+
microcoreos dev Boot with auto-reload
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Except for `new`, run them from the root of a project — the directory holding
|
|
15
|
+
`tools/`, `domains/` and `plans/`. Running elsewhere is refused rather than
|
|
16
|
+
booting an empty system and calling it Ready.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## `microcoreos new <path>`
|
|
21
|
+
|
|
22
|
+
Copies the framework's source into your directory: `tools/`, `domains/system`,
|
|
23
|
+
`domains/devtools`, `extras/`, `plans/`, `dev_infra/`, `main.py`, `Dockerfile`,
|
|
24
|
+
`.env.example` — plus the AI kit (`AGENTS.md`, `INSTRUCTIONS_FOR_AI.md`,
|
|
25
|
+
`.agent/`, `docs/`).
|
|
26
|
+
|
|
27
|
+
One list decides all of it: `scaffold.RUNTIME_ENTRIES`. The wheel's copy under
|
|
28
|
+
`_template/` is generated from that same list by `hatch_build.py`, so what a
|
|
29
|
+
checkout copies and what the package ships cannot disagree.
|
|
30
|
+
|
|
31
|
+
**Why copied instead of imported.** Installing and swapping infrastructure here
|
|
32
|
+
IS file placement (`mv extras/available_tools/postgresql tools/`, dropping a
|
|
33
|
+
`{name}_driver.py` into `tools/event_bus/`). None of that works against
|
|
34
|
+
`site-packages`: it may be read-only, and anything written there is wiped by
|
|
35
|
+
the next upgrade. Only the Kernel — `microcoreos/` itself — stays in the
|
|
36
|
+
package.
|
|
37
|
+
|
|
38
|
+
It also writes, only when absent: `.env` (from `.env.example`),
|
|
39
|
+
`pyproject.toml`, and a `README.md` for the project. An existing one is never
|
|
40
|
+
overwritten, so `uv init && uv add microcoreos && microcoreos new .` works.
|
|
41
|
+
|
|
42
|
+
| Flag | Effect |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `--force` | Materialize even if `tools/` or `domains/` already exist |
|
|
45
|
+
| `--no-ai-kit` | Skip `AGENTS.md`, `INSTRUCTIONS_FOR_AI.md`, `.agent/`, `docs/` |
|
|
46
|
+
|
|
47
|
+
**Not materialized:** `ping` (the hello-world) and auth. A fresh project has no
|
|
48
|
+
`tools/auth` and no `domains/users` — no users table, no JWT, and no
|
|
49
|
+
`AUTH_SECRET_KEY` to set before it will boot. `http_server_tool` takes
|
|
50
|
+
`auth_validator` as an optional callback and never imports auth, so nothing
|
|
51
|
+
misses it.
|
|
52
|
+
|
|
53
|
+
`microcoreos add auth` installs it: the tool, plus the four plugins that ARE
|
|
54
|
+
auth — `create_user`, `login`, `get_me`, `logout` — with the model and the
|
|
55
|
+
migration. The CRUD around them (list, get-by-id, update, delete) is not
|
|
56
|
+
shipped: that is what you write for your own entities.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## `microcoreos add <extra>`
|
|
61
|
+
|
|
62
|
+
Activating an extra is three acts, and skipping any one fails somewhere
|
|
63
|
+
different:
|
|
64
|
+
|
|
65
|
+
| Act | Skipping it |
|
|
66
|
+
|---|---|
|
|
67
|
+
| Install the optional dependency | `No module named 'asyncpg'` at boot |
|
|
68
|
+
| Move the source into `tools/`/`domains/` | Nothing happens — discovery only sees those directories |
|
|
69
|
+
| Add its settings to `.env` | Boots, then connects nowhere |
|
|
70
|
+
|
|
71
|
+
This does all three:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
microcoreos add postgres
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
📦 Installing extra 'postgres'
|
|
79
|
+
|
|
80
|
+
$ uv add 'microcoreos[postgres]'
|
|
81
|
+
✓ extras/available_tools/postgresql → tools/postgresql
|
|
82
|
+
✓ .env += PG_HOST, PG_PORT, PG_USER, PG_PASSWORD, PG_DATABASE
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Available: `auth`, `ping`, `postgres`, `redis`, `s3`, `scheduler`, `kafka`,
|
|
86
|
+
`rabbitmq`, `chaos`. Run `microcoreos add` with no argument to list them.
|
|
87
|
+
|
|
88
|
+
`auth` and `ping` are the two that need no external service. `auth` is an extra
|
|
89
|
+
because a users table, a roles model and a JWT flavour are not something a
|
|
90
|
+
framework should impose; `ping` is a single `GET /ping` you read for the shape
|
|
91
|
+
of a minimal plugin and then delete.
|
|
92
|
+
|
|
93
|
+
Three rules it follows:
|
|
94
|
+
|
|
95
|
+
- **The tool moves before its domain.** A plugin cannot exist without the tool
|
|
96
|
+
it asks for, so the other order leaves a boot that aborts the plugin.
|
|
97
|
+
- **Drivers are not tools.** `kafka` and `rabbitmq` do not become
|
|
98
|
+
`tools/kafka/`; the `*_driver.py` drops into `tools/event_bus/` and `.env`
|
|
99
|
+
gets `EVENT_BUS_DRIVER=kafka`.
|
|
100
|
+
- **`.env` is never overwritten.** A value already there is your decision.
|
|
101
|
+
Re-running `add` is a no-op.
|
|
102
|
+
|
|
103
|
+
`uv add` runs only when there is a `pyproject.toml` and `uv` is on PATH —
|
|
104
|
+
otherwise the command is printed for you to run. `--no-install` skips that step.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## `microcoreos upgrade`
|
|
109
|
+
|
|
110
|
+
The mitigation for honest vendoring: your tools and domains are your source, so
|
|
111
|
+
a fix upstream does not reach them on its own.
|
|
112
|
+
|
|
113
|
+
`new` records the SHA-256 of every file it writes in
|
|
114
|
+
`.microcoreos/manifest.json`. That baseline is what makes three otherwise
|
|
115
|
+
indistinguishable states distinguishable:
|
|
116
|
+
|
|
117
|
+
| local | upstream | verdict |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| = baseline | changed | **safe to update** — you never touched it |
|
|
120
|
+
| changed | = baseline | **yours** — left alone, always |
|
|
121
|
+
| changed | changed | **conflict** — reported, never written |
|
|
122
|
+
| = baseline | withdrawn | **deleted** — the framework's file to take back |
|
|
123
|
+
| changed | withdrawn | **released** — kept, and no longer tracked at all |
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
microcoreos upgrade # report only (default)
|
|
127
|
+
microcoreos upgrade --apply # write the safe ones, move the baseline
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`--apply` never touches a conflict and never resurrects a file you deleted —
|
|
131
|
+
removing a tool you do not use is a supported act, not damage.
|
|
132
|
+
|
|
133
|
+
**A file dropped upstream is deleted here, unless you edited it.** The rule is
|
|
134
|
+
the same one as everywhere else: untouched is the framework's to withdraw, and
|
|
135
|
+
an empty `tools/<name>/` goes with the last file in it. If you did edit it, it
|
|
136
|
+
stays and leaves the baseline for good — upstream no longer ships it and you
|
|
137
|
+
changed it, so there is nothing left to compare, and re-reporting it on every
|
|
138
|
+
run would just teach you to skip the output.
|
|
139
|
+
|
|
140
|
+
If most of the baseline appears to have vanished, nothing is deleted: that is
|
|
141
|
+
what a partial or broken install looks like, not what a release looks like.
|
|
142
|
+
|
|
143
|
+
**The manifest belongs in version control.** The scaffolded `.gitignore` does
|
|
144
|
+
not exclude it on purpose: a teammate who clones the project without it cannot
|
|
145
|
+
upgrade, because their edits would be indistinguishable from stale files.
|
|
146
|
+
Without a manifest the command refuses outright — it is written by
|
|
147
|
+
`microcoreos new`, and its absence is an error rather than a degraded mode.
|
|
148
|
+
|
|
149
|
+
**Extras you installed are tracked to where they moved.** `microcoreos add`
|
|
150
|
+
records the move, and the baseline stays keyed by the file's upstream path —
|
|
151
|
+
so a fix to `extras/available_tools/scheduler/` reaches `tools/scheduler/`
|
|
152
|
+
where you actually put it.
|
|
153
|
+
|
|
154
|
+
Moving a folder yourself works too, including to a name no convention could
|
|
155
|
+
guess (`mv extras/available_tools/postgresql tools/my-db`). Names cannot follow
|
|
156
|
+
that, so content does: an unedited file still hashes to its baseline digest
|
|
157
|
+
wherever it now sits, and the move is written into the manifest the first time
|
|
158
|
+
`upgrade` runs — after which the folder is tracked by name and you can edit it
|
|
159
|
+
freely. A digest matching more than one candidate proves nothing and is
|
|
160
|
+
ignored, so nothing is claimed on a guess.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## `microcoreos run` / `microcoreos dev`
|
|
165
|
+
|
|
166
|
+
`microcoreos` alone is `microcoreos run`. Identical to `uv run main.py` —
|
|
167
|
+
the root `main.py` is a shim over this same code path, so both are the same
|
|
168
|
+
boot.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
microcoreos # boot
|
|
172
|
+
microcoreos run --boot-tool db # boot ONE tool in isolation, then exit
|
|
173
|
+
microcoreos dev # boot, reload on .py changes
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`--boot-tool` is the migrations pipeline entry point:
|
|
177
|
+
`DB_AUTO_MIGRATE=true microcoreos run --boot-tool db` — see
|
|
178
|
+
[ELASTIC_DEPLOYMENT.md](ELASTIC_DEPLOYMENT.md).
|
|
179
|
+
|
|
180
|
+
`dev` needs `watchfiles` (`uv add --dev watchfiles`); it says so if missing.
|
|
181
|
+
|
|
182
|
+
**Two things the command does that `python main.py` gets for free.** A console
|
|
183
|
+
script starts with the venv's `bin/` as `sys.path[0]`, not your project, so the
|
|
184
|
+
project root is inserted explicitly — without it every `import tools.*` fails
|
|
185
|
+
and the Kernel reports an empty system as Ready. And `.env` is loaded by
|
|
186
|
+
explicit path, because bare `load_dotenv()` searches upward from its *caller*,
|
|
187
|
+
which installed is `site-packages`, not your project.
|