> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blockx.chaintable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Design principles and code conventions

> How the three principles Sans-IO, Scoped Resource Context, and Occam's Razor become checkable code constraints, and what each of the core / adapters / app / api layers is allowed to do.

The BlockX repository uses three design principles to constrain both specs and code. They are not abstract slogans: the repository's `AGENTS.md` and `docs/specs/code-style.md` expand them into concrete module boundaries, dependency rules, error semantics, and time semantics. This page is a reader's guide to those two files, so that you know what will get sent back in review before you write any code.

## Three principles

<AccordionGroup>
  <Accordion title="Sans-IO: the default rule">
    A new subsystem first defines a pure in-memory core that describes its behavior with explicit events and commands, and only then wraps adapters around it. The core only maintains the state machine, idempotency decisions, phase advancement, and result convergence; RPC, UDS, etcd, databases, clocks, logging, and metrics all stay in adapters.

    `WorkerCore` (`internal/worker/core/`), `CoordinatorCore` (`internal/coordinator/core/`), and `DispatcherCore` (`internal/worker/core/dispatcher.go`) in the repository all take this shape. They are code boundaries, not new processes or roles.
  </Accordion>

  <Accordion title="Scoped Resource Context: the rule for resource-governance subsystems">
    Modules such as IO access, which revolve around who owns a resource and when it is released, are not forced into an event/command stream. They explicitly model a Worker-level shared scope and task-level local scopes, and bind quota, cache, singleflight, deadline, and cleanup to the scope lifecycle. Scope exit is a structural cleanup boundary; it does not rely on "sending a release command later".

    `internal/io/` takes this shape; its entry points are synchronous interfaces such as `WorkerIOScope` / `TaskIOScope`.
  </Accordion>

  <Accordion title="Occam's Razor: constrains both spec and code">
    Do not multiply entities beyond necessity. Before adding a struct, interface, error code, queue, state, or service, first ask whether an existing concept can be reused or simplified. If you cannot clearly explain why the existing `module / core / adapters / scope` boundaries cannot accommodate a new entity, do not add it yet.
  </Accordion>
</AccordionGroup>

The rule of thumb for choosing between the principles comes from `AGENTS.md`: if a behavior is lifecycle-heavy (state advancement of slots, tasks, and calls), express it with explicit states, events, and commands; if it is resource-governance-heavy (connection pools, quotas, caches), express it with explicit scope ownership, acquisition, and cleanup rules; if neither framework yields a small, enumerable set of rules, the design is still too vague.

## Layering and dependency rules

| Directory                       | Allowed                                                                                                                                    | Forbidden                                                                                                                                                                                                       |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cmd/<bin>/`                    | Assemble `app.Profile` and call `app.Run`                                                                                                  | Domain decisions, admission policy, protocol semantics branching                                                                                                                                                |
| `internal/<module>/core/`       | State machines, idempotency, phase advancement, result convergence, pure time decisions                                                    | Depending on gRPC / etcd client / DB client / logging implementations / metrics implementations; reading the current time, generating random values, or starting goroutines on its own; using `context.Context` |
| `internal/<module>/adapters/`   | Transport, storage, clocks, logging, metrics, concurrency; translating external input into core events and core commands into side effects | Duplicating the core's state decisions; modifying core-internal objects directly across layers                                                                                                                  |
| `internal/<module>/app/`        | Wiring core and adapters together, loading configuration, defining shutdown order                                                          | Domain rules                                                                                                                                                                                                    |
| `internal/io/` (scope-oriented) | Using `context`, `defer`, semaphores, and synchronous singleflight within a scope                                                          | Letting the scheduling layer, hook layer, and backend adapters each maintain their own shadow counters                                                                                                          |
| `api/`                          | Protocol structures shared across modules, message envelopes, field conventions, side-effect-free validation and encoding helpers          | Transport handlers, storage models, domain state machines                                                                                                                                                       |

Do not introduce catch-all directories such as `pkg/utils`, `common`, or `shared` without clear boundaries. The existing `internal/common/` in the repository only holds a few small packages with a clear purpose — `argspool`, `clientid`, `types`, and `unsafebytes` — and is not a general-purpose junk drawer.

## Interaction pattern between adapters and the core

The recommended pattern is the one-way "input event → core decision → adapter executes commands", rather than having the core hold a set of reverse callbacks. The sketch from `docs/specs/code-style.md`:

```go theme={null}
decision := workerCore.HandleRequestTaskSlot(core.RequestTaskSlotInput{
    TaskID: taskID,
    TTL:    ttl,
    Now:    now,
})

