# Rule Registry

This is the canonical registry of Astrale-specific domain-policy candidates. It intentionally keeps
both executable candidates and ideas that should remain guidance, so rejected automation ideas are not
rediscovered later. The executable-status table records what is currently enforced; every other entry
remains `proposed` regardless of its automation disposition.

The canonical identifier is `astrale/<slug>`. Each section heading supplies the `family`; owner, scope,
automation disposition, intended default, and fix class are explicit in every row. `owner` identifies
the prospective evidence owner; it does not imply an implementation commitment.

Automation dispositions are:

- `implement`: deterministic, valuable, and expected to have a low false-positive rate under the
  current contract.
- `research`: potentially valuable, but blocked on a reliable detection contract or measured
  false-positive evidence. It remains off and is not on the implementation roadmap.
- `guidance`: valuable architectural advice that should not be a static diagnostic in its current
  formulation. It remains here as a decision record.

Generic JavaScript, TypeScript, React, Vitest, accessibility, and import rules remain owned by Oxlint
and `@astrale-os/ox`; they are not duplicated here. Behavioral and live properties belong in tests or
release checks rather than this static-rule registry.

## Executable status

| Rule | Owner | Status |
| --- | --- | --- |
| `astrale/thin-schema-composition-root` | `analyzer` | enforced |
| `astrale/one-remote-definition-per-file` | `analyzer` | enforced |
| `astrale/core-is-pure` | `analyzer` | enforced |
| `astrale/core-has-no-async` | `oxlint` | enforced |
| `astrale/no-nested-steps` | `oxlint` | enforced |
| `astrale/no-read-inside-mutate` | `oxlint` | enforced |

An `implement` disposition records that a policy passed the automation-admission judgment; it is not a
claim that its SDK or analyzer implementation exists.

## Structure

| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
| --- | --- | --- | --- | --- | --- | --- |
| `thin-schema-composition-root` | `analyzer` | `project` | `implement` | error | none | `schema/index.ts` may import, re-export, assemble `defineSchema`, and compile `D`; schema member constructors such as `nodeClass`, `edgeClass`, `nodeInterface`, `edgeInterface`, `fn`, and `func` belong in owned modules. |
| `thin-domain-composition-root` | `analyzer` | `project` | `research` | off | none | The domain root may assemble schema, methods, functions, deps, views, manifest, and install metadata but must not implement their behavior. |
| `bounded-context-layer-alignment` | `analyzer` | `project` | `guidance` | off | none | Semantic contexts discovered under schema have their owned runtime/core artifacts under the same context name; composition files are excluded. |
| `one-remote-definition-per-file` | `analyzer` | `file` | `implement` | error | none | A source file may contain at most one Astrale callable definition through `remoteMethod` or `defineRemoteFunction`; ordinary functions, callbacks, validators, and local helpers do not count. |
| `explicit-callable-filename` | `analyzer` | `file` | `research` | off | none | A callable file uses a verb-object name such as `assign-issue.ts`, not a bare verb such as `assign.ts`; detection compares registered vocabulary with the filename. |
| `no-technical-context-bucket` | `analyzer` | `project` | `guidance` | off | none | Domain layers should avoid dumping-ground paths such as `schema/functions`, `edges.ts`, `contracts.ts`, `runtime/handlers.ts`, or generic `helpers`/`utils` containers. |
| `context-owned-errors` | `analyzer` | `project` | `guidance` | off | none | Context-specific errors should live in `core/<context>/errors.ts`; only cross-context errors should live in `core/errors.ts`. Runtime may throw but should not define them. |
| `context-owned-paths` | `analyzer` | `project` | `guidance` | off | none | Exported graph path constants and constructors should live in `core/<context>/paths.ts` under the bounded context that owns the resource, not in runtime handlers. |
| `simulation-data-isolated` | `analyzer` | `project` | `research` | off | none | Fake data, samples, capture kernels, and demo scenarios live under top-level `simulation/`, and production layers must not import them. |
| `tests-follow-layer-ownership` | `analyzer` | `project` | `guidance` | off | none | Focused tests should live in the owning layer's `__tests__`; cross-context business workflows should live under `simulation/`. |
| `explicit-default-props-name` | `analyzer` | `file` | `guidance` | off | none | Default-property builders should live in `properties.defaults.ts` and use an explicit `default<Entity>Props` name such as `defaultCommentProps`. |
| `semantic-constant-filename` | `analyzer` | `file` | `guidance` | off | none | Exported vocabulary constants should live in a semantic file such as `tags/colors.constants.ts`, not a context-wide `constants.ts` dumping ground. |

## Purity

| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
| --- | --- | --- | --- | --- | --- | --- |
| `core-is-pure` | `analyzer` | `project` | `implement` | error | none | Production files under `core/` must not reach known effectful Astrale/Node packages or the project's `runtime`, `functions`, `integrations`, `client`, or `simulation` layers; detection follows resolved local imports, including package-import aliases, and excludes tests. Unknown third-party packages are not guessed to be effectful. |
| `core-has-no-async` | `oxlint` | `file` | `implement` | error | none | Production files under `core/` must not declare async functions or async generators, or use top-level `await`/`for await`; tests are excluded. |
| `integrations-enter-through-deps` | `analyzer` | `project` | `research` | off | none | Runtime handlers receive external clients through the domain dependency container and must not construct provider clients or import configured singletons directly. |
| `deps-construction-has-no-effects` | `oxlint` | `file` | `research` | off | none | The dependency factory may construct clients from configuration but must not await, fetch, call the kernel, read time/randomness, or perform request-specific I/O. |

## Schema

| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
| --- | --- | --- | --- | --- | --- | --- |
| `path-schema-for-addresses` | `analyzer` | `node` | `research` | off | none | Schema parameters representing graph addresses use `pathSchema()` or `absolutePathSchema()`, not `z.string()`; name-only guesses cannot emit this diagnostic. |
| `pathlike-at-input-boundaries` | `sdk` | `node` | `implement` | error | none | Public helpers that accept graph addresses use `Path` or `PathLike`; SDK-owned graph APIs must not publish `ref: string`-style contracts. |
| `schema-derived-handler-types` | `oxlint` | `file` | `research` | off | none | Callable implementations use `remoteMethod`/`defineRemoteFunction` schema-derived types and must not redeclare parallel params, result, self, or context interfaces. |
| `exact-runtime-callable-map` | `sdk` | `project` | `implement` | error | none | Methods and standalone-function maps contain every and only the callables declared by the compiled schema. |
| `every-member-wired` | `sdk` | `project` | `implement` | error | none | Every declared method, standalone function, view, post-install function, and referenced dependency is present in the compiled install/runtime definition. |
| `client-imports-schema-package` | `analyzer` | `project` | `research` | off | none | A separate frontend imports the published schema package and client surfaces; it must not reach through worker source for schema, core, runtime, deps, functions, or integrations. |

## Authorization

| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
| --- | --- | --- | --- | --- | --- | --- |
| `authorize-is-explicit` | `sdk` | `node` | `implement` | error | suggestion | Every method and standalone function declares `authorize`; an intentionally open callable uses an explicit empty hook. |
| `caller-gate-names-principal` | `analyzer` | `file` | `research` | off | none | A caller-sensitive authorization check explicitly passes `auth.principal` as `who`; success through the composed handler kernel is not caller proof. |

## Durability

| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
| --- | --- | --- | --- | --- | --- | --- |
| `effects-run-in-step` | `analyzer` | `project` | `research` | off | none | Every call path from a callable to a known kernel operation, dependency I/O, clock read, randomness source, or registered effect crosses exactly one `step.run` boundary. |
| `stable-step-identifier` | `analyzer` | `project` | `research` | off | none | Every executed `step.run` identifier resolves to stable kebab-case vocabulary; helper-owned steps may receive an identifier from callers only when every reachable call site supplies stable evidence. |
| `no-nested-steps` | `oxlint` | `node` | `implement` | error | none | A `step.run` callback must not directly invoke another `step.run`; interprocedural double wrapping is covered by `astrale/effects-run-in-step`. |
| `serializable-step-result` | `sdk` | `node` | `implement` | error | none | Values crossing a step boundary conform to the SDK's durable JSON-value contract; clients, functions, symbols, bigint, paths, bound nodes, and cyclic instances are rejected. |

## Graph

| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
| --- | --- | --- | --- | --- | --- | --- |
| `prefer-schema-bound-client` | `oxlint` | `file` | `research` | off | none | When a handler kernel is schema-bound, ordinary class reads, methods, links, and properties use the typed surface; raw calls require an unsupported or dynamic capability. |
| `raw-path-only-at-boundary` | `analyzer` | `node` | `research` | off | none | `.raw` is used only for serialization, wire protocols, persistence keys, or APIs requiring strings, not for ordinary equality or typed graph calls. |
| `no-redundant-raw-of` | `analyzer` | `node` | `implement` | error | suggestion | `rawOf()` receives a genuine `PathLike` boundary; values already typed as `Path` use `.raw`, and equality uses `.equals()`. |
| `no-read-inside-mutate` | `oxlint` | `node` | `implement` | error | none | A `kernel.mutate` builder callback must not be async or directly invoke a known graph-read method on the same kernel receiver; only patch construction belongs inside it. Arbitrary external I/O is not guessed by this file-local rule. |
| `do-not-reread-hydrated-node` | `analyzer` | `file` | `research` | off | none | Nodes returned by `children`, `neighbors`, `query`, or a bound read must not be immediately fetched again without an explicit fresh-snapshot reason. |
| `pagination-intent-is-explicit` | `analyzer` | `file` | `research` | off | none | Code consuming a paged result explicitly chooses first-page access, draining, continuation, cursor persistence, or async iteration. |
| `no-raw-database-query` | `oxlint` | `file` | `implement` | error | none | Domain code must not issue Cypher or adapter-specific database queries; it uses the typed graph API or supported query AST. |

