@astrale-os/sdk 0.4.12 → 0.4.13
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.
- package/dist/cli/run.d.ts +8 -2
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +51 -1
- package/dist/cli/run.js.map +1 -1
- package/dist/define/remote-function.d.ts +3 -1
- package/dist/define/remote-function.d.ts.map +1 -1
- package/dist/define/remote-function.js.map +1 -1
- package/dist/dispatch/dispatcher.d.ts +10 -20
- package/dist/dispatch/dispatcher.d.ts.map +1 -1
- package/dist/dispatch/dispatcher.js +55 -54
- package/dist/dispatch/dispatcher.js.map +1 -1
- package/dist/dispatch/execute.d.ts +2 -1
- package/dist/dispatch/execute.d.ts.map +1 -1
- package/dist/dispatch/execute.js +1 -0
- package/dist/dispatch/execute.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/linter/analyze.d.ts +6 -0
- package/dist/linter/analyze.d.ts.map +1 -0
- package/dist/linter/analyze.js +12 -0
- package/dist/linter/analyze.js.map +1 -0
- package/dist/linter/diagnostic.d.ts +32 -0
- package/dist/linter/diagnostic.d.ts.map +1 -0
- package/dist/linter/diagnostic.js +35 -0
- package/dist/linter/diagnostic.js.map +1 -0
- package/dist/linter/index.d.ts +10 -0
- package/dist/linter/index.d.ts.map +1 -0
- package/dist/linter/index.js +6 -0
- package/dist/linter/index.js.map +1 -0
- package/dist/linter/lint.d.ts +8 -0
- package/dist/linter/lint.d.ts.map +1 -0
- package/dist/linter/lint.js +26 -0
- package/dist/linter/lint.js.map +1 -0
- package/dist/linter/oxlint/config.d.ts +14 -0
- package/dist/linter/oxlint/config.d.ts.map +1 -0
- package/dist/linter/oxlint/config.js +18 -0
- package/dist/linter/oxlint/config.js.map +1 -0
- package/dist/linter/oxlint/plugin.d.ts +13 -0
- package/dist/linter/oxlint/plugin.d.ts.map +1 -0
- package/dist/linter/oxlint/plugin.js +14 -0
- package/dist/linter/oxlint/plugin.js.map +1 -0
- package/dist/linter/oxlint/run.d.ts +7 -0
- package/dist/linter/oxlint/run.d.ts.map +1 -0
- package/dist/linter/oxlint/run.js +122 -0
- package/dist/linter/oxlint/run.js.map +1 -0
- package/dist/linter/preflight.d.ts +2 -0
- package/dist/linter/preflight.d.ts.map +1 -0
- package/dist/linter/preflight.js +162 -0
- package/dist/linter/preflight.js.map +1 -0
- package/dist/linter/project.d.ts +12 -0
- package/dist/linter/project.d.ts.map +1 -0
- package/dist/linter/project.js +88 -0
- package/dist/linter/project.js.map +1 -0
- package/dist/linter/report.d.ts +4 -0
- package/dist/linter/report.d.ts.map +1 -0
- package/dist/linter/report.js +22 -0
- package/dist/linter/report.js.map +1 -0
- package/dist/linter/rules/catalog.d.ts +56 -0
- package/dist/linter/rules/catalog.d.ts.map +1 -0
- package/dist/linter/rules/catalog.js +53 -0
- package/dist/linter/rules/catalog.js.map +1 -0
- package/dist/linter/rules/core-has-no-async.d.ts +3 -0
- package/dist/linter/rules/core-has-no-async.d.ts.map +1 -0
- package/dist/linter/rules/core-has-no-async.js +43 -0
- package/dist/linter/rules/core-has-no-async.js.map +1 -0
- package/dist/linter/rules/core-is-pure.d.ts +3 -0
- package/dist/linter/rules/core-is-pure.d.ts.map +1 -0
- package/dist/linter/rules/core-is-pure.js +74 -0
- package/dist/linter/rules/core-is-pure.js.map +1 -0
- package/dist/linter/rules/no-nested-steps.d.ts +3 -0
- package/dist/linter/rules/no-nested-steps.d.ts.map +1 -0
- package/dist/linter/rules/no-nested-steps.js +32 -0
- package/dist/linter/rules/no-nested-steps.js.map +1 -0
- package/dist/linter/rules/no-read-inside-mutate.d.ts +3 -0
- package/dist/linter/rules/no-read-inside-mutate.d.ts.map +1 -0
- package/dist/linter/rules/no-read-inside-mutate.js +64 -0
- package/dist/linter/rules/no-read-inside-mutate.js.map +1 -0
- package/dist/linter/rules/one-remote-definition-per-file.d.ts +3 -0
- package/dist/linter/rules/one-remote-definition-per-file.d.ts.map +1 -0
- package/dist/linter/rules/one-remote-definition-per-file.js +23 -0
- package/dist/linter/rules/one-remote-definition-per-file.js.map +1 -0
- package/dist/linter/rules/oxlint-ast.d.ts +30 -0
- package/dist/linter/rules/oxlint-ast.d.ts.map +1 -0
- package/dist/linter/rules/oxlint-ast.js +104 -0
- package/dist/linter/rules/oxlint-ast.js.map +1 -0
- package/dist/linter/rules/oxlint-rule.d.ts +45 -0
- package/dist/linter/rules/oxlint-rule.d.ts.map +1 -0
- package/dist/linter/rules/oxlint-rule.js +2 -0
- package/dist/linter/rules/oxlint-rule.js.map +1 -0
- package/dist/linter/rules/project-rule.d.ts +10 -0
- package/dist/linter/rules/project-rule.d.ts.map +1 -0
- package/dist/linter/rules/project-rule.js +24 -0
- package/dist/linter/rules/project-rule.js.map +1 -0
- package/dist/linter/rules/thin-schema-composition-root.d.ts +3 -0
- package/dist/linter/rules/thin-schema-composition-root.d.ts.map +1 -0
- package/dist/linter/rules/thin-schema-composition-root.js +23 -0
- package/dist/linter/rules/thin-schema-composition-root.js.map +1 -0
- package/dist/linter/source.d.ts +24 -0
- package/dist/linter/source.d.ts.map +1 -0
- package/dist/linter/source.js +279 -0
- package/dist/linter/source.js.map +1 -0
- package/dist/linter/suppression.d.ts +3 -0
- package/dist/linter/suppression.d.ts.map +1 -0
- package/dist/linter/suppression.js +11 -0
- package/dist/linter/suppression.js.map +1 -0
- package/dist/method/context.d.ts +3 -1
- package/dist/method/context.d.ts.map +1 -1
- package/dist/server/auxiliary-routes.d.ts.map +1 -1
- package/dist/server/auxiliary-routes.js +4 -4
- package/dist/server/auxiliary-routes.js.map +1 -1
- package/dist/server/create.js +1 -1
- package/dist/server/create.js.map +1 -1
- package/dist/server/start.d.ts.map +1 -1
- package/dist/server/start.js.map +1 -1
- package/package.json +27 -7
- package/src/cli/run.ts +53 -2
- package/src/define/remote-function.ts +3 -1
- package/src/dispatch/dispatcher.ts +62 -69
- package/src/dispatch/execute.ts +3 -1
- package/src/index.ts +2 -2
- package/src/linter/analyze.ts +17 -0
- package/src/linter/diagnostic.ts +68 -0
- package/src/linter/docs/DIAGNOSTICS.md +47 -0
- package/src/linter/docs/JUDGMENT.md +81 -0
- package/src/linter/docs/PRINCIPLES.md +97 -0
- package/src/linter/docs/README.md +38 -0
- package/src/linter/docs/RULE-AUTHORING.md +144 -0
- package/src/linter/docs/RULES.md +153 -0
- package/src/linter/index.ts +14 -0
- package/src/linter/lint.ts +32 -0
- package/src/linter/oxlint/astrale-ox.d.ts +5 -0
- package/src/linter/oxlint/config.ts +19 -0
- package/src/linter/oxlint/plugin.js +16 -0
- package/src/linter/oxlint/run.ts +162 -0
- package/src/linter/preflight.ts +181 -0
- package/src/linter/project.ts +108 -0
- package/src/linter/report.ts +25 -0
- package/src/linter/rules/catalog.ts +67 -0
- package/src/linter/rules/core-has-no-async.js +43 -0
- package/src/linter/rules/core-is-pure.ts +103 -0
- package/src/linter/rules/no-nested-steps.js +37 -0
- package/src/linter/rules/no-read-inside-mutate.js +72 -0
- package/src/linter/rules/one-remote-definition-per-file.ts +29 -0
- package/src/linter/rules/oxlint-ast.js +110 -0
- package/src/linter/rules/oxlint-rule.ts +35 -0
- package/src/linter/rules/project-rule.ts +37 -0
- package/src/linter/rules/thin-schema-composition-root.ts +26 -0
- package/src/linter/source.ts +323 -0
- package/src/linter/suppression.ts +11 -0
- package/src/method/context.ts +3 -1
- package/src/server/auxiliary-routes.ts +9 -4
- package/src/server/create.ts +1 -1
- package/src/server/start.ts +2 -1
- package/dist/defer/index.d.ts +0 -24
- package/dist/defer/index.d.ts.map +0 -1
- package/dist/defer/index.js +0 -42
- package/dist/defer/index.js.map +0 -1
- package/src/defer/index.ts +0 -64
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { LintDiagnostic } from './diagnostic.js'
|
|
2
|
+
import type { DomainProject } from './project.js'
|
|
3
|
+
import type { AnalyzerRule } from './rules/project-rule.js'
|
|
4
|
+
|
|
5
|
+
import { coreIsPure } from './rules/core-is-pure.js'
|
|
6
|
+
import { oneRemoteDefinitionPerFile } from './rules/one-remote-definition-per-file.js'
|
|
7
|
+
import { thinSchemaCompositionRoot } from './rules/thin-schema-composition-root.js'
|
|
8
|
+
|
|
9
|
+
export const analyzerRules: readonly AnalyzerRule[] = [
|
|
10
|
+
thinSchemaCompositionRoot,
|
|
11
|
+
oneRemoteDefinitionPerFile,
|
|
12
|
+
coreIsPure,
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
export function analyzeProject(project: DomainProject): LintDiagnostic[] {
|
|
16
|
+
return analyzerRules.flatMap((rule) => rule.analyze(project))
|
|
17
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { RuleOwner } from './rules/catalog.js'
|
|
2
|
+
|
|
3
|
+
export type DiagnosticSeverity = 'error' | 'warning'
|
|
4
|
+
|
|
5
|
+
export type DiagnosticLocation = {
|
|
6
|
+
path: string
|
|
7
|
+
line: number
|
|
8
|
+
column: number
|
|
9
|
+
offset?: number
|
|
10
|
+
length?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type LintDiagnostic = {
|
|
14
|
+
id: string
|
|
15
|
+
severity: DiagnosticSeverity
|
|
16
|
+
message: string
|
|
17
|
+
help?: string
|
|
18
|
+
url?: string
|
|
19
|
+
owner: RuleOwner | 'generic'
|
|
20
|
+
location?: DiagnosticLocation
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type LintResult = {
|
|
24
|
+
root: string
|
|
25
|
+
diagnostics: LintDiagnostic[]
|
|
26
|
+
files: number
|
|
27
|
+
durationMs: number
|
|
28
|
+
exitCode: 0 | 1
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class LinterToolError extends Error {
|
|
32
|
+
readonly exitCode = 2
|
|
33
|
+
|
|
34
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
35
|
+
super(message, options)
|
|
36
|
+
this.name = 'LinterToolError'
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function compareDiagnostics(a: LintDiagnostic, b: LintDiagnostic): number {
|
|
41
|
+
return (
|
|
42
|
+
(a.location?.path ?? '').localeCompare(b.location?.path ?? '') ||
|
|
43
|
+
(a.location?.line ?? 0) - (b.location?.line ?? 0) ||
|
|
44
|
+
(a.location?.column ?? 0) - (b.location?.column ?? 0) ||
|
|
45
|
+
a.id.localeCompare(b.id) ||
|
|
46
|
+
a.message.localeCompare(b.message)
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function dedupeDiagnostics(diagnostics: readonly LintDiagnostic[]): LintDiagnostic[] {
|
|
51
|
+
const seen = new Set<string>()
|
|
52
|
+
const result: LintDiagnostic[] = []
|
|
53
|
+
for (const diagnostic of diagnostics) {
|
|
54
|
+
const location = diagnostic.location
|
|
55
|
+
const key = [
|
|
56
|
+
diagnostic.id,
|
|
57
|
+
location?.path ?? '',
|
|
58
|
+
location?.line ?? 0,
|
|
59
|
+
location?.column ?? 0,
|
|
60
|
+
location?.length ?? 0,
|
|
61
|
+
diagnostic.message,
|
|
62
|
+
].join(':')
|
|
63
|
+
if (seen.has(key)) continue
|
|
64
|
+
seen.add(key)
|
|
65
|
+
result.push(diagnostic)
|
|
66
|
+
}
|
|
67
|
+
return result.sort(compareDiagnostics)
|
|
68
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Diagnostics
|
|
2
|
+
|
|
3
|
+
Every Astrale diagnostic uses the engine-neutral `LintDiagnostic` model and contains:
|
|
4
|
+
|
|
5
|
+
- A stable `astrale/<slug>` identifier.
|
|
6
|
+
- `error` or `warning` severity.
|
|
7
|
+
- A message describing the violated invariant.
|
|
8
|
+
- Remediation and a documentation URL when available.
|
|
9
|
+
- A normalized source location when one exists.
|
|
10
|
+
- The policy owner: `sdk`, `oxlint`, or `analyzer`.
|
|
11
|
+
|
|
12
|
+
Messages describe domain policy rather than AST or analyzer implementation details. Engine changes must
|
|
13
|
+
not rename identifiers, suppressions, or output fields.
|
|
14
|
+
|
|
15
|
+
## Output
|
|
16
|
+
|
|
17
|
+
Stylish output is intended for people. JSON output preserves the complete `LintResult` for automation.
|
|
18
|
+
Diagnostics are sorted by path, position, identifier, and message. Duplicate reports with the same
|
|
19
|
+
identifier and source span are collapsed because dual ownership is a tooling defect.
|
|
20
|
+
|
|
21
|
+
Exit codes are part of the contract:
|
|
22
|
+
|
|
23
|
+
- `0`: no enabled error remains.
|
|
24
|
+
- `1`: policy diagnostics failed the run.
|
|
25
|
+
- `2`: configuration, dependency resolution, or an underlying tool failed.
|
|
26
|
+
|
|
27
|
+
Project preflight failures are tool errors, not suppressible policy diagnostics. Their messages must
|
|
28
|
+
name the conflicting package or tool and preserve the original cause.
|
|
29
|
+
|
|
30
|
+
## Suppressions
|
|
31
|
+
|
|
32
|
+
Astrale rules share one exact, engine-neutral suppression syntax:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// astrale-disable-next-line astrale/raw-path-only-at-boundary -- provider cache key requires a string
|
|
36
|
+
const key = node.path.raw
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
A suppression must:
|
|
40
|
+
|
|
41
|
+
- Name exact rule identifiers.
|
|
42
|
+
- Include a non-empty reason after `--`.
|
|
43
|
+
- Apply only to the following line.
|
|
44
|
+
- Continue to work if rule ownership moves between Oxlint and the analyzer.
|
|
45
|
+
|
|
46
|
+
Generic Oxlint rules continue to use Oxlint suppression syntax. Suppressions are exceptional evidence,
|
|
47
|
+
not compensation for an ambiguous or noisy rule.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Static-analysis Judgment
|
|
2
|
+
|
|
3
|
+
Not every valuable Astrale practice is a static rule. The linter must be honest about what it cannot
|
|
4
|
+
prove.
|
|
5
|
+
|
|
6
|
+
## False-positive budget
|
|
7
|
+
|
|
8
|
+
The burden of proof belongs to automation, not to the code author. If a rule cannot distinguish valid
|
|
9
|
+
code from a violation without inferring intent, its default disposition is `guidance` or `research`,
|
|
10
|
+
not `warning`.
|
|
11
|
+
|
|
12
|
+
Before implementing a rule, ask:
|
|
13
|
+
|
|
14
|
+
- Is the violation represented by syntax, resolved symbols, types, compiled schema, or an explicit
|
|
15
|
+
project contract?
|
|
16
|
+
- Can every common valid alternative be represented without a suppression?
|
|
17
|
+
- Does the diagnostic prevent a concrete failure often enough to justify its maintenance and review
|
|
18
|
+
cost?
|
|
19
|
+
- Would a narrower rule capture the mechanically decidable part without claiming the broader design
|
|
20
|
+
judgment?
|
|
21
|
+
|
|
22
|
+
The existence of a possible AST heuristic is not evidence that a rule should exist. Severity does not
|
|
23
|
+
solve ambiguity, and a local disable comment does not make systematic false positives acceptable.
|
|
24
|
+
|
|
25
|
+
## Keep in knowledge and review
|
|
26
|
+
|
|
27
|
+
The following require domain or product judgment:
|
|
28
|
+
|
|
29
|
+
- Whether bounded-context names and ownership reflect the business correctly.
|
|
30
|
+
- Which context semantically owns a cross-context edge.
|
|
31
|
+
- Whether a generic relation should instead be assignment, tagging, blocking, duplication, hierarchy,
|
|
32
|
+
or mention behavior.
|
|
33
|
+
- Whether an authorization check targets the correct resource and permission.
|
|
34
|
+
- Whether an intentionally open callable is appropriate.
|
|
35
|
+
- Whether deletion should cascade, reject, preserve, archive, or detach related data.
|
|
36
|
+
- Whether import conflict, identity, partial-failure, and idempotency semantics are adequate.
|
|
37
|
+
- Whether a set of writes forms one business invariant.
|
|
38
|
+
- Whether a query projects every fact required by the use case.
|
|
39
|
+
- Whether a fresh read is actually required after receiving hydrated data.
|
|
40
|
+
- Whether a helper legitimately owns its own durable step.
|
|
41
|
+
- Whether UI vocabulary, hierarchy, and interaction design are domain-first and high quality.
|
|
42
|
+
- Whether a feature should exist at all.
|
|
43
|
+
|
|
44
|
+
These topics may have simulations, review checklists, or evidence collectors. They must not become
|
|
45
|
+
static diagnostics based on filenames or keywords.
|
|
46
|
+
|
|
47
|
+
The registry keeps these ideas with an explicit `guidance` disposition and rationale. This is a design
|
|
48
|
+
record, not an implementation backlog. A narrower replacement may be proposed later under its own
|
|
49
|
+
detection contract.
|
|
50
|
+
|
|
51
|
+
## Heuristic promotion
|
|
52
|
+
|
|
53
|
+
A heuristic may begin as an opt-in warning when it provides useful evidence, but its message must say
|
|
54
|
+
what was observed rather than claim intent.
|
|
55
|
+
|
|
56
|
+
For example:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
Observed 101 get() calls after one children() page in scenario delete-large-issue.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
is valid evidence. The following overclaims:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
Your query architecture is wrong.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Promotion from warning to error requires repeated evidence across golden domains, a deterministic
|
|
69
|
+
contract, and documented valid exceptions.
|
|
70
|
+
|
|
71
|
+
Before a heuristic becomes even an opt-in warning, it must first move from `research` to `implement`.
|
|
72
|
+
Report-only prototypes may collect evidence, but they must not appear as ordinary project diagnostics.
|
|
73
|
+
|
|
74
|
+
## Knowledge relationship
|
|
75
|
+
|
|
76
|
+
Every accepted rule links to knowledge that explains why it exists. Knowledge entries may reference
|
|
77
|
+
the semantic identifier, but the registry is authoritative for automation disposition and lifecycle.
|
|
78
|
+
Executable owner, severity, and detection behavior apply only after an `implement` entry is accepted.
|
|
79
|
+
|
|
80
|
+
Agent skills should instruct agents to run the native project commands and interpret diagnostics. They
|
|
81
|
+
should not duplicate the entire rule catalog in prose.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Linter Principles
|
|
2
|
+
|
|
3
|
+
## Outcome
|
|
4
|
+
|
|
5
|
+
An Astrale domain should naturally compile, lint, and test toward the preferred architecture. Agents
|
|
6
|
+
and humans should not need to memorize a long style guide before receiving precise feedback.
|
|
7
|
+
|
|
8
|
+
Knowledge remains responsible for rationale, examples, and judgment. Mechanically decidable policy
|
|
9
|
+
belongs in executable tooling.
|
|
10
|
+
|
|
11
|
+
## Enforcement order
|
|
12
|
+
|
|
13
|
+
For every proposed convention, choose the earliest static layer that can enforce it correctly:
|
|
14
|
+
|
|
15
|
+
1. **SDK** uses its API, TypeScript types, schema compilation, and runtime contracts to reject invalid
|
|
16
|
+
states directly.
|
|
17
|
+
2. **Oxlint** reports file-local syntax, scope, or control-flow violations.
|
|
18
|
+
3. **Astrale analyzer** reports cross-file, import-graph, schema-aware, or project-shape violations.
|
|
19
|
+
A later static layer must not duplicate a diagnostic already owned reliably by an earlier layer. If
|
|
20
|
+
none can decide the convention without guessing, keep it in tests, review, or knowledge rather than
|
|
21
|
+
weakening the linter.
|
|
22
|
+
|
|
23
|
+
## Product principles
|
|
24
|
+
|
|
25
|
+
### One native workflow
|
|
26
|
+
|
|
27
|
+
Authors run ordinary package scripts. They do not assemble Oxlint plugins, TypeScript parser services,
|
|
28
|
+
schema inspectors, or analyzer configuration themselves.
|
|
29
|
+
|
|
30
|
+
### Prevent before reporting
|
|
31
|
+
|
|
32
|
+
If an SDK signature can require `authorize`, a linter should not be the primary enforcement mechanism.
|
|
33
|
+
If a complete schema-derived handler map can be expressed as a TypeScript type, a filesystem scanner
|
|
34
|
+
should not guess whether it is complete.
|
|
35
|
+
|
|
36
|
+
### High signal
|
|
37
|
+
|
|
38
|
+
The default decision is not to automate. A rule earns implementation only when the linter can identify
|
|
39
|
+
the prohibited state and its legitimate exceptions from machine-visible evidence. Even a useful
|
|
40
|
+
principle should remain guidance when detection depends on guessing intent, ownership, or architectural
|
|
41
|
+
quality.
|
|
42
|
+
|
|
43
|
+
An error-level rule must have a deterministic detection contract and a very low false-positive rate.
|
|
44
|
+
Warnings are held to the same honesty standard: lower severity does not make recurring false positives
|
|
45
|
+
acceptable. Suppressions are an escape hatch for exceptional code, not compensation for a vague rule.
|
|
46
|
+
|
|
47
|
+
### Stable diagnostics, replaceable engines
|
|
48
|
+
|
|
49
|
+
Accepted `astrale/<slug>` identifiers are Astrale contracts. Oxlint, a TypeScript project service, or
|
|
50
|
+
any future engine is an implementation detail. Changing engines must not rename accepted rules,
|
|
51
|
+
suppressions, or CI output.
|
|
52
|
+
|
|
53
|
+
### Strict defaults, narrow exceptions
|
|
54
|
+
|
|
55
|
+
New domains receive the strict preset. Configuration exists to describe real domain structure or a
|
|
56
|
+
documented exception, not to recreate an arbitrary lint stack. Suppressions are local, reasoned, and
|
|
57
|
+
auditable.
|
|
58
|
+
|
|
59
|
+
### Safe fixes only
|
|
60
|
+
|
|
61
|
+
`pnpm lint:fix` may apply transformations that preserve behavior and intent. Moving a declaration into
|
|
62
|
+
a guessed bounded context, inventing an authorization target, or grouping mutations automatically is
|
|
63
|
+
not a safe fix.
|
|
64
|
+
|
|
65
|
+
### Measured speed
|
|
66
|
+
|
|
67
|
+
The fast path must remain interactive. New engines or rules are admitted only with cold, warm, and
|
|
68
|
+
changed-file measurements against representative domains.
|
|
69
|
+
|
|
70
|
+
## Rule admission bar
|
|
71
|
+
|
|
72
|
+
A proposed rule may become `accepted` only when all of the following are true:
|
|
73
|
+
|
|
74
|
+
- The prohibited behavior has a concrete failure mode.
|
|
75
|
+
- The preferred behavior is stable across more than one domain.
|
|
76
|
+
- The primary owner is exactly one of `sdk`, `oxlint`, or `analyzer`.
|
|
77
|
+
- Valid and invalid examples can be stated without relying on taste.
|
|
78
|
+
- Detection does not require guessing business intent.
|
|
79
|
+
- Representative legitimate alternatives do not trigger.
|
|
80
|
+
- Every documented exception is either machine-visible or explicitly outside the rule's scope.
|
|
81
|
+
- The expected prevention value exceeds the cost of review, suppression, maintenance, and lost trust
|
|
82
|
+
from false positives.
|
|
83
|
+
- The diagnostic identifies a useful source location and remediation.
|
|
84
|
+
- Suppression semantics are defined.
|
|
85
|
+
- Runtime and maintenance cost are measured.
|
|
86
|
+
|
|
87
|
+
## Non-goals
|
|
88
|
+
|
|
89
|
+
The linter does not:
|
|
90
|
+
|
|
91
|
+
- Build a new JavaScript or TypeScript parser, formatter, or compiler.
|
|
92
|
+
- Replace `tsgo`, Oxlint, Vitest, schema compilation, or live domain verification.
|
|
93
|
+
- Infer the correct business bounded context from vocabulary alone.
|
|
94
|
+
- Prove that an authorization target expresses the intended business policy.
|
|
95
|
+
- Grade visual design quality or domain language by keyword.
|
|
96
|
+
- Promise zero migration work for arbitrary repositories that were not scaffolded with the system.
|
|
97
|
+
- Turn every recommendation into an error.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Astrale Linter
|
|
2
|
+
|
|
3
|
+
Astrale Linter is the SDK-owned authoring system that turns stable Astrale domain conventions into
|
|
4
|
+
type constraints and static diagnostics.
|
|
5
|
+
|
|
6
|
+
Domain authors use the ordinary project commands:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pnpm lint
|
|
10
|
+
pnpm lint:fix
|
|
11
|
+
pnpm typecheck
|
|
12
|
+
pnpm test
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Generated domains contain the compatible dependencies and Oxlint bridge. Authors should not need to
|
|
16
|
+
assemble parser services, plugins, or analyzer configuration themselves.
|
|
17
|
+
|
|
18
|
+
## Reference
|
|
19
|
+
|
|
20
|
+
- [PRINCIPLES.md](./PRINCIPLES.md) defines the enforcement philosophy and rule-admission bar.
|
|
21
|
+
- [JUDGMENT.md](./JUDGMENT.md) defines what must remain human or agent judgment.
|
|
22
|
+
- [RULE-AUTHORING.md](./RULE-AUTHORING.md) defines identifiers, metadata, lifecycle, and testing.
|
|
23
|
+
- [RULES.md](./RULES.md) is the canonical policy registry and implementation-status record.
|
|
24
|
+
- [DIAGNOSTICS.md](./DIAGNOSTICS.md) defines output, tool failures, and suppressions.
|
|
25
|
+
|
|
26
|
+
## Scope
|
|
27
|
+
|
|
28
|
+
The implementation is authoring-only and is exposed through `@astrale-os/sdk/linter`. It is not
|
|
29
|
+
re-exported from the isomorphic SDK root and must never enter a worker or browser bundle.
|
|
30
|
+
|
|
31
|
+
Each executable policy has one owner:
|
|
32
|
+
|
|
33
|
+
- `sdk` prevents invalid states through public types and authoring contracts.
|
|
34
|
+
- `oxlint` detects reliable file-local syntax and control-flow violations.
|
|
35
|
+
- `analyzer` detects project shape, import-graph, and cross-file violations.
|
|
36
|
+
|
|
37
|
+
Generic JavaScript and TypeScript rules remain owned by `@astrale-os/ox`. Astrale rules retain stable
|
|
38
|
+
`astrale/<slug>` identifiers regardless of their execution engine.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Rule Authoring
|
|
2
|
+
|
|
3
|
+
## Identity
|
|
4
|
+
|
|
5
|
+
Every Astrale-specific domain rule has one canonical semantic identifier:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
astrale/<slug>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Examples:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
astrale/core-is-pure
|
|
15
|
+
astrale/effects-run-in-step
|
|
16
|
+
astrale/no-read-inside-mutate
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The `astrale` namespace is declared once by the linter. The registry stores only the slug. Slugs use
|
|
20
|
+
lower-case kebab case, state the invariant clearly, and avoid engine words such as `oxlint`, `ast`, or
|
|
21
|
+
`typescript`.
|
|
22
|
+
|
|
23
|
+
Prefer established lint vocabulary:
|
|
24
|
+
|
|
25
|
+
- `no-*` prohibits a concrete pattern.
|
|
26
|
+
- `require-*` requires explicit syntax or evidence.
|
|
27
|
+
- `prefer-*` is advisory when valid exceptions exist.
|
|
28
|
+
- A direct invariant such as `core-is-pure` is acceptable when it reads more clearly.
|
|
29
|
+
|
|
30
|
+
Artificial abbreviations, trigrams, category prefixes, and monotonic numbers are not identifiers.
|
|
31
|
+
Family and owner can change without renaming a rule.
|
|
32
|
+
|
|
33
|
+
## Stability
|
|
34
|
+
|
|
35
|
+
Identifier stability begins at `accepted`, not `proposed`:
|
|
36
|
+
|
|
37
|
+
- A `proposed` rule may be renamed, merged, or removed freely.
|
|
38
|
+
- An `accepted` or later rule keeps its canonical identifier.
|
|
39
|
+
- If an accepted rule must be renamed, its old identifier remains a deprecated alias through at least
|
|
40
|
+
the next major SDK release.
|
|
41
|
+
- A retired identifier is never assigned to a different invariant.
|
|
42
|
+
|
|
43
|
+
This preserves early design freedom without maintaining an opaque surrogate code alongside every
|
|
44
|
+
readable rule name.
|
|
45
|
+
|
|
46
|
+
## Classification
|
|
47
|
+
|
|
48
|
+
Every registry entry declares:
|
|
49
|
+
|
|
50
|
+
| Field | Values |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `family` | `structure`, `purity`, `schema`, `authorization`, `durability`, `graph`, `errors`, or `client` |
|
|
53
|
+
| `owner` | `sdk`, `oxlint`, or `analyzer` |
|
|
54
|
+
| `scope` | `node`, `file`, or `project` |
|
|
55
|
+
| `automation` | `implement`, `research`, or `guidance` |
|
|
56
|
+
| `default` | `error`, `warning`, or `off` |
|
|
57
|
+
| `fix` | `safe`, `suggestion`, or `none` |
|
|
58
|
+
| `status` | `proposed`, `accepted`, `implemented`, `enforced`, `deprecated`, or `retired` |
|
|
59
|
+
|
|
60
|
+
Classification is metadata, not identity. Moving `astrale/authorize-is-explicit` from an analyzer hint
|
|
61
|
+
to an SDK type invariant preserves its identifier, suppressions, documentation URL, and history.
|
|
62
|
+
|
|
63
|
+
`automation` is an explicit decision, not a maturity score:
|
|
64
|
+
|
|
65
|
+
- `implement` means the current contract is suitable for automation and may enter the rule lifecycle.
|
|
66
|
+
- `research` means the idea is not approved for implementation. Its row must state what evidence or
|
|
67
|
+
narrower contract is missing.
|
|
68
|
+
- `guidance` means the current formulation should remain knowledge or review guidance. Its row remains
|
|
69
|
+
in the registry as a decision record, with `default: off` and no fix.
|
|
70
|
+
|
|
71
|
+
Changing `research` or `guidance` to `implement` requires a review of the detection contract and
|
|
72
|
+
false-positive fixtures. It is never implied merely because an engine could approximate the rule.
|
|
73
|
+
|
|
74
|
+
## Minimal policy specification
|
|
75
|
+
|
|
76
|
+
Every entry in [RULES.md](./RULES.md) records:
|
|
77
|
+
|
|
78
|
+
- A unique semantic slug.
|
|
79
|
+
- Family, primary owner, scope, automation disposition, intended default severity, fix class, and
|
|
80
|
+
status.
|
|
81
|
+
- One-sentence invariant.
|
|
82
|
+
- For `research`, the evidence or narrower contract needed before implementation.
|
|
83
|
+
- For `guidance`, why static enforcement would be misleading.
|
|
84
|
+
|
|
85
|
+
Before an `implement` entry can become `accepted`, it additionally requires:
|
|
86
|
+
|
|
87
|
+
- A deterministic detection contract and relevant source locations.
|
|
88
|
+
- At least one valid and invalid fixture.
|
|
89
|
+
- Representative legitimate alternatives and documented exceptions that must not trigger.
|
|
90
|
+
|
|
91
|
+
An accepted rule should graduate to a dedicated document only when examples, options, or edge cases no
|
|
92
|
+
longer fit clearly in the registry. Its filename is `<slug>.md`, for example
|
|
93
|
+
`effects-run-in-step.md`.
|
|
94
|
+
|
|
95
|
+
## Lifecycle
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
proposed -> accepted -> implemented -> enforced
|
|
99
|
+
| |
|
|
100
|
+
+-> deprecated +-> deprecated -> retired
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- `proposed`: candidate policy; no compatibility guarantee and no default diagnostic.
|
|
104
|
+
- `accepted`: identity, detection contract, and ownership approved.
|
|
105
|
+
- `implemented`: rule exists behind an explicit experimental preset.
|
|
106
|
+
- `enforced`: enabled at its documented severity in the strict preset.
|
|
107
|
+
- `deprecated`: still recognized, with replacement guidance where applicable.
|
|
108
|
+
- `retired`: no diagnostic is emitted; its historical identifier remains reserved.
|
|
109
|
+
|
|
110
|
+
No rule moves directly from prose to `enforced`.
|
|
111
|
+
|
|
112
|
+
Only entries classified `implement` may move from `proposed` to `accepted`. A `research` entry may have
|
|
113
|
+
an isolated prototype for measurement, but that prototype must not emit default project diagnostics.
|
|
114
|
+
A `guidance` entry does not enter the executable lifecycle unless it is narrowed and reclassified.
|
|
115
|
+
|
|
116
|
+
## Severity policy
|
|
117
|
+
|
|
118
|
+
- `error` means the code is structurally unsafe, invalid, or violates a settled Astrale contract.
|
|
119
|
+
- `warning` means the observation is reliable but the observed pattern is risky rather than invalid in
|
|
120
|
+
every occurrence.
|
|
121
|
+
- `off` means the rule is experimental, expensive, or awaiting platform support.
|
|
122
|
+
|
|
123
|
+
Severity is not a proxy for implementation progress. An unimplemented rule remains `proposed` even if
|
|
124
|
+
its intended default is `error`.
|
|
125
|
+
|
|
126
|
+
Warnings are not a shelter for noisy heuristics. A likely false positive still consumes attention,
|
|
127
|
+
creates suppressions, and weakens trust in every other diagnostic.
|
|
128
|
+
|
|
129
|
+
## Owner migration
|
|
130
|
+
|
|
131
|
+
Enforcement moves to the earliest reliable owner. A source rule should migrate from Oxlint or the
|
|
132
|
+
analyzer to `sdk` once an SDK type, schema compilation path, or runtime authoring contract can make the
|
|
133
|
+
invalid shape impossible. During migration, one owner emits the diagnostic; dual reporting is a bug.
|
|
134
|
+
|
|
135
|
+
## Rule test contract
|
|
136
|
+
|
|
137
|
+
Every implemented rule includes:
|
|
138
|
+
|
|
139
|
+
- Minimal valid fixtures.
|
|
140
|
+
- Minimal invalid fixtures with exact identifier, location, and message.
|
|
141
|
+
- False-positive fixtures for documented exceptions.
|
|
142
|
+
- Suppression fixtures.
|
|
143
|
+
- Fix snapshots when `fix` is `safe` or `suggestion`.
|
|
144
|
+
- A performance fixture when the rule walks imports, types, or compiled schema.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Rule Registry
|
|
2
|
+
|
|
3
|
+
This is the canonical registry of Astrale-specific domain-policy candidates. It intentionally keeps
|
|
4
|
+
both executable candidates and ideas that should remain guidance, so rejected automation ideas are not
|
|
5
|
+
rediscovered later. The executable-status table records what is currently enforced; every other entry
|
|
6
|
+
remains `proposed` regardless of its automation disposition.
|
|
7
|
+
|
|
8
|
+
The canonical identifier is `astrale/<slug>`. Each section heading supplies the `family`; owner, scope,
|
|
9
|
+
automation disposition, intended default, and fix class are explicit in every row. `owner` identifies
|
|
10
|
+
the prospective evidence owner; it does not imply an implementation commitment.
|
|
11
|
+
|
|
12
|
+
Automation dispositions are:
|
|
13
|
+
|
|
14
|
+
- `implement`: deterministic, valuable, and expected to have a low false-positive rate under the
|
|
15
|
+
current contract.
|
|
16
|
+
- `research`: potentially valuable, but blocked on a reliable detection contract or measured
|
|
17
|
+
false-positive evidence. It remains off and is not on the implementation roadmap.
|
|
18
|
+
- `guidance`: valuable architectural advice that should not be a static diagnostic in its current
|
|
19
|
+
formulation. It remains here as a decision record.
|
|
20
|
+
|
|
21
|
+
Generic JavaScript, TypeScript, React, Vitest, accessibility, and import rules remain owned by Oxlint
|
|
22
|
+
and `@astrale-os/ox`; they are not duplicated here. Behavioral and live properties belong in tests or
|
|
23
|
+
release checks rather than this static-rule registry.
|
|
24
|
+
|
|
25
|
+
## Executable status
|
|
26
|
+
|
|
27
|
+
| Rule | Owner | Status |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| `astrale/thin-schema-composition-root` | `analyzer` | enforced |
|
|
30
|
+
| `astrale/one-remote-definition-per-file` | `analyzer` | enforced |
|
|
31
|
+
| `astrale/core-is-pure` | `analyzer` | enforced |
|
|
32
|
+
| `astrale/core-has-no-async` | `oxlint` | enforced |
|
|
33
|
+
| `astrale/no-nested-steps` | `oxlint` | enforced |
|
|
34
|
+
| `astrale/no-read-inside-mutate` | `oxlint` | enforced |
|
|
35
|
+
|
|
36
|
+
An `implement` disposition records that a policy passed the automation-admission judgment; it is not a
|
|
37
|
+
claim that its SDK or analyzer implementation exists.
|
|
38
|
+
|
|
39
|
+
## Structure
|
|
40
|
+
|
|
41
|
+
| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
|
|
42
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
43
|
+
| `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. |
|
|
44
|
+
| `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. |
|
|
45
|
+
| `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. |
|
|
46
|
+
| `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. |
|
|
47
|
+
| `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. |
|
|
48
|
+
| `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. |
|
|
49
|
+
| `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. |
|
|
50
|
+
| `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. |
|
|
51
|
+
| `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. |
|
|
52
|
+
| `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/`. |
|
|
53
|
+
| `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`. |
|
|
54
|
+
| `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. |
|
|
55
|
+
|
|
56
|
+
## Purity
|
|
57
|
+
|
|
58
|
+
| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
|
|
59
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
60
|
+
| `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 relative imports and excludes tests. Unknown third-party packages are not guessed to be effectful. |
|
|
61
|
+
| `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. |
|
|
62
|
+
| `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. |
|
|
63
|
+
| `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. |
|
|
64
|
+
|
|
65
|
+
## Schema
|
|
66
|
+
|
|
67
|
+
| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
|
|
68
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
69
|
+
| `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. |
|
|
70
|
+
| `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. |
|
|
71
|
+
| `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. |
|
|
72
|
+
| `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. |
|
|
73
|
+
| `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. |
|
|
74
|
+
| `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. |
|
|
75
|
+
|
|
76
|
+
## Authorization
|
|
77
|
+
|
|
78
|
+
| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
|
|
79
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
80
|
+
| `authorize-is-explicit` | `sdk` | `node` | `implement` | error | suggestion | Every method and standalone function declares `authorize`; an intentionally open callable uses an explicit empty hook. |
|
|
81
|
+
| `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. |
|
|
82
|
+
|
|
83
|
+
## Durability
|
|
84
|
+
|
|
85
|
+
| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
|
|
86
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
87
|
+
| `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. |
|
|
88
|
+
| `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. |
|
|
89
|
+
| `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`. |
|
|
90
|
+
| `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. |
|
|
91
|
+
|
|
92
|
+
## Graph
|
|
93
|
+
|
|
94
|
+
| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
|
|
95
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
96
|
+
| `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. |
|
|
97
|
+
| `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. |
|
|
98
|
+
| `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()`. |
|
|
99
|
+
| `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. |
|
|
100
|
+
| `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. |
|
|
101
|
+
| `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. |
|
|
102
|
+
| `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. |
|
|
103
|
+
|
|
104
|
+
## Errors
|
|
105
|
+
|
|
106
|
+
| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
|
|
107
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
108
|
+
| `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`. |
|
|
109
|
+
| `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. |
|
|
110
|
+
| `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. |
|
|
111
|
+
|
|
112
|
+
## Client
|
|
113
|
+
|
|
114
|
+
| Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
|
|
115
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
116
|
+
| `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. |
|
|
117
|
+
|
|
118
|
+
## Automation rationale
|
|
119
|
+
|
|
120
|
+
### Research before implementation
|
|
121
|
+
|
|
122
|
+
| Slug | What must be proven first |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| `thin-domain-composition-root` | Syntax alone does not reliably distinguish composition from small domain behavior; the accepted contract needs explicit allowed and forbidden declarations. |
|
|
125
|
+
| `explicit-callable-filename` | Callable identity must come from a resolved registration symbol rather than filename or verb guessing. |
|
|
126
|
+
| `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. |
|
|
127
|
+
| `integrations-enter-through-deps` | Detection needs SDK-owned dependency and integration identities; arbitrary client imports and factories have legitimate uses. |
|
|
128
|
+
| `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. |
|
|
129
|
+
| `path-schema-for-addresses` | Graph-address intent must be visible in compiled schema metadata; parameter names such as `ref` or `path` are insufficient evidence. |
|
|
130
|
+
| `schema-derived-handler-types` | The analyzer must distinguish duplicate callable contracts from legitimate local helper types. |
|
|
131
|
+
| `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. |
|
|
132
|
+
| `caller-gate-names-principal` | The rule needs exact authorization-call identity and proof that the operation is caller-sensitive. |
|
|
133
|
+
| `effects-run-in-step` | Reliable enforcement requires interprocedural effect summaries, known effect identities, and explicit step ownership across helpers. |
|
|
134
|
+
| `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. |
|
|
135
|
+
| `prefer-schema-bound-client` | A warning is valid only when the analyzer can prove the typed client offers the equivalent capability. |
|
|
136
|
+
| `raw-path-only-at-boundary` | Type and callee-signature evidence must distinguish serialization boundaries from ordinary typed graph work. |
|
|
137
|
+
| `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. |
|
|
138
|
+
| `pagination-intent-is-explicit` | The SDK must expose paged-result intent in types or methods that the analyzer can follow without guessing control flow. |
|
|
139
|
+
| `unknown-errors-propagate` | The analyzer must recognize typed error guards and mappings before it can distinguish selective translation from catch-all masking. |
|
|
140
|
+
| `use-canonical-shell-bridge` | Import requirements are detectable, but semantic copies of Shell infrastructure are not; the rule needs a narrower import-boundary contract. |
|
|
141
|
+
|
|
142
|
+
### Keep as guidance in the current formulation
|
|
143
|
+
|
|
144
|
+
| Slug | Why static enforcement would be misleading |
|
|
145
|
+
| --- | --- |
|
|
146
|
+
| `bounded-context-layer-alignment` | Context ownership is business semantics, and valid layers may intentionally use different projections or context boundaries. |
|
|
147
|
+
| `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. |
|
|
148
|
+
| `context-owned-errors` | A machine cannot reliably decide whether an error is context-specific or genuinely shared. |
|
|
149
|
+
| `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. |
|
|
150
|
+
| `tests-follow-layer-ownership` | The distinction between a focused layer test and a business scenario depends on test intent, not directory names alone. |
|
|
151
|
+
| `explicit-default-props-name` | The exact builder name and filename are conventions whose value rarely justifies false positives across valid construction patterns. |
|
|
152
|
+
| `semantic-constant-filename` | Whether a constants file is a dumping ground depends on cohesion and consumers, not the word `constants`. |
|
|
153
|
+
| `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. |
|