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.
Files changed (112) hide show
  1. microcoreos/__init__.py +23 -0
  2. microcoreos/_template/.agent/skills/microcoreos-architecture/SKILL.md +24 -0
  3. microcoreos/_template/.agent/skills/microcoreos-architecture/agent.md +18 -0
  4. microcoreos/_template/.agent/workflows/feature-plan.md +111 -0
  5. microcoreos/_template/.agent/workflows/multi-domain-plan.md +96 -0
  6. microcoreos/_template/.agent/workflows/new-domain.md +201 -0
  7. microcoreos/_template/.agent/workflows/new-tool.md +72 -0
  8. microcoreos/_template/.dockerignore +23 -0
  9. microcoreos/_template/.env.example +96 -0
  10. microcoreos/_template/.gitignore +57 -0
  11. microcoreos/_template/AGENTS.md +175 -0
  12. microcoreos/_template/Dockerfile +35 -0
  13. microcoreos/_template/INSTRUCTIONS_FOR_AI.md +560 -0
  14. microcoreos/_template/dev_infra/cache_probe.py +176 -0
  15. microcoreos/_template/dev_infra/docker-compose.yml +137 -0
  16. microcoreos/_template/docs/CLI.md +187 -0
  17. microcoreos/_template/docs/CORE_INFRASTRUCTURE.md +372 -0
  18. microcoreos/_template/docs/ELASTIC_DEPLOYMENT.md +277 -0
  19. microcoreos/_template/docs/EVENT_BUS.md +288 -0
  20. microcoreos/_template/docs/HTTP_SERVER.md +98 -0
  21. microcoreos/_template/docs/INDEX.md +41 -0
  22. microcoreos/_template/docs/OBSERVABILITY.md +56 -0
  23. microcoreos/_template/docs/OBSERVABILITY_API.md +183 -0
  24. microcoreos/_template/docs/PARALLEL_DEVELOPMENT.md +494 -0
  25. microcoreos/_template/docs/PLAN_EVENT_LINTER.md +95 -0
  26. microcoreos/_template/docs/TECH_DEBT.md +388 -0
  27. microcoreos/_template/docs/translations/es/README.md +265 -0
  28. microcoreos/_template/domains/devtools/lint/plugin_sources.py +51 -0
  29. microcoreos/_template/domains/devtools/plugins/discovery_naming_linter_plugin.py +89 -0
  30. microcoreos/_template/domains/devtools/plugins/domain_isolation_linter_plugin.py +80 -0
  31. microcoreos/_template/domains/devtools/plugins/event_contract_linter_plugin.py +420 -0
  32. microcoreos/_template/domains/devtools/plugins/event_schemas_plugin.py +94 -0
  33. microcoreos/_template/domains/devtools/plugins/field_divergence_linter_plugin.py +178 -0
  34. microcoreos/_template/domains/devtools/plugins/plan_validator_plugin.py +581 -0
  35. microcoreos/_template/domains/devtools/plugins/route_collision_linter_plugin.py +51 -0
  36. microcoreos/_template/domains/devtools/plugins/table_ownership_linter_plugin.py +66 -0
  37. microcoreos/_template/domains/devtools/plugins/tool_doc_drift_linter_plugin.py +74 -0
  38. microcoreos/_template/domains/system/plugins/event_delivery_monitor_plugin.py +53 -0
  39. microcoreos/_template/domains/system/plugins/system_events_plugin.py +132 -0
  40. microcoreos/_template/domains/system/plugins/system_events_stream_plugin.py +46 -0
  41. microcoreos/_template/domains/system/plugins/system_logs_stream_plugin.py +47 -0
  42. microcoreos/_template/domains/system/plugins/system_metrics_plugin.py +85 -0
  43. microcoreos/_template/domains/system/plugins/system_status_plugin.py +66 -0
  44. microcoreos/_template/domains/system/plugins/system_traces_plugin.py +128 -0
  45. microcoreos/_template/domains/system/plugins/system_traces_stream_plugin.py +121 -0
  46. microcoreos/_template/domains/system/plugins/tool_health_plugin.py +53 -0
  47. microcoreos/_template/extras/available_domains/chaos/plugins/blocking_boot_plugin.py +19 -0
  48. microcoreos/_template/extras/available_domains/chaos/plugins/chaos_control_plugin.py +625 -0
  49. microcoreos/_template/extras/available_domains/chaos/plugins/failing_plugin.py +27 -0
  50. microcoreos/_template/extras/available_domains/chaos/plugins/stress_plugin.py +40 -0
  51. microcoreos/_template/extras/available_domains/ping/plugins/ping_plugin.py +36 -0
  52. microcoreos/_template/extras/available_domains/scheduler/migrations/001_scheduler_one_shots.sql +8 -0
  53. microcoreos/_template/extras/available_domains/scheduler/models/scheduler_one_shot.py +15 -0
  54. microcoreos/_template/extras/available_domains/scheduler/plugins/durable_one_shots_plugin.py +125 -0
  55. microcoreos/_template/extras/available_domains/users/migrations/001_create_users.sql +12 -0
  56. microcoreos/_template/extras/available_domains/users/models/user.py +31 -0
  57. microcoreos/_template/extras/available_domains/users/plugins/create_user_plugin.py +89 -0
  58. microcoreos/_template/extras/available_domains/users/plugins/get_me_plugin.py +63 -0
  59. microcoreos/_template/extras/available_domains/users/plugins/login_plugin.py +107 -0
  60. microcoreos/_template/extras/available_domains/users/plugins/logout_plugin.py +26 -0
  61. microcoreos/_template/extras/available_tools/auth/auth_tool.py +132 -0
  62. microcoreos/_template/extras/available_tools/chaos/chaos_tool.py +32 -0
  63. microcoreos/_template/extras/available_tools/kafka/kafka_driver.py +452 -0
  64. microcoreos/_template/extras/available_tools/postgresql/postgresql_tool.py +882 -0
  65. microcoreos/_template/extras/available_tools/rabbitmq/rabbitmq_driver.py +303 -0
  66. microcoreos/_template/extras/available_tools/redis_state/redis_state_tool.py +253 -0
  67. microcoreos/_template/extras/available_tools/s3/__init__.py +0 -0
  68. microcoreos/_template/extras/available_tools/s3/s3_tool.py +426 -0
  69. microcoreos/_template/extras/available_tools/scheduler/scheduler_tool.py +328 -0
  70. microcoreos/_template/main.py +9 -0
  71. microcoreos/_template/plans/PILOT.md +99 -0
  72. microcoreos/_template/plans/README.md +236 -0
  73. microcoreos/_template/plans/active_plan.md +25 -0
  74. microcoreos/_template/plans/active_plan.yaml +43 -0
  75. microcoreos/_template/tools/config/config_tool.py +110 -0
  76. microcoreos/_template/tools/context/authoring_guide.md +305 -0
  77. microcoreos/_template/tools/context/context_tool.py +161 -0
  78. microcoreos/_template/tools/context/renderers.py +118 -0
  79. microcoreos/_template/tools/context/scanners.py +267 -0
  80. microcoreos/_template/tools/event_bus/drivers.py +108 -0
  81. microcoreos/_template/tools/event_bus/envelope.py +55 -0
  82. microcoreos/_template/tools/event_bus/event_bus_tool.py +506 -0
  83. microcoreos/_template/tools/event_bus/redis_streams_driver.py +300 -0
  84. microcoreos/_template/tools/event_bus/sqlite_driver.py +316 -0
  85. microcoreos/_template/tools/http_server/context.py +120 -0
  86. microcoreos/_template/tools/http_server/http_server_tool.py +558 -0
  87. microcoreos/_template/tools/http_server/pipeline.py +267 -0
  88. microcoreos/_template/tools/logger/logger_tool.py +96 -0
  89. microcoreos/_template/tools/sqlite/errors.py +152 -0
  90. microcoreos/_template/tools/sqlite/migrations.py +140 -0
  91. microcoreos/_template/tools/sqlite/sqlite_tool.py +591 -0
  92. microcoreos/_template/tools/sqlite/transaction.py +189 -0
  93. microcoreos/_template/tools/state/state_tool.py +173 -0
  94. microcoreos/_template/tools/system/registry_tool.py +90 -0
  95. microcoreos/_template/tools/telemetry/__init__.py +0 -0
  96. microcoreos/_template/tools/telemetry/telemetry_tool.py +143 -0
  97. microcoreos/base_plugin.py +20 -0
  98. microcoreos/base_tool.py +48 -0
  99. microcoreos/catalog.py +268 -0
  100. microcoreos/cli.py +206 -0
  101. microcoreos/container.py +222 -0
  102. microcoreos/context.py +6 -0
  103. microcoreos/kernel.py +223 -0
  104. microcoreos/project_readme.md +107 -0
  105. microcoreos/registry.py +53 -0
  106. microcoreos/scaffold.py +218 -0
  107. microcoreos/upgrade.py +547 -0
  108. microcoreos-0.1.0.dist-info/METADATA +366 -0
  109. microcoreos-0.1.0.dist-info/RECORD +112 -0
  110. microcoreos-0.1.0.dist-info/WHEEL +4 -0
  111. microcoreos-0.1.0.dist-info/entry_points.txt +2 -0
  112. microcoreos-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -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
+