## Errors

| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
| --- | --- | --- | --- | --- | --- | --- |
| `known-failures-use-domain-errors` | `analyzer` | `file` | `guidance` | off | none | Known not-found, invalid-transition, conflict, occupied-path, and relation failures should throw context-owned domain errors rather than generic `Error`. |
| `domain-error-has-stable-code` | `sdk` | `node` | `implement` | error | none | Every exported domain error exposes a non-empty stable machine-readable code through the SDK error contract; the linter does not infer which context fields the business requires. |
| `unknown-errors-propagate` | `analyzer` | `node` | `research` | off | none | A catch block must not translate every unknown kernel/provider failure into one generic domain error; only recognized failures may be mapped. |

## Client

| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
| --- | --- | --- | --- | --- | --- | --- |
| `use-canonical-shell-bridge` | `analyzer` | `project` | `research` | off | none | A domain client uses `@astrale-os/shell-react` and must not contain a local copy of session, intent-router, graph-memory, or Shell handshake infrastructure. |

## Automation rationale

### Research before implementation

| Slug | What must be proven first |
| --- | --- |
| `thin-domain-composition-root` | Syntax alone does not reliably distinguish composition from small domain behavior; the accepted contract needs explicit allowed and forbidden declarations. |
| `explicit-callable-filename` | Callable identity must come from a resolved registration symbol rather than filename or verb guessing. |
| `simulation-data-isolated` | A narrow production-imports-simulation rule is detectable, but identifying fake data by names or contents is not; the policy should be split before acceptance. |
| `integrations-enter-through-deps` | Detection needs SDK-owned dependency and integration identities; arbitrary client imports and factories have legitimate uses. |
| `deps-construction-has-no-effects` | The linter must identify the dependency factory and known effect calls precisely instead of banning common syntax in similarly named files. |
| `path-schema-for-addresses` | Graph-address intent must be visible in compiled schema metadata; parameter names such as `ref` or `path` are insufficient evidence. |
| `schema-derived-handler-types` | The analyzer must distinguish duplicate callable contracts from legitimate local helper types. |
| `client-imports-schema-package` | Package and deployment boundaries must be discovered reliably before source imports can be judged as worker reach-through; relative imports from a client into backend `core/` are part of the forbidden boundary. |
| `caller-gate-names-principal` | The rule needs exact authorization-call identity and proof that the operation is caller-sensitive. |
| `effects-run-in-step` | Reliable enforcement requires interprocedural effect summaries, known effect identities, and explicit step ownership across helpers. |
| `stable-step-identifier` | File-local literal enforcement rejects valid helper-owned steps such as `readIssue(..., 'read-created-issue')`; project analysis or an SDK-branded identifier is required to prove every call site. |
| `prefer-schema-bound-client` | A warning is valid only when the analyzer can prove the typed client offers the equivalent capability. |
| `raw-path-only-at-boundary` | Type and callee-signature evidence must distinguish serialization boundaries from ordinary typed graph work. |
| `do-not-reread-hydrated-node` | Dataflow must prove that the same hydrated value is reread and provide a machine-visible way to request a fresh snapshot. |
| `pagination-intent-is-explicit` | The SDK must expose paged-result intent in types or methods that the analyzer can follow without guessing control flow. |
| `unknown-errors-propagate` | The analyzer must recognize typed error guards and mappings before it can distinguish selective translation from catch-all masking. |
| `use-canonical-shell-bridge` | Import requirements are detectable, but semantic copies of Shell infrastructure are not; the rule needs a narrower import-boundary contract. |

### Keep as guidance in the current formulation

| Slug | Why static enforcement would be misleading |
| --- | --- |
| `bounded-context-layer-alignment` | Context ownership is business semantics, and valid layers may intentionally use different projections or context boundaries. |
| `no-technical-context-bucket` | Names such as `helpers`, `contracts`, or `edges` can indicate poor design but are not violations without understanding their contents and consumers. |
| `context-owned-errors` | A machine cannot reliably decide whether an error is context-specific or genuinely shared. |
| `context-owned-paths` | Resource ownership and the correct bounded context are architectural decisions; filenames cannot prove them. A narrower ban on runtime-local path declarations may be proposed separately. |
| `tests-follow-layer-ownership` | The distinction between a focused layer test and a business scenario depends on test intent, not directory names alone. |
| `explicit-default-props-name` | The exact builder name and filename are conventions whose value rarely justifies false positives across valid construction patterns. |
| `semantic-constant-filename` | Whether a constants file is a dumping ground depends on cohesion and consumers, not the word `constants`. |
| `known-failures-use-domain-errors` | Identifying a known business failure requires domain semantics; a generic `Error` ban would also reject legitimate infrastructure and assertion failures. |
