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
microcoreos/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MicroCoreOS — the public address of the framework.
|
|
3
|
+
|
|
4
|
+
Plugins and tools import from HERE, never from the file that happens to define
|
|
5
|
+
a name today:
|
|
6
|
+
|
|
7
|
+
from microcoreos import BasePlugin, BaseTool, ToolUnavailableError
|
|
8
|
+
|
|
9
|
+
`Kernel`, `Container` and `Registry` stay in their submodules on purpose: they
|
|
10
|
+
are boot-time machinery (`main.py`, tests), not plugin surface.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from microcoreos.base_plugin import BasePlugin
|
|
14
|
+
from microcoreos.base_tool import BaseTool, ToolUnavailableError
|
|
15
|
+
from microcoreos.context import current_event_id_var, current_identity_var
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"BasePlugin",
|
|
19
|
+
"BaseTool",
|
|
20
|
+
"ToolUnavailableError",
|
|
21
|
+
"current_event_id_var",
|
|
22
|
+
"current_identity_var",
|
|
23
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: microcoreos-architecture
|
|
3
|
+
description: Ensures adherence to MicroCoreOS "Atomic Microkernel" architecture. Use when creating or modifying core, tools, plugins, or domains.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# MicroCoreOS Architecture Skill
|
|
7
|
+
|
|
8
|
+
## Reading Path
|
|
9
|
+
|
|
10
|
+
**To write a plugin**: Read `AI_CONTEXT.md` + entity model in `domains/{domain}/models/`. Nothing else.
|
|
11
|
+
**To create a full domain**: Use the `/new-domain` workflow.
|
|
12
|
+
**Templates + anti-patterns + detailed rules**: `INSTRUCTIONS_FOR_AI.md`.
|
|
13
|
+
|
|
14
|
+
## Pre-commit Checklist
|
|
15
|
+
|
|
16
|
+
- [ ] `main.py` untouched
|
|
17
|
+
- [ ] No cross-domain imports (use `event_bus`)
|
|
18
|
+
- [ ] Entity in `models/` mirrors DB only — schemas inline in plugin
|
|
19
|
+
- [ ] All request fields use `pydantic.Field` with constraints
|
|
20
|
+
- [ ] `response_model=` passed to `add_endpoint`
|
|
21
|
+
- [ ] Plugin is a single self-contained file
|
|
22
|
+
- [ ] `async def` for I/O, `def` for CPU
|
|
23
|
+
- [ ] Test file exists at `tests/test_{name}_plugin.py`
|
|
24
|
+
- [ ] `uv run main.py` runs without errors (or `microcoreos` / `microcoreos run` if installed)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Agent Persona for MicroCoreOS
|
|
2
|
+
|
|
3
|
+
You are a **Systems Architect** specialized in high-performance, resilient micro-kernels.
|
|
4
|
+
|
|
5
|
+
## Communication Style
|
|
6
|
+
- Precise and technical.
|
|
7
|
+
- Proactive in identifying architectural violations.
|
|
8
|
+
- Transparent about performance trade-offs (e.g., threading, memory isolation).
|
|
9
|
+
|
|
10
|
+
## Decision Framework
|
|
11
|
+
- **Core First**: Is this change affecting the Core? If yes, look for an alternative in Plugins/Tools.
|
|
12
|
+
- **Tool Isolation**: Does this Tool import another Tool? If so, REJECT the design and move the logic to a Plugin (Bridge).
|
|
13
|
+
- **Sacred Rules Review**: Before implementation, verify against the "Three Golden Rules" in `SKILL.md`.
|
|
14
|
+
- **Resilience**: Will a failure here crash the entire system?
|
|
15
|
+
- **Observability**: Can this be monitored via the `registry`?
|
|
16
|
+
|
|
17
|
+
- **No Boilerplate Thinking:** If you feel the urge to add setup code to `main.py`, STOP. The Kernel is auto-discovering. Do not write manual initialization code.
|
|
18
|
+
- **The "Magic" is in the Kernel:** Trust the `Kernel` to inject arguments based on parameter names in the Plugin's constructor.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Plan and build one or more features (plugins) on an EXISTING domain
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Feature Plan Workflow
|
|
6
|
+
|
|
7
|
+
The smallest planning level: new plugins on a domain that already exists. No
|
|
8
|
+
migrations, no new tools — if you need either, escalate to
|
|
9
|
+
[new-domain.md](new-domain.md) or [multi-domain-plan.md](multi-domain-plan.md).
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
- Read `AI_CONTEXT.md` — the live inventory (tools, domains, events, routes).
|
|
14
|
+
- Check `GET /system/events/schemas` (or the "Events emitted" lines in
|
|
15
|
+
`AI_CONTEXT.md`) for the payload contracts of any event you will consume.
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
### 1. Write the mini-plan
|
|
20
|
+
|
|
21
|
+
Write it to `plans/active_plan.yaml`: one `features:` entry per plugin
|
|
22
|
+
(~10-15 lines each), plus a `flows:` entry ONLY if the feature publishes or
|
|
23
|
+
consumes events — omit `flows` entirely otherwise. Same schema as the formal
|
|
24
|
+
plan format (`docs/PARALLEL_DEVELOPMENT.md`), just without `phase_0`:
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
plan:
|
|
28
|
+
domain: orders # existing domain
|
|
29
|
+
features:
|
|
30
|
+
- plugin: CancelOrderPlugin
|
|
31
|
+
file: domains/orders/plugins/cancel_order_plugin.py
|
|
32
|
+
function: "Cancel an order and announce it"
|
|
33
|
+
route: { method: POST, path: /orders/{order_id}/cancel }
|
|
34
|
+
db: { writes: [orders], reads: [] } # persistence contract — own-domain tables only
|
|
35
|
+
publishes:
|
|
36
|
+
- event: order.cancelled
|
|
37
|
+
model: OrderCancelledPayload
|
|
38
|
+
payload: { id: int, reason: str }
|
|
39
|
+
consumes: []
|
|
40
|
+
mocks: [db, event_bus]
|
|
41
|
+
test: tests/test_cancel_order.py
|
|
42
|
+
flows:
|
|
43
|
+
- name: order-cancellation
|
|
44
|
+
durability: ephemeral # durable → in-flight events must survive a crash (needs sqlite/redis driver)
|
|
45
|
+
happy_path: "POST /orders/{id}/cancel → order.cancelled → RefundPlugin → order.refunded"
|
|
46
|
+
e2e_test: tests/test_order_cancellation_chain.py
|
|
47
|
+
sad_path_test: tests/test_order_cancellation_dlq.py # mandatory: a link declares retries
|
|
48
|
+
links:
|
|
49
|
+
- consumes: order.cancelled
|
|
50
|
+
consumer: RefundPlugin
|
|
51
|
+
retries: 3
|
|
52
|
+
backoff: 1.0
|
|
53
|
+
idempotent: true # mandatory when retries > 0 OR the flow is durable
|
|
54
|
+
idempotency_test: tests/test_refund.py::test_on_order_cancelled_delivered_twice
|
|
55
|
+
dlq_watcher: null
|
|
56
|
+
atomic_with_db: false # true → this feature is the trigger for Issue 28 (outbox)
|
|
57
|
+
compensation: null
|
|
58
|
+
rpc_links: [] # every request() call, with timeout + on_timeout
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 2. Validate before writing code
|
|
62
|
+
|
|
63
|
+
`POST /system/plan/validate` with the plan (YAML or JSON) — it runs the 14
|
|
64
|
+
validity rules of `docs/PARALLEL_DEVELOPMENT.md` against this plan AND the
|
|
65
|
+
live system. Zero `errors` before any code; `warnings` are advisory. The main
|
|
66
|
+
things it catches at this level:
|
|
67
|
+
|
|
68
|
+
- The `route` and `file` collide with nothing live.
|
|
69
|
+
- Every consumed event exists (live system or this plan) and provides the
|
|
70
|
+
`requires` keys.
|
|
71
|
+
- Every flow link has the sad-path checklist answered (`idempotent: true` +
|
|
72
|
+
`idempotency_test` where `retries > 0` or the flow is `durable`).
|
|
73
|
+
- `sad_path_test` present where the flow declares retries / DLQ / compensation.
|
|
74
|
+
- `db:` tables are owned by this domain.
|
|
75
|
+
|
|
76
|
+
### 3. Implement
|
|
77
|
+
|
|
78
|
+
One file per feature. Request, response AND event payload schemas inline.
|
|
79
|
+
Publish with `XxxPayload(...).model_dump()` — bare call, no arguments.
|
|
80
|
+
|
|
81
|
+
### 4. Test
|
|
82
|
+
|
|
83
|
+
- One test per plugin proving the black-box contract: input → output, DB
|
|
84
|
+
effects on the declared tables, published payloads with the declared fields.
|
|
85
|
+
Mock exactly the tools the plan's `mocks:` lists; run the rest as real
|
|
86
|
+
in-memory instances (`INSTRUCTIONS_FOR_AI.md` § Testing).
|
|
87
|
+
- One double-delivery test per idempotent link (same envelope twice → same
|
|
88
|
+
final state), at the path declared in `idempotency_test`.
|
|
89
|
+
- One chain test per flow, using the helper:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from tests.helpers.trace_chains import build_tree, assert_chain
|
|
93
|
+
# trigger the flow, then:
|
|
94
|
+
assert_chain(build_tree(bus.get_trace_history()), ["order.cancelled", "order.refunded"])
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- One sad-path test per flow that declares retries / DLQ / compensation: force
|
|
98
|
+
the consumer to fail (mock that raises) and assert the decided outcome —
|
|
99
|
+
`_dlq.<event>` is causally chained to the event that failed, so the same
|
|
100
|
+
helper works: `assert_chain(tree, ["order.cancelled", "_dlq.order.cancelled"])`.
|
|
101
|
+
|
|
102
|
+
### 5. Close
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
// turbo
|
|
106
|
+
uv run main.py # or: microcoreos (if you installed the package)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- `GET /system/lint` → no warnings, no `UNTYPED_PAYLOAD` for your events.
|
|
110
|
+
- Regenerated `AI_CONTEXT.md` matches the plan (routes, events, keys).
|
|
111
|
+
**The feature is done when AI_CONTEXT == plan.**
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Plan and build a large spec spanning multiple domains (full formal plan, parallel execution)
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Multi-Domain Plan Workflow
|
|
6
|
+
|
|
7
|
+
The largest planning level: a spec that creates or touches several domains,
|
|
8
|
+
with event chains crossing domain boundaries. The methodology is fully
|
|
9
|
+
specified in `docs/PARALLEL_DEVELOPMENT.md` — this workflow is its checklist.
|
|
10
|
+
|
|
11
|
+
**Everything is decided before any code exists**: every migration, model,
|
|
12
|
+
tool, plugin, route, event (with its payload model), and every chain with its
|
|
13
|
+
happy path and sad paths. Code-time conflicts are structurally impossible;
|
|
14
|
+
what remains is getting the plan right.
|
|
15
|
+
|
|
16
|
+
## Phase 1 — The full plan (the contract, authored FIRST)
|
|
17
|
+
|
|
18
|
+
Write the complete YAML plan of `docs/PARALLEL_DEVELOPMENT.md` ("Formal plan
|
|
19
|
+
format") to `plans/active_plan.yaml`: `phase_0` (every migration with its
|
|
20
|
+
`tables:` ownership AND its full `columns:` — phase 0 is built from the plan,
|
|
21
|
+
nothing is improvised later), `features` (one per plugin, with
|
|
22
|
+
`publishes.model` / `consumes.requires` / the `db:` persistence contract),
|
|
23
|
+
and `flows` — each with
|
|
24
|
+
its `durability` (may in-flight events die with the process? `durable` needs
|
|
25
|
+
the sqlite/redis driver) and the sad-path checklist per link:
|
|
26
|
+
|
|
27
|
+
- `retries` / `backoff` — re-delivery policy
|
|
28
|
+
- `idempotent` — MANDATORY `true` where `retries > 0` OR the flow is `durable`
|
|
29
|
+
(durable transports re-deliver after a crash even with zero retries)
|
|
30
|
+
- `idempotency_test` — the double-delivery proof for every idempotent link
|
|
31
|
+
- `dlq_watcher` — who consumes `_dlq.<event>` (`null` = loss explicitly
|
|
32
|
+
accepted; a non-null watcher must exist in the plan or live)
|
|
33
|
+
- `atomic_with_db` — `true` means this chain cannot lose the event between DB
|
|
34
|
+
commit and publish → it is the implementation trigger for the Transactional
|
|
35
|
+
Outbox (ROADMAP Issue 28); flag it, do not improvise one
|
|
36
|
+
- `compensation` — the event that undoes upstream work if the chain dies
|
|
37
|
+
(saga); it must be published AND consumed within the plan
|
|
38
|
+
- `sad_path_test` (flow-level) — mandatory when any link declares retries,
|
|
39
|
+
a DLQ watcher or a compensation
|
|
40
|
+
- `rpc_links` (flow-level) — every `request()` call, with `timeout` and
|
|
41
|
+
`on_timeout`
|
|
42
|
+
|
|
43
|
+
Then run the 15 validity rules mechanically: `POST /system/plan/validate`
|
|
44
|
+
with the plan (YAML or JSON) against the live system — zero `errors` before
|
|
45
|
+
building anything. An invalid plan is a task-allocation error — fix the plan,
|
|
46
|
+
never patch it in code.
|
|
47
|
+
|
|
48
|
+
## Phase 0 — Foundation (built FROM the plan; serial, one author)
|
|
49
|
+
|
|
50
|
+
1. New **tools** only if the spec demands infrastructure that does not exist
|
|
51
|
+
→ follow [new-tool.md](new-tool.md), parity suite included. Written 1:1
|
|
52
|
+
from the plan's `contract:` (signatures + return shape), never inventing
|
|
53
|
+
a method — same rule as `columns:` for migrations.
|
|
54
|
+
2. All **migrations** (`domains/*/migrations/*.sql`) with their **models**,
|
|
55
|
+
written 1:1 from the plan's `columns:`, sequential numbering, `-- depends:`
|
|
56
|
+
for cross-domain ordering.
|
|
57
|
+
|
|
58
|
+
(1 and 2 are independent at write time — disjoint files; two agents in any
|
|
59
|
+
order or in parallel is fine. Migrations keep one author for numbering.)
|
|
60
|
+
|
|
61
|
+
3. Boot once (`uv run main.py`, or `microcoreos` if you installed the
|
|
62
|
+
package), only after everything is written →
|
|
63
|
+
regenerated `AI_CONTEXT.md` is the ground truth every agent receives
|
|
64
|
+
(it must include the new tools' interfaces). Freeze phase 0.
|
|
65
|
+
|
|
66
|
+
## Phase 2 — Execution (parallel)
|
|
67
|
+
|
|
68
|
+
Dispatch one agent per feature with the **canonical executor prompt**
|
|
69
|
+
(`docs/PARALLEL_DEVELOPMENT.md` § Phase 2): a byte-identical shared prefix —
|
|
70
|
+
`AI_CONTEXT.md` (which embeds the executor rules and templates as its
|
|
71
|
+
"Plugin Authoring Guide" section) → full plan — plus one final per-agent line
|
|
72
|
+
naming its feature. Dispatch executors **write-only, scoped to the two paths
|
|
73
|
+
their plan entry declares** (`file:` + `test:` for a feature, the flow's two
|
|
74
|
+
test paths for flow tests — no read/search/shell tools): the prefix is self-sufficient
|
|
75
|
+
by construction, with one complete template per deliverable type embedded in
|
|
76
|
+
`AI_CONTEXT.md` § Authoring Templates. Agents never open the plan or `AI_CONTEXT.md` themselves;
|
|
77
|
+
the identical prefix lets any prefix-caching engine process the shared block
|
|
78
|
+
once for the whole wave. Each agent produces exactly two files: its plugin
|
|
79
|
+
and its unit test. Event payload schemas go inline in each publisher plugin
|
|
80
|
+
(`XxxPayload(...).model_dump()`). Never assign two agents to the same feature.
|
|
81
|
+
|
|
82
|
+
## Phase 3 — Integration boot (the safety net)
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
// turbo
|
|
86
|
+
uv run main.py # or: microcoreos (if you installed the package)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
1. `GET /system/lint` → zero warnings (arch, drift, event contracts) and no
|
|
90
|
+
`UNTYPED_PAYLOAD` for the plan's events.
|
|
91
|
+
2. `GET /system/events/schemas` → every planned event appears with its model.
|
|
92
|
+
3. Full suite: `uv run -m pytest` — includes one chain e2e per flow
|
|
93
|
+
(`tests/helpers/trace_chains.py`: `assert_chain(build_tree(...), [...])`),
|
|
94
|
+
the sad-path tests (`_dlq.<event>` chains) and the double-delivery
|
|
95
|
+
idempotency tests the plan declared.
|
|
96
|
+
4. Regenerated `AI_CONTEXT.md` == plan. **The spec is done when they match.**
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Create a complete new domain with entity, migration, and CRUD plugins
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# New Domain Workflow
|
|
6
|
+
|
|
7
|
+
Creates a full domain from scratch: entity model, SQL migration, and one plugin per use case.
|
|
8
|
+
|
|
9
|
+
> Planning levels: single features on an existing domain → [feature-plan.md](feature-plan.md) ·
|
|
10
|
+
> one new domain → this workflow · several domains / cross-domain chains →
|
|
11
|
+
> [multi-domain-plan.md](multi-domain-plan.md) · new infrastructure → [new-tool.md](new-tool.md).
|
|
12
|
+
|
|
13
|
+
## Prerequisites
|
|
14
|
+
- Read `AI_CONTEXT.md` for available tools.
|
|
15
|
+
- Read `INSTRUCTIONS_FOR_AI.md` for rules and templates.
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
### 0. Plan the domain first
|
|
20
|
+
|
|
21
|
+
Write the plan of `docs/PARALLEL_DEVELOPMENT.md` ("Formal plan format") scoped
|
|
22
|
+
to this domain: `phase_0` (its migrations + models, with table ownership AND
|
|
23
|
+
every table's `columns:` — name, SQL type, constraints; steps 2-3 below are
|
|
24
|
+
written from this, never invented), `features` (one per plugin, every
|
|
25
|
+
published event with its payload `model` and fields, plus the `db:`
|
|
26
|
+
persistence contract), and — ONLY if any plugin publishes or consumes events —
|
|
27
|
+
`flows` (durability, happy path + the sad-path checklist per link — including
|
|
28
|
+
the `atomic_with_db` outbox question — and the declared `idempotency_test` /
|
|
29
|
+
`sad_path_test` files). A pure-CRUD domain has no `flows` section at all; a
|
|
30
|
+
domain whose delete cascades through one event has exactly one flow.
|
|
31
|
+
Validate with `POST /system/plan/validate` before writing code. Build in that
|
|
32
|
+
order — tools first if any, then migrations + models, then plugins with their
|
|
33
|
+
events. Nothing below this line should require a decision the plan did not
|
|
34
|
+
already make. Expected size for a CRUD domain with one event chain: ~80-120
|
|
35
|
+
lines of YAML, one pass.
|
|
36
|
+
|
|
37
|
+
### 1. Create the domain folder structure
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
// turbo
|
|
41
|
+
mkdir -p domains/{name}/models domains/{name}/migrations domains/{name}/plugins
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Create `domains/{name}/__init__.py`:
|
|
45
|
+
```python
|
|
46
|
+
# Auto-discovered by the Kernel. No manual registration needed.
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2. Create the Entity model
|
|
50
|
+
|
|
51
|
+
File: `domains/{name}/models/{name}.py`
|
|
52
|
+
|
|
53
|
+
This file contains ONE thing: the Pydantic model that mirrors the database table exactly.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from pydantic import BaseModel
|
|
57
|
+
|
|
58
|
+
class {Name}Entity(BaseModel):
|
|
59
|
+
id: int | None = None
|
|
60
|
+
# Add fields that match the DB columns exactly
|
|
61
|
+
# Use the DB column names (e.g. password_hash, not password)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. Create the SQL migration
|
|
65
|
+
|
|
66
|
+
File: `domains/{name}/migrations/001_create_{name}_table.sql`
|
|
67
|
+
|
|
68
|
+
Write raw SQL that creates the table. Use `$1, $2...` placeholders in queries (PostgreSQL-style, auto-converted for SQLite).
|
|
69
|
+
|
|
70
|
+
```sql
|
|
71
|
+
CREATE TABLE IF NOT EXISTS {name}s (
|
|
72
|
+
id SERIAL PRIMARY KEY,
|
|
73
|
+
-- columns matching the entity model
|
|
74
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
75
|
+
);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 4. Create plugins (1 file = 1 use case)
|
|
79
|
+
|
|
80
|
+
For each operation (create, get_all, get_by_id, update, delete), create a separate plugin file in `domains/{name}/plugins/`.
|
|
81
|
+
|
|
82
|
+
**Critical rules**:
|
|
83
|
+
- Define the **request schema** (what the HTTP client sends) at the **top of the plugin file**, NOT in the models folder.
|
|
84
|
+
- Define the **response schema** (what the HTTP client receives) at the **top of the plugin file** too — never import the Entity for this; only expose the fields you actually return.
|
|
85
|
+
- Define the **event payload schema** for every event this plugin publishes, also at the top of the file: `{Name}CreatedPayload(BaseModel)`. Publish with `.model_dump()` (bare call, no arguments). The publisher owns the event contract — consumers in other domains never import it; they declare their own model with only the fields they need (tolerant reader).
|
|
86
|
+
- Always pass `response_model=` to `add_endpoint` — this generates complete OpenAPI docs.
|
|
87
|
+
|
|
88
|
+
Example for create:
|
|
89
|
+
|
|
90
|
+
File: `domains/{name}/plugins/create_{name}_plugin.py`
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from typing import Optional
|
|
94
|
+
from pydantic import BaseModel
|
|
95
|
+
from microcoreos import BasePlugin
|
|
96
|
+
|
|
97
|
+
# ── Request schema lives HERE ──────────────────────
|
|
98
|
+
class Create{Name}Request(BaseModel):
|
|
99
|
+
# Only input fields — no id, no internal fields
|
|
100
|
+
field1: str
|
|
101
|
+
field2: int
|
|
102
|
+
|
|
103
|
+
# ── Response schema lives HERE ─────────────────────
|
|
104
|
+
class {Name}Data(BaseModel):
|
|
105
|
+
id: int
|
|
106
|
+
field1: str
|
|
107
|
+
field2: int
|
|
108
|
+
|
|
109
|
+
class Create{Name}Response(BaseModel):
|
|
110
|
+
success: bool
|
|
111
|
+
data: Optional[{Name}Data] = None
|
|
112
|
+
error: Optional[str] = None
|
|
113
|
+
|
|
114
|
+
# ── Event payload schema lives HERE (publisher owns the contract) ──
|
|
115
|
+
class {Name}CreatedPayload(BaseModel):
|
|
116
|
+
id: int
|
|
117
|
+
|
|
118
|
+
class Create{Name}Plugin(BasePlugin):
|
|
119
|
+
def __init__(self, http, db, event_bus, logger):
|
|
120
|
+
self.http = http
|
|
121
|
+
self.db = db
|
|
122
|
+
self.bus = event_bus
|
|
123
|
+
self.logger = logger
|
|
124
|
+
|
|
125
|
+
async def on_boot(self):
|
|
126
|
+
self.http.add_endpoint(
|
|
127
|
+
"/{name}s", "POST", self.execute,
|
|
128
|
+
tags=["{Name}s"], request_model=Create{Name}Request,
|
|
129
|
+
response_model=Create{Name}Response,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
async def execute(self, data: dict, context=None):
|
|
133
|
+
try:
|
|
134
|
+
req = Create{Name}Request(**data)
|
|
135
|
+
new_id = await self.db.execute(
|
|
136
|
+
"INSERT INTO {name}s (field1, field2) VALUES ($1, $2) RETURNING id",
|
|
137
|
+
[req.field1, req.field2]
|
|
138
|
+
)
|
|
139
|
+
self.logger.info(f"{Name} created with ID {new_id}")
|
|
140
|
+
await self.bus.publish("{name}.created", {Name}CreatedPayload(id=new_id).model_dump())
|
|
141
|
+
return {"success": True, "data": {"id": new_id, "field1": req.field1, "field2": req.field2}}
|
|
142
|
+
except Exception as e:
|
|
143
|
+
# Safe Error Reporting: log technically, respond safely (never str(e)).
|
|
144
|
+
self.logger.error(f"Failed to create {name}: {e}")
|
|
145
|
+
return {"success": False, "error": "Database operation failed"}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Repeat for: `get_{name}s_plugin.py`, `get_{name}_by_id_plugin.py`, `update_{name}_plugin.py`, `delete_{name}_plugin.py`.
|
|
149
|
+
|
|
150
|
+
### 5. Verify
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
// turbo
|
|
154
|
+
uv run main.py # or: microcoreos (if you installed the package)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Check that:
|
|
158
|
+
- Migration ran successfully (look for `[Migration] ✅` in logs)
|
|
159
|
+
- Endpoints appear in the Swagger UI at `http://localhost:5000/docs`
|
|
160
|
+
- `GET /system/lint` has no warnings and no `UNTYPED_PAYLOAD` for your events
|
|
161
|
+
- `GET /system/events/schemas` lists every event the plan declared
|
|
162
|
+
- `AI_CONTEXT.md` was regenerated with the new domain — **done when it matches the plan**
|
|
163
|
+
|
|
164
|
+
### 6. Generate tests
|
|
165
|
+
|
|
166
|
+
Create `tests/test_{name}_plugin.py` with one test per plugin. Mock exactly
|
|
167
|
+
the tools the plan's `mocks:` field lists; run the rest as real in-memory
|
|
168
|
+
instances (`INSTRUCTIONS_FOR_AI.md` § Testing). Example with everything
|
|
169
|
+
mocked (`mocks: [http, db, event_bus, logger]`):
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
import pytest
|
|
173
|
+
from unittest.mock import MagicMock, AsyncMock
|
|
174
|
+
from domains.{name}.plugins.create_{name}_plugin import Create{Name}Plugin
|
|
175
|
+
|
|
176
|
+
@pytest.mark.anyio
|
|
177
|
+
async def test_create_{name}_success():
|
|
178
|
+
plugin = Create{Name}Plugin(
|
|
179
|
+
http=MagicMock(),
|
|
180
|
+
db=AsyncMock(return_value=1),
|
|
181
|
+
event_bus=AsyncMock(),
|
|
182
|
+
logger=MagicMock(),
|
|
183
|
+
)
|
|
184
|
+
result = await plugin.execute({"field1": "value", "field2": 42})
|
|
185
|
+
assert result["success"] is True
|
|
186
|
+
assert result["data"]["id"] == 1
|
|
187
|
+
|
|
188
|
+
@pytest.mark.anyio
|
|
189
|
+
async def test_create_{name}_db_error():
|
|
190
|
+
plugin = Create{Name}Plugin(
|
|
191
|
+
http=MagicMock(),
|
|
192
|
+
db=AsyncMock(side_effect=Exception("DB down")),
|
|
193
|
+
event_bus=AsyncMock(),
|
|
194
|
+
logger=MagicMock(),
|
|
195
|
+
)
|
|
196
|
+
result = await plugin.execute({"field1": "value", "field2": 42})
|
|
197
|
+
assert result["success"] is False
|
|
198
|
+
assert "DB down" not in result["error"] # Safe Errors: technical detail never reaches the client
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Run with `uv run -m pytest tests/test_{name}_plugin.py`.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Create a new infrastructure Tool, or a replacement Tool that swaps an existing one (parity suite mandatory)
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# New Tool Workflow
|
|
6
|
+
|
|
7
|
+
Tools are the ONE legitimate place for shared infrastructure logic. Everything
|
|
8
|
+
else about them exists so they can be swapped without touching a single plugin.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
- Read `INSTRUCTIONS_FOR_AI.md` → "New Tool" and "The Parity Rule".
|
|
13
|
+
- Decide which case you are in:
|
|
14
|
+
- **A. New capability** (a tool name that does not exist yet, e.g. `s3`).
|
|
15
|
+
- **B. Replacement** (same `name`, different backend, e.g. Redis state for
|
|
16
|
+
in-memory state). **The parity suite is NOT optional here.**
|
|
17
|
+
|
|
18
|
+
## Rules (both cases)
|
|
19
|
+
|
|
20
|
+
1. **Location**: `tools/{name}/{name}_tool.py` — or `extras/available_tools/{name}/`
|
|
21
|
+
if it should not be active by default (a replacement ALWAYS starts in
|
|
22
|
+
extras/: two tools with the same `name` silently overwrite each other).
|
|
23
|
+
2. **The `name` property is the contract** — it is the DI injection key.
|
|
24
|
+
3. **A tool never uses other tools.** If a capability needs `db` + `event_bus`
|
|
25
|
+
+ `scheduler`, it is not a tool: compose it in the plugin layer
|
|
26
|
+
(precedents: DurableOneShotsPlugin, the deferred Outbox — Issue 28).
|
|
27
|
+
4. **Self-documented**: every public method appears in
|
|
28
|
+
`get_interface_description()` — the anti-drift linter warns on discrepancies.
|
|
29
|
+
5. **Config via `os.getenv()`** inside the tool (the `config` tool is for plugins).
|
|
30
|
+
6. **Header spec**: the tool's docstring/header documents its replacement
|
|
31
|
+
contract — the exact API and semantics a substitute must honor.
|
|
32
|
+
7. **External backend?** Make its connection-error class inherit
|
|
33
|
+
`ToolUnavailableError` so ToolProxy marks it DEAD on the first
|
|
34
|
+
infrastructure failure. In-memory/local tools skip this.
|
|
35
|
+
|
|
36
|
+
## Steps
|
|
37
|
+
|
|
38
|
+
### 1. Write the contract first
|
|
39
|
+
|
|
40
|
+
The header spec + `get_interface_description()`. If this is case B, the
|
|
41
|
+
contract already exists in the reference tool's header — read it, honor it.
|
|
42
|
+
If the tool is phase 0 of a formal plan, the plan's `contract:` entry declares
|
|
43
|
+
the signatures — honor it the way migrations honor `columns:`, never inventing
|
|
44
|
+
a method. Keep the signatures backend-neutral: a payments contract that mirrors
|
|
45
|
+
one provider's API makes the future swap cost what the abstraction saved.
|
|
46
|
+
|
|
47
|
+
### 2. Implement
|
|
48
|
+
|
|
49
|
+
No imports from other tools, no plugin imports, stateless where possible.
|
|
50
|
+
|
|
51
|
+
### 3. Parity suite (case B: mandatory / case A: write it for the future)
|
|
52
|
+
|
|
53
|
+
The same test battery runs against the reference implementation AND yours:
|
|
54
|
+
|
|
55
|
+
- Canonical examples: `tests/tools/test_state_parity.py`,
|
|
56
|
+
`tests/tools/test_event_bus_broker_parity.py` (parameterized over transports).
|
|
57
|
+
- If the backend needs a server, the suite skips itself when unavailable and
|
|
58
|
+
the server is added to CI services (`dev_infra/docker-compose.yml`).
|
|
59
|
+
- **A replacement that does not pass the reference's parity suite is not a
|
|
60
|
+
replacement.** (Issue 22 — Contract Parity Rules.)
|
|
61
|
+
|
|
62
|
+
### 4. Activate and verify end to end
|
|
63
|
+
|
|
64
|
+
- Case B: move the reference out of `tools/`, move yours in (or set its env
|
|
65
|
+
switch), boot, and exercise a real flow through the swapped tool.
|
|
66
|
+
- Check `AI_CONTEXT.md` regenerated with the tool's interface, and
|
|
67
|
+
`GET /system/status` shows it `OK`.
|
|
68
|
+
|
|
69
|
+
### 5. Document
|
|
70
|
+
|
|
71
|
+
- Add the tool to `README.md` (Available Tools / extras table).
|
|
72
|
+
- If it introduced a new pattern or decision, record it in `ROADMAP.md`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Dependencies and virtual environments
|
|
2
|
+
.venv/
|
|
3
|
+
|
|
4
|
+
# Secrets — never bake into the image
|
|
5
|
+
.env
|
|
6
|
+
|
|
7
|
+
# Local database files
|
|
8
|
+
*.db
|
|
9
|
+
*.sqlite3
|
|
10
|
+
|
|
11
|
+
# Python cache
|
|
12
|
+
__pycache__/
|
|
13
|
+
*.py[cod]
|
|
14
|
+
*.so
|
|
15
|
+
|
|
16
|
+
# Dev tooling
|
|
17
|
+
.github/
|
|
18
|
+
dev_infra/
|
|
19
|
+
tests/
|
|
20
|
+
|
|
21
|
+
# OS artifacts
|
|
22
|
+
.DS_Store
|
|
23
|
+
Thumbs.db
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# ══════════════════════════════════════════════════════════════════
|
|
2
|
+
# MicroCoreOS — Environment Variables Reference
|
|
3
|
+
# Copy this file to .env and fill in your values.
|
|
4
|
+
# ══════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# ─── HTTP Server ──────────────────────────────────────────────────
|
|
8
|
+
HTTP_PORT=5000
|
|
9
|
+
|
|
10
|
+
# Bind address. Default: 127.0.0.1 (dev-safe).
|
|
11
|
+
# ⚠ PRODUCTION / DOCKER: must be 0.0.0.0 or the service won't accept external connections.
|
|
12
|
+
# HTTP_HOST=0.0.0.0
|
|
13
|
+
|
|
14
|
+
# CORS allowed origins. Default: * (allow all — dev-friendly).
|
|
15
|
+
# Production: set to your actual frontend domain(s), comma-separated.
|
|
16
|
+
# HTTP_CORS_ORIGINS=https://app.example.com,https://admin.example.com
|
|
17
|
+
|
|
18
|
+
# Uvicorn log level. Default: warning (silent in prod).
|
|
19
|
+
# Set to "info" to enable HTTP access logs (method, path, status, latency).
|
|
20
|
+
# HTTP_LOG_LEVEL=info
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ─── Authentication ───────────────────────────────────────────────
|
|
24
|
+
# Not here by default: auth is an extra. `microcoreos add auth` appends
|
|
25
|
+
# AUTH_SECRET_KEY and AUTH_TOKEN_EXPIRE_MINUTES to your .env.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ─── Database ─────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
# SQLite (default, zero-config — just run uv run main.py)
|
|
31
|
+
SQLITE_DB_PATH=database.db
|
|
32
|
+
|
|
33
|
+
# Migrations policy. Default: true (dev convenience — boot applies pending
|
|
34
|
+
# migrations). ⚠ PRODUCTION: migrating from a replica is PROHIBITED — set
|
|
35
|
+
# false in EVERY replica. Migrations run ONLY as a CI/CD pipeline step, in a
|
|
36
|
+
# SINGLE instance, under explicit human supervision:
|
|
37
|
+
# DB_AUTO_MIGRATE=true uv run main.py --boot-tool db
|
|
38
|
+
# DB_AUTO_MIGRATE=false
|
|
39
|
+
|
|
40
|
+
# PostgreSQL (production — swap db tool, see INSTRUCTIONS_FOR_AI.md)
|
|
41
|
+
# PG_HOST=localhost
|
|
42
|
+
# PG_PORT=5432
|
|
43
|
+
# PG_USER=postgres
|
|
44
|
+
# PG_PASSWORD=postgres
|
|
45
|
+
# PG_DATABASE=microcoreos
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ─── Observability: Metrics ───────────────────────────────────────
|
|
49
|
+
# Timing is ALWAYS ON — ToolProxy measures every tool call automatically.
|
|
50
|
+
# No config needed. Access via:
|
|
51
|
+
# records = self.registry.get_metrics()
|
|
52
|
+
# self.registry.add_metrics_sink(callback)
|
|
53
|
+
# Each record: {tool, method, duration_ms, success, timestamp}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ─── Observability: Health Check ──────────────────────────────────
|
|
57
|
+
# ToolHealthPlugin runs in the background and calls db.health_check()
|
|
58
|
+
# periodically, updating the registry status proactively.
|
|
59
|
+
HEALTH_CHECK_INTERVAL=30 # seconds between checks (default: 30)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ─── Observability: OpenTelemetry ─────────────────────────────────
|
|
63
|
+
# Disabled by default. When enabled, ALL tool calls get OTel spans
|
|
64
|
+
# automatically via ToolProxy — no changes needed in plugins or tools.
|
|
65
|
+
#
|
|
66
|
+
# Install required packages first:
|
|
67
|
+
# uv add opentelemetry-sdk opentelemetry-exporter-otlp opentelemetry-instrumentation-fastapi
|
|
68
|
+
#
|
|
69
|
+
OTEL_ENABLED=false
|
|
70
|
+
OTEL_SERVICE_NAME=microcoreos
|
|
71
|
+
|
|
72
|
+
# Export destination — leave commented to use console (dev mode)
|
|
73
|
+
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 # Jaeger / Grafana Tempo / any OTLP backend
|
|
74
|
+
|
|
75
|
+
# BatchSpanProcessor tuning (optional — OTel standard env vars)
|
|
76
|
+
# OTEL_BSP_SCHEDULE_DELAY=5000 # ms between export batches (default: 5000)
|
|
77
|
+
# OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512 # spans per batch (default: 512)
|
|
78
|
+
# OTEL_BSP_MAX_QUEUE_SIZE=2048 # in-memory buffer size (default: 2048)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ─── Chaos Engineering ────────────────────────────────────────────
|
|
83
|
+
# Intentionally fails tools/plugins on boot to test fault tolerance.
|
|
84
|
+
# NEVER enable in production.
|
|
85
|
+
# CHAOS_ENABLED=true
|
|
86
|
+
# ─── S3 Storage ───────────────────────────────────────────────────
|
|
87
|
+
AWS_ACCESS_KEY_ID=your-access-key
|
|
88
|
+
AWS_SECRET_ACCESS_KEY=your-secret-key
|
|
89
|
+
AWS_DEFAULT_REGION=us-east-1
|
|
90
|
+
AWS_S3_ENDPOINT_URL=http://localhost:9000
|
|
91
|
+
AWS_S3_DEFAULT_BUCKET=microcoreos-bucket
|
|
92
|
+
AWS_S3_SIZE_LIMIT_ENABLED=true
|
|
93
|
+
AWS_S3_MAX_FILE_SIZE_MB=10
|
|
94
|
+
AWS_S3_VERIFY_SSL=true
|
|
95
|
+
|
|
96
|
+
|