if decision.Err != nil {
    return writeRPCError(decision.Err)
}

for _, cmd := range decision.Commands {
    switch c := cmd.(type) {
    case core.ReturnSlotReservation:
        return writeRPCResult(c)
    case core.ReleaseSlot:
        slotStore.Release(c.SlotID)
    }
}
```

When a timer, Executor, Plugin, or IO callback completes, the adapter wraps the result back into a core event and feeds it in again. This keeps the core single-threaded, replayable, and testable in a table-driven way.

For the exact event and command names, refer to the individual component pages: [Worker](/en/components/worker), [Coordinator](/en/components/coordinator), and [Call execution subsystem](/en/components/call-execution).

## Using interfaces

* Define interfaces on the side that actually consumes them.
* When adapters call the core, there is usually no need to abstract an interface for the core; depend directly on concrete types such as `WorkerCore` and `CoordinatorCore`.
* Only when the core genuinely needs a synchronous port, and that port can stay pure, narrow, and replaceable, define a minimal interface in `core/` for adapters to implement.
* Do not extract interfaces ahead of time for "possible future extension" before there is a second implementation or a clear need for a test double.

## Naming and modeling

* Package names are short, stable, and domain-specific; prefer nouns.
* Use clear names for exported identifiers; avoid weak suffixes such as `Manager`, `Util`, `Data`, and `Info`.
* States, events, and commands reflect domain semantics directly, for example `TaskPhaseFinished`, `PrepareTaskActivation`, and `ReleaseSlot`.
* Prefer explicit structs and named enums; do not overuse `map[string]any`, `any`, or scattered string literals.
* When you need a combination of several boolean fields to understand an object's state, switch to an explicit state enum.
* Use `NewXxx` only when it establishes invariants; a literal wrapper does not need a forced constructor.

## Error and result expression

* First distinguish "admission / protocol errors" from "terminal results after execution has started"; do not mix them into one set of error returns. On the Worker, the boundary is whether the slot has been activated from `ALLOCATED` to `RUNNING`: errors before activation are submission / admission-layer errors, and failures after activation all go into `TaskResult`.
* Stable semantics rely on error codes, enums, or named result types, not on error strings.
* When an external protocol already has stable error code conventions, the protocol fields are authoritative; do not invent parallel semantics inside adapters.
* In a Sans-IO core, model expected business outcomes as explicit results; do not overuse `error`.
* Boundaries such as `TaskResult`, `InvalidSlot`, `NoSlot`, and `Unavailable` must be consistent with `docs/specs/architecture.md` and `docs/specs/worker.md`.

## Time semantics

* Model time semantics centrally; do not scatter them across handlers, Plugins, and drivers, each making its own judgment.
* On the Worker side, slot TTL, task deadline, and the result retention window are concentrated in the Worker module and its timer adapter, with `docs/specs/worker.md` as the authority.
* On the Coordinator side, view freshness, candidate retries, and placement-related time semantics are concentrated in the Coordinator module, with `docs/specs/task-resource-coordinator.md` as the authority.
* In scope-oriented modules, the end of a lifecycle is guaranteed primarily by scope `Close()`, context cancellation, and structural cleanup, supplemented by defenses against late results.
* The creation, exit, and reclamation paths of goroutines must be clear; do not leave lifecycle management to process exit as a fallback.

## Keeping tests and docs in sync

* `core/` tests focus on state transitions, idempotency, TTL, deadline, and failure convergence; prefer table-driven tests.
* Tests for scope-oriented modules focus on quota acquisition / release, scope closing, dropping late results, singleflight behavior, and the absence of resource leaks.
* Adapter tests focus on protocol mapping, error translation, and command execution orchestration; do not repeat the state combinations already covered by the core.
* When you change protocol or lifecycle semantics, update the tests and the corresponding spec in the same change.

See [Testing](/en/development/testing) for the exact commands and test layering.

## Related docs

* `AGENTS.md` in the blockx repository: repository-level principles, directory skeleton, test baseline, commit / PR rules.
* `docs/specs/code-style.md` in the blockx repository: the source this page expands on, including the `api/` boundary and the concrete constraints of Sans-IO and Scoped Resource Context.
* `docs/specs/io-subsystem.md` in the blockx repository: the complete definition of Scoped Resource Context.
* [Repository layout and code layering](/en/architecture/repository-layout)
* [Contributing](/en/development/contributing)
