@ontrails/warden 1.0.0-beta.4 → 1.0.0-beta.41
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/CHANGELOG.md +835 -13
- package/README.md +133 -26
- package/bin/warden.ts +51 -0
- package/package.json +27 -6
- package/src/adapter-check.ts +136 -0
- package/src/cli.ts +1570 -105
- package/src/command.ts +986 -0
- package/src/config.ts +193 -0
- package/src/draft.ts +22 -0
- package/src/drift.ts +233 -23
- package/src/fix.ts +126 -0
- package/src/formatters.ts +78 -13
- package/src/guide.ts +245 -0
- package/src/index.ts +247 -15
- package/src/project-context.ts +446 -0
- package/src/project-rules.ts +290 -0
- package/src/resolve.ts +531 -0
- package/src/rules/activation-orphan.ts +97 -0
- package/src/rules/captured-kernel.ts +375 -0
- package/src/rules/circular-refs.ts +150 -0
- package/src/rules/cli-command-route-coherence.ts +177 -0
- package/src/rules/composes-declarations.ts +839 -0
- package/src/rules/context-no-surface-types.ts +79 -15
- package/src/rules/dead-internal-trail.ts +161 -0
- package/src/rules/dead-public-trail.ts +258 -0
- package/src/rules/draft-file-marking.ts +155 -0
- package/src/rules/draft-visible-debt.ts +82 -0
- package/src/rules/duplicate-exported-symbol.ts +211 -0
- package/src/rules/duplicate-public-contract.ts +137 -0
- package/src/rules/entity-exists.ts +254 -0
- package/src/rules/entity-ids.ts +15 -0
- package/src/rules/error-mapping-completeness.ts +290 -0
- package/src/rules/example-valid.ts +395 -0
- package/src/rules/fires-declarations.ts +740 -0
- package/src/rules/governed-symbol-residue.ts +438 -0
- package/src/rules/implementation-returns-result.ts +1409 -166
- package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
- package/src/rules/incomplete-crud.ts +583 -0
- package/src/rules/index.ts +277 -10
- package/src/rules/intent-propagation.ts +125 -0
- package/src/rules/layer-field-name-drift.ts +102 -0
- package/src/rules/library-projection-coherence.ts +100 -0
- package/src/rules/metadata.ts +871 -0
- package/src/rules/missing-reconcile.ts +97 -0
- package/src/rules/missing-visibility.ts +111 -0
- package/src/rules/no-destructured-compose.ts +196 -0
- package/src/rules/no-dev-permit-in-source.ts +99 -0
- package/src/rules/no-direct-implementation-call.ts +12 -7
- package/src/rules/no-legacy-cli-alias-export.ts +247 -0
- package/src/rules/no-legacy-layer-imports.ts +211 -0
- package/src/rules/no-native-error-result.ts +118 -0
- package/src/rules/no-redundant-result-error-wrap.ts +384 -0
- package/src/rules/no-retired-cross-vocabulary.ts +204 -0
- package/src/rules/no-sync-result-assumption.ts +1141 -98
- package/src/rules/no-throw-in-detour-recover.ts +225 -0
- package/src/rules/no-throw-in-implementation.ts +15 -8
- package/src/rules/no-top-level-surface.ts +371 -0
- package/src/rules/on-references-exist.ts +194 -0
- package/src/rules/orphaned-signal.ts +149 -0
- package/src/rules/owner-projection-parity.ts +143 -0
- package/src/rules/permit-governance.ts +25 -0
- package/src/rules/public-export-example-coverage.ts +573 -0
- package/src/rules/public-internal-deep-imports.ts +456 -0
- package/src/rules/public-output-schema.ts +29 -0
- package/src/rules/public-union-output-discriminants.ts +150 -0
- package/src/rules/read-intent-fires.ts +188 -0
- package/src/rules/reference-exists.ts +97 -0
- package/src/rules/registry-names.ts +167 -0
- package/src/rules/resolved-import-boundary.ts +146 -0
- package/src/rules/resource-declarations.ts +697 -0
- package/src/rules/resource-exists.ts +181 -0
- package/src/rules/resource-id-grammar.ts +65 -0
- package/src/rules/resource-mock-coverage.ts +115 -0
- package/src/rules/retired-vocabulary.ts +633 -0
- package/src/rules/scan.ts +38 -25
- package/src/rules/scheduled-destroy-intent.ts +44 -0
- package/src/rules/signal-graph-coaching.ts +220 -0
- package/src/rules/source/composition.ts +165 -0
- package/src/rules/source/drafts.ts +164 -0
- package/src/rules/source/entities.ts +618 -0
- package/src/rules/source/pragmas.ts +45 -0
- package/src/rules/source/resources.ts +64 -0
- package/src/rules/source/signals.ts +397 -0
- package/src/rules/source/stores.ts +310 -0
- package/src/rules/specs.ts +9 -5
- package/src/rules/static-resource-accessor-preference.ts +653 -0
- package/src/rules/surface-overlay-coherence.ts +262 -0
- package/src/rules/surface-trailhead-coherence.ts +366 -0
- package/src/rules/trail-fork-coaching.ts +625 -0
- package/src/rules/trail-versioning-source.ts +1076 -0
- package/src/rules/trail-versioning-topo.ts +172 -0
- package/src/rules/trailhead-override-divergence.ts +356 -0
- package/src/rules/types.ts +354 -8
- package/src/rules/unmaterialized-activation-source.ts +85 -0
- package/src/rules/unreachable-detour-shadowing.ts +339 -0
- package/src/rules/valid-describe-refs.ts +162 -32
- package/src/rules/valid-detour-contract.ts +78 -0
- package/src/rules/warden-export-symmetry.ts +540 -0
- package/src/rules/warden-rules-use-ast.ts +1109 -0
- package/src/rules/webhook-route-collision.ts +306 -0
- package/src/trails/activation-orphan.trail.ts +84 -0
- package/src/trails/captured-kernel.trail.ts +108 -0
- package/src/trails/circular-refs.trail.ts +29 -0
- package/src/trails/cli-command-route-coherence.trail.ts +47 -0
- package/src/trails/composes-declarations.trail.ts +22 -0
- package/src/trails/context-no-surface-types.trail.ts +21 -0
- package/src/trails/dead-internal-trail.trail.ts +26 -0
- package/src/trails/dead-public-trail.trail.ts +31 -0
- package/src/trails/deprecation-without-guidance.trail.ts +21 -0
- package/src/trails/draft-file-marking.trail.ts +16 -0
- package/src/trails/draft-visible-debt.trail.ts +16 -0
- package/src/trails/duplicate-exported-symbol.trail.ts +48 -0
- package/src/trails/duplicate-public-contract.trail.ts +47 -0
- package/src/trails/entity-exists.trail.ts +21 -0
- package/src/trails/error-mapping-completeness.trail.ts +30 -0
- package/src/trails/example-valid.trail.ts +25 -0
- package/src/trails/fires-declarations.trail.ts +23 -0
- package/src/trails/fork-without-preserved-implementation.trail.ts +31 -0
- package/src/trails/governed-symbol-residue.trail.ts +24 -0
- package/src/trails/implementation-returns-result.trail.ts +20 -0
- package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
- package/src/trails/incomplete-crud.trail.ts +39 -0
- package/src/trails/index.ts +89 -0
- package/src/trails/intent-propagation.trail.ts +30 -0
- package/src/trails/layer-field-name-drift.trail.ts +39 -0
- package/src/trails/library-projection-coherence.trail.ts +43 -0
- package/src/trails/marker-schema-unsupported.trail.ts +23 -0
- package/src/trails/missing-reconcile.trail.ts +33 -0
- package/src/trails/missing-visibility.trail.ts +22 -0
- package/src/trails/no-destructured-compose.trail.ts +44 -0
- package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
- package/src/trails/no-direct-implementation-call.trail.ts +16 -0
- package/src/trails/no-legacy-cli-alias-export.trail.ts +41 -0
- package/src/trails/no-legacy-layer-imports.trail.ts +41 -0
- package/src/trails/no-native-error-result.trail.ts +18 -0
- package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
- package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
- package/src/trails/no-sync-result-assumption.trail.ts +19 -0
- package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
- package/src/trails/no-throw-in-implementation.trail.ts +20 -0
- package/src/trails/no-top-level-surface.trail.ts +43 -0
- package/src/trails/on-references-exist.trail.ts +21 -0
- package/src/trails/orphaned-signal.trail.ts +36 -0
- package/src/trails/owner-projection-parity.trail.ts +26 -0
- package/src/trails/pending-force.trail.ts +21 -0
- package/src/trails/permit-governance.trail.ts +51 -0
- package/src/trails/prefer-schema-inference.trail.ts +21 -0
- package/src/trails/public-export-example-coverage.trail.ts +16 -0
- package/src/trails/public-internal-deep-imports.trail.ts +94 -0
- package/src/trails/public-output-schema.trail.ts +55 -0
- package/src/trails/public-union-output-discriminants.trail.ts +33 -0
- package/src/trails/read-intent-fires.trail.ts +20 -0
- package/src/trails/reference-exists.trail.ts +25 -0
- package/src/trails/resolved-import-boundary.trail.ts +109 -0
- package/src/trails/resource-declarations.trail.ts +25 -0
- package/src/trails/resource-exists.trail.ts +27 -0
- package/src/trails/resource-id-grammar.trail.ts +39 -0
- package/src/trails/resource-mock-coverage.trail.ts +40 -0
- package/src/trails/run.ts +160 -0
- package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
- package/src/trails/schema.ts +237 -0
- package/src/trails/signal-graph-coaching.trail.ts +77 -0
- package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
- package/src/trails/surface-overlay-coherence.trail.ts +24 -0
- package/src/trails/surface-trailhead-coherence.trail.ts +25 -0
- package/src/trails/topo.ts +6 -0
- package/src/trails/trail-fork-coaching.trail.ts +42 -0
- package/src/trails/trailhead-override-divergence.trail.ts +47 -0
- package/src/trails/unmaterialized-activation-source.trail.ts +72 -0
- package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
- package/src/trails/valid-describe-refs.trail.ts +18 -0
- package/src/trails/valid-detour-contract.trail.ts +71 -0
- package/src/trails/version-gap.trail.ts +35 -0
- package/src/trails/version-pinned-compose.trail.ts +23 -0
- package/src/trails/version-without-examples.trail.ts +38 -0
- package/src/trails/warden-export-symmetry.trail.ts +16 -0
- package/src/trails/warden-rules-use-ast.trail.ts +64 -0
- package/src/trails/webhook-route-collision.trail.ts +50 -0
- package/src/trails/wrap-rule.ts +224 -0
- package/src/workspaces.ts +199 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/cli.d.ts +0 -46
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -218
- package/dist/cli.js.map +0 -1
- package/dist/drift.d.ts +0 -26
- package/dist/drift.d.ts.map +0 -1
- package/dist/drift.js +0 -27
- package/dist/drift.js.map +0 -1
- package/dist/formatters.d.ts +0 -29
- package/dist/formatters.d.ts.map +0 -1
- package/dist/formatters.js +0 -87
- package/dist/formatters.js.map +0 -1
- package/dist/index.d.ts +0 -26
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -26
- package/dist/index.js.map +0 -1
- package/dist/rules/ast.d.ts +0 -41
- package/dist/rules/ast.d.ts.map +0 -1
- package/dist/rules/ast.js +0 -161
- package/dist/rules/ast.js.map +0 -1
- package/dist/rules/context-no-surface-types.d.ts +0 -12
- package/dist/rules/context-no-surface-types.d.ts.map +0 -1
- package/dist/rules/context-no-surface-types.js +0 -96
- package/dist/rules/context-no-surface-types.js.map +0 -1
- package/dist/rules/implementation-returns-result.d.ts +0 -13
- package/dist/rules/implementation-returns-result.d.ts.map +0 -1
- package/dist/rules/implementation-returns-result.js +0 -277
- package/dist/rules/implementation-returns-result.js.map +0 -1
- package/dist/rules/index.d.ts +0 -15
- package/dist/rules/index.d.ts.map +0 -1
- package/dist/rules/index.js +0 -34
- package/dist/rules/index.js.map +0 -1
- package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
- package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
- package/dist/rules/no-direct-impl-in-route.js +0 -47
- package/dist/rules/no-direct-impl-in-route.js.map +0 -1
- package/dist/rules/no-direct-implementation-call.d.ts +0 -12
- package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
- package/dist/rules/no-direct-implementation-call.js +0 -39
- package/dist/rules/no-direct-implementation-call.js.map +0 -1
- package/dist/rules/no-sync-result-assumption.d.ts +0 -6
- package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
- package/dist/rules/no-sync-result-assumption.js +0 -98
- package/dist/rules/no-sync-result-assumption.js.map +0 -1
- package/dist/rules/no-throw-in-detour-target.d.ts +0 -12
- package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
- package/dist/rules/no-throw-in-detour-target.js +0 -87
- package/dist/rules/no-throw-in-detour-target.js.map +0 -1
- package/dist/rules/no-throw-in-implementation.d.ts +0 -9
- package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
- package/dist/rules/no-throw-in-implementation.js +0 -34
- package/dist/rules/no-throw-in-implementation.js.map +0 -1
- package/dist/rules/prefer-schema-inference.d.ts +0 -7
- package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
- package/dist/rules/prefer-schema-inference.js +0 -86
- package/dist/rules/prefer-schema-inference.js.map +0 -1
- package/dist/rules/scan.d.ts +0 -8
- package/dist/rules/scan.d.ts.map +0 -1
- package/dist/rules/scan.js +0 -32
- package/dist/rules/scan.js.map +0 -1
- package/dist/rules/specs.d.ts +0 -29
- package/dist/rules/specs.d.ts.map +0 -1
- package/dist/rules/specs.js +0 -192
- package/dist/rules/specs.js.map +0 -1
- package/dist/rules/structure.d.ts +0 -13
- package/dist/rules/structure.d.ts.map +0 -1
- package/dist/rules/structure.js +0 -142
- package/dist/rules/structure.js.map +0 -1
- package/dist/rules/types.d.ts +0 -52
- package/dist/rules/types.d.ts.map +0 -1
- package/dist/rules/types.js +0 -2
- package/dist/rules/types.js.map +0 -1
- package/dist/rules/valid-describe-refs.d.ts +0 -7
- package/dist/rules/valid-describe-refs.d.ts.map +0 -1
- package/dist/rules/valid-describe-refs.js +0 -51
- package/dist/rules/valid-describe-refs.js.map +0 -1
- package/dist/rules/valid-detour-refs.d.ts +0 -6
- package/dist/rules/valid-detour-refs.d.ts.map +0 -1
- package/dist/rules/valid-detour-refs.js +0 -116
- package/dist/rules/valid-detour-refs.js.map +0 -1
- package/src/__tests__/cli.test.ts +0 -198
- package/src/__tests__/drift.test.ts +0 -74
- package/src/__tests__/formatters.test.ts +0 -157
- package/src/__tests__/implementation-returns-result.test.ts +0 -129
- package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
- package/src/__tests__/no-sync-result-assumption.test.ts +0 -85
- package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
- package/src/__tests__/prefer-schema-inference.test.ts +0 -84
- package/src/__tests__/rules.test.ts +0 -227
- package/src/__tests__/valid-describe-refs.test.ts +0 -60
- package/src/rules/ast.ts +0 -213
- package/src/rules/no-direct-impl-in-route.ts +0 -81
- package/src/rules/no-throw-in-detour-target.ts +0 -150
- package/src/rules/valid-detour-refs.ts +0 -187
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { markerSchemaUnsupported } from '../rules/trail-versioning-source.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const markerSchemaUnsupportedTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'src/trails/versioned.ts',
|
|
10
|
+
sourceCode: `
|
|
11
|
+
trail('versioned.schema', {
|
|
12
|
+
version: 2,
|
|
13
|
+
input: z.object({ name: z.string() }),
|
|
14
|
+
output: z.object({ message: z.string() }),
|
|
15
|
+
implementation: async () => Result.ok({ message: 'ok' }),
|
|
16
|
+
});
|
|
17
|
+
`,
|
|
18
|
+
},
|
|
19
|
+
name: 'Stable marker-compatible schemas pass',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
rule: markerSchemaUnsupported,
|
|
23
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { missingReconcile } from '../rules/missing-reconcile.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const missingReconcileTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
// Composite keys: `${storeBinding}:${tableName}` so two stores with
|
|
10
|
+
// the same table name don't collide.
|
|
11
|
+
crudTableIds: ['definition:notes'],
|
|
12
|
+
filePath: 'clean.ts',
|
|
13
|
+
knownTrailIds: ['notes.reconcile'],
|
|
14
|
+
reconcileTableIds: ['definition:notes'],
|
|
15
|
+
sourceCode: `import { store } from '@ontrails/store';
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
|
|
18
|
+
const definition = store({
|
|
19
|
+
notes: {
|
|
20
|
+
identity: 'id',
|
|
21
|
+
schema: z.object({
|
|
22
|
+
id: z.string(),
|
|
23
|
+
title: z.string(),
|
|
24
|
+
}),
|
|
25
|
+
versioned: true,
|
|
26
|
+
},
|
|
27
|
+
});`,
|
|
28
|
+
},
|
|
29
|
+
name: 'Versioned CRUD tables stay clean when reconcile exists',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
rule: missingReconcile,
|
|
33
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { missingVisibility } from '../rules/missing-visibility.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const missingVisibilityTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
composeTargetTrailIds: ['entity.resolve'],
|
|
10
|
+
filePath: 'clean.ts',
|
|
11
|
+
knownTrailIds: ['entity.resolve'],
|
|
12
|
+
sourceCode: `trail('entity.resolve', {
|
|
13
|
+
visibility: 'internal',
|
|
14
|
+
composeInput: z.object({ forkedFrom: z.string() }),
|
|
15
|
+
implementation: async () => Result.ok({}),
|
|
16
|
+
});`,
|
|
17
|
+
},
|
|
18
|
+
name: 'Composition-only trails stay quiet when already internal',
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
rule: missingVisibility,
|
|
22
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { noDestructuredCompose } from '../rules/no-destructured-compose.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const noDestructuredComposeTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'clean.ts',
|
|
10
|
+
sourceCode: `trail("entity.onboard", {
|
|
11
|
+
composes: ["entity.create"],
|
|
12
|
+
implementation: async (input, ctx) => ctx.compose("entity.create", input),
|
|
13
|
+
});`,
|
|
14
|
+
},
|
|
15
|
+
name: 'Clean implementation using ctx.compose directly',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
expected: {
|
|
19
|
+
diagnostics: [
|
|
20
|
+
{
|
|
21
|
+
filePath: 'destructured.ts',
|
|
22
|
+
line: 4,
|
|
23
|
+
message:
|
|
24
|
+
'Trail "entity.onboard" destructures compose from the implementation context. Use ctx.compose(...) directly so composition stays visible and Warden can recognize composed Result values.',
|
|
25
|
+
rule: 'no-destructured-compose',
|
|
26
|
+
severity: 'warn',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
input: {
|
|
31
|
+
filePath: 'destructured.ts',
|
|
32
|
+
sourceCode: `trail("entity.onboard", {
|
|
33
|
+
composes: ["entity.create"],
|
|
34
|
+
implementation: async (input, ctx) => {
|
|
35
|
+
const { compose } = ctx;
|
|
36
|
+
return compose("entity.create", input);
|
|
37
|
+
},
|
|
38
|
+
});`,
|
|
39
|
+
},
|
|
40
|
+
name: 'Warns when compose is destructured from the implementation context',
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
rule: noDestructuredCompose,
|
|
44
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { noDevPermitInSource } from '../rules/no-dev-permit-in-source.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const noDevPermitInSourceTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'apps/example/src/cli.ts',
|
|
10
|
+
sourceCode: `import { tokenPreset } from '@ontrails/cli';\n`,
|
|
11
|
+
},
|
|
12
|
+
name: 'Source files without --dev-permit are clean',
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
rule: noDevPermitInSource,
|
|
16
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { noDirectImplementationCall } from '../rules/no-direct-implementation-call.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const noDirectImplementationCallTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'clean.ts',
|
|
10
|
+
sourceCode: `const data = await ctx.compose("entity.show", { id: "1" });`,
|
|
11
|
+
},
|
|
12
|
+
name: 'Clean code using ctx.compose instead of .implementation()',
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
rule: noDirectImplementationCall,
|
|
16
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { noLegacyCliAliasExport } from '../rules/no-legacy-cli-alias-export.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const noLegacyCliAliasExportTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'apps/example/src/app.ts',
|
|
10
|
+
sourceCode: `import { surfaceOverlay } from '@ontrails/core';\n\nexport const trailsOverlays = [surfaceOverlay({ cli: { ls: 'gear.list' } })];\n`,
|
|
11
|
+
},
|
|
12
|
+
name: 'App modules binding CLI through trailsOverlays are clean',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
expected: {
|
|
16
|
+
diagnostics: [
|
|
17
|
+
{
|
|
18
|
+
filePath: 'apps/example/src/app.ts',
|
|
19
|
+
fix: {
|
|
20
|
+
class: 'export-restructure',
|
|
21
|
+
reason:
|
|
22
|
+
"Legacy CLI alias export 'trailsCliAliases' must be rewritten into a surfaceOverlay({ cli: { ... } }) entry inside the module's trailsOverlays array export; run the Regrade class export-restructure:cli-aliases (TRL-1210) to automate this restructure.",
|
|
23
|
+
safety: 'review',
|
|
24
|
+
},
|
|
25
|
+
line: 1,
|
|
26
|
+
message:
|
|
27
|
+
"Legacy CLI alias export 'trailsCliAliases' was removed in the TRL-1207 surfaces-overlay cutover. Wrap the alias map into surfaceOverlay({ cli: { ... } }) from @ontrails/core inside the module's trailsOverlays array export.",
|
|
28
|
+
rule: 'no-legacy-cli-alias-export',
|
|
29
|
+
severity: 'error',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
input: {
|
|
34
|
+
filePath: 'apps/example/src/app.ts',
|
|
35
|
+
sourceCode: `export const trailsCliAliases = { 'gear.ls': [['gear', 'ls']] };\n`,
|
|
36
|
+
},
|
|
37
|
+
name: 'Legacy CLI alias exports produce migration diagnostics',
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
rule: noLegacyCliAliasExport,
|
|
41
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { noLegacyLayerImports } from '../rules/no-legacy-layer-imports.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const noLegacyLayerImportsTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'apps/example/src/cli.ts',
|
|
10
|
+
sourceCode: `import { tokenPreset } from '@ontrails/cli';\n`,
|
|
11
|
+
},
|
|
12
|
+
name: 'Source files without legacy layer references are clean',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
expected: {
|
|
16
|
+
diagnostics: [
|
|
17
|
+
{
|
|
18
|
+
filePath: 'apps/example/src/cli.ts',
|
|
19
|
+
fix: {
|
|
20
|
+
class: 'term-rewrite',
|
|
21
|
+
reason:
|
|
22
|
+
"Legacy layer 'authLayer' was removed in TRL-475; Permit enforcement is intrinsic to executeTrail. Removal has no mechanical replacement, so it needs human migration.",
|
|
23
|
+
safety: 'review',
|
|
24
|
+
},
|
|
25
|
+
line: 1,
|
|
26
|
+
message:
|
|
27
|
+
"Legacy layer 'authLayer' was removed in TRL-475. Permit enforcement is intrinsic to executeTrail. See docs/adr/0043-layer-evolution.md.",
|
|
28
|
+
rule: 'no-legacy-layer-imports',
|
|
29
|
+
severity: 'error',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
input: {
|
|
34
|
+
filePath: 'apps/example/src/cli.ts',
|
|
35
|
+
sourceCode: `import { authLayer } from '@ontrails/permits';\n`,
|
|
36
|
+
},
|
|
37
|
+
name: 'Legacy layer imports produce migration diagnostics',
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
rule: noLegacyLayerImports,
|
|
41
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { noNativeErrorResult } from '../rules/no-native-error-result.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const noNativeErrorResultTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'entity.ts',
|
|
10
|
+
sourceCode: `import { InternalError, Result } from "@ontrails/core";
|
|
11
|
+
|
|
12
|
+
export const load = () => Result.err(new InternalError("failed"));`,
|
|
13
|
+
},
|
|
14
|
+
name: 'Specific TrailsError subclasses stay clean',
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
rule: noNativeErrorResult,
|
|
18
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { noRedundantResultErrorWrap } from '../rules/no-redundant-result-error-wrap.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const noRedundantResultErrorWrapTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'entity.ts',
|
|
10
|
+
sourceCode: `import { Result, trail } from "@ontrails/core";
|
|
11
|
+
|
|
12
|
+
trail("entity.load", {
|
|
13
|
+
implementation: async (input, ctx) => {
|
|
14
|
+
const loaded = await ctx.compose("entity.fetch", input);
|
|
15
|
+
if (loaded.isErr()) {
|
|
16
|
+
return loaded;
|
|
17
|
+
}
|
|
18
|
+
return Result.ok(loaded.value);
|
|
19
|
+
},
|
|
20
|
+
});`,
|
|
21
|
+
},
|
|
22
|
+
name: 'Existing Result values pass through directly',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
expected: {
|
|
26
|
+
diagnostics: [
|
|
27
|
+
{
|
|
28
|
+
filePath: 'entity.ts',
|
|
29
|
+
line: 7,
|
|
30
|
+
message:
|
|
31
|
+
'Trail "entity.load": Result.err(loaded.error) re-wraps a Result that already carries that error. Return loaded directly to preserve the original Result boundary.',
|
|
32
|
+
rule: 'no-redundant-result-error-wrap',
|
|
33
|
+
severity: 'warn',
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
input: {
|
|
38
|
+
filePath: 'entity.ts',
|
|
39
|
+
sourceCode: `import { Result, trail } from "@ontrails/core";
|
|
40
|
+
|
|
41
|
+
trail("entity.load", {
|
|
42
|
+
implementation: async (input, ctx) => {
|
|
43
|
+
const loaded = await ctx.compose("entity.fetch", input);
|
|
44
|
+
if (loaded.isErr()) {
|
|
45
|
+
return Result.err(loaded.error);
|
|
46
|
+
}
|
|
47
|
+
return Result.ok(loaded.value);
|
|
48
|
+
},
|
|
49
|
+
});`,
|
|
50
|
+
},
|
|
51
|
+
name: 'Warns when an existing Result error is re-wrapped',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
rule: noRedundantResultErrorWrap,
|
|
55
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { noRetiredCrossVocabulary } from '../rules/no-retired-cross-vocabulary.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const noRetiredCrossVocabularyTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'apps/example/src/trails/play.ts',
|
|
10
|
+
sourceCode: 'export const play = trail("play", { composes: [] });\n',
|
|
11
|
+
},
|
|
12
|
+
name: 'Source files using compose vocabulary are clean',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
expected: {
|
|
16
|
+
diagnostics: [
|
|
17
|
+
{
|
|
18
|
+
filePath: 'apps/example/src/trails/play.ts',
|
|
19
|
+
fix: {
|
|
20
|
+
class: 'term-rewrite',
|
|
21
|
+
edits: [{ end: 43, replacement: 'composes', start: 36 }],
|
|
22
|
+
reason:
|
|
23
|
+
"Retired composition vocabulary 'crosses' has a mechanical beta.19 replacement 'composes'.",
|
|
24
|
+
safety: 'safe',
|
|
25
|
+
},
|
|
26
|
+
line: 1,
|
|
27
|
+
message:
|
|
28
|
+
"Retired composition vocabulary 'crosses' should be 'composes' after the beta.19 compose cutover.",
|
|
29
|
+
rule: 'no-retired-cross-vocabulary',
|
|
30
|
+
severity: 'error',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
input: {
|
|
35
|
+
filePath: 'apps/example/src/trails/play.ts',
|
|
36
|
+
sourceCode: 'export const play = trail("play", { crosses: [] });\n',
|
|
37
|
+
},
|
|
38
|
+
name: 'Retired crosses declarations produce safe migration diagnostics',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
rule: noRetiredCrossVocabulary,
|
|
42
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { noSyncResultAssumption } from '../rules/no-sync-result-assumption.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const noSyncResultAssumptionTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'clean.ts',
|
|
10
|
+
sourceCode: `const result = await myTrail.implementation(input, ctx);
|
|
11
|
+
if (result.isOk()) {
|
|
12
|
+
console.log(result.value);
|
|
13
|
+
}`,
|
|
14
|
+
},
|
|
15
|
+
name: 'Properly awaited .implementation() call',
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
rule: noSyncResultAssumption,
|
|
19
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { noThrowInDetourRecover } from '../rules/no-throw-in-detour-recover.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const noThrowInDetourRecoverTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'clean.ts',
|
|
10
|
+
sourceCode: `trail("entity.save", {
|
|
11
|
+
detours: [
|
|
12
|
+
{
|
|
13
|
+
on: ConflictError,
|
|
14
|
+
recover: async () => Result.ok({ recovered: true }),
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
implementation: () => Result.err(new ConflictError("conflict")),
|
|
18
|
+
})`,
|
|
19
|
+
},
|
|
20
|
+
name: 'Detour recover without throw',
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
rule: noThrowInDetourRecover,
|
|
24
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { noThrowInImplementation } from '../rules/no-throw-in-implementation.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const noThrowInImplementationTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'clean.ts',
|
|
10
|
+
sourceCode: `trail("entity.show", {
|
|
11
|
+
implementation: async (input, ctx) => {
|
|
12
|
+
return Result.ok({ name: "test" });
|
|
13
|
+
}
|
|
14
|
+
})`,
|
|
15
|
+
},
|
|
16
|
+
name: 'Clean implementation without throw',
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
rule: noThrowInImplementation,
|
|
20
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { noTopLevelSurface } from '../rules/no-top-level-surface.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const noTopLevelSurfaceTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'src/mcp.ts',
|
|
10
|
+
sourceCode: `import { surface } from "@ontrails/mcp";
|
|
11
|
+
import graph from "./app";
|
|
12
|
+
|
|
13
|
+
await surface(graph);`,
|
|
14
|
+
},
|
|
15
|
+
name: 'Allows dedicated surface entry modules',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
expected: {
|
|
19
|
+
diagnostics: [
|
|
20
|
+
{
|
|
21
|
+
filePath: 'src/app.ts',
|
|
22
|
+
line: 6,
|
|
23
|
+
message:
|
|
24
|
+
'This module exports a topo and opens a surface at module top level. Trails introspection commands (`survey`, `guide`, `compile`) import topo entry modules, so opening a surface here can trigger sockets or transports during introspection. Move surface-opening to a separate entry/bin and keep the topo-export module side-effect-free.',
|
|
25
|
+
rule: 'no-top-level-surface',
|
|
26
|
+
severity: 'warn',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
input: {
|
|
31
|
+
filePath: 'src/app.ts',
|
|
32
|
+
sourceCode: `import { topo } from "@ontrails/core";
|
|
33
|
+
import { surface } from "@ontrails/mcp";
|
|
34
|
+
import * as trails from "./trails";
|
|
35
|
+
|
|
36
|
+
export const graph = topo("app", trails);
|
|
37
|
+
await surface(graph);`,
|
|
38
|
+
},
|
|
39
|
+
name: 'Warns when a topo export module opens a surface',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
rule: noTopLevelSurface,
|
|
43
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { onReferencesExist } from '../rules/on-references-exist.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const onReferencesExistTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'consumer.ts',
|
|
10
|
+
knownSignalIds: ['entity.created'],
|
|
11
|
+
knownTrailIds: ['notify'],
|
|
12
|
+
sourceCode: `trail("notify", {
|
|
13
|
+
on: ["entity.created"],
|
|
14
|
+
implementation: async (input, ctx) => Result.ok({}),
|
|
15
|
+
})`,
|
|
16
|
+
},
|
|
17
|
+
name: 'Resolved on: reference',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
rule: onReferencesExist,
|
|
21
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { orphanedSignal } from '../rules/orphaned-signal.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const orphanedSignalTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
// Composite keys: `${storeBinding}:${tableName}` so two stores with
|
|
10
|
+
// the same table name don't collide.
|
|
11
|
+
crudTableIds: ['definition:notes'],
|
|
12
|
+
filePath: 'clean.ts',
|
|
13
|
+
knownTrailIds: ['notes.notify'],
|
|
14
|
+
onTargetSignalIds: [
|
|
15
|
+
'definition:notes.created',
|
|
16
|
+
'definition:notes.updated',
|
|
17
|
+
'definition:notes.removed',
|
|
18
|
+
],
|
|
19
|
+
sourceCode: `import { store } from '@ontrails/store';
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
|
|
22
|
+
const definition = store({
|
|
23
|
+
notes: {
|
|
24
|
+
identity: 'id',
|
|
25
|
+
schema: z.object({
|
|
26
|
+
id: z.string(),
|
|
27
|
+
title: z.string(),
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
|
+
});`,
|
|
31
|
+
},
|
|
32
|
+
name: 'Derived store signals stay quiet when trail on consumers exist',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
rule: orphanedSignal,
|
|
36
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
|
|
3
|
+
import { ownerProjectionParity } from '../rules/owner-projection-parity.js';
|
|
4
|
+
import { wrapRule } from './wrap-rule.js';
|
|
5
|
+
|
|
6
|
+
export const ownerProjectionParityTrail = wrapRule({
|
|
7
|
+
examples: [
|
|
8
|
+
{
|
|
9
|
+
expected: { diagnostics: [] },
|
|
10
|
+
input: {
|
|
11
|
+
filePath: fileURLToPath(
|
|
12
|
+
new URL('../../../http/src/method.ts', import.meta.url)
|
|
13
|
+
),
|
|
14
|
+
sourceCode: `import type { Intent } from '@ontrails/core';
|
|
15
|
+
|
|
16
|
+
export const httpMethodByIntent = {
|
|
17
|
+
destroy: 'DELETE',
|
|
18
|
+
read: 'GET',
|
|
19
|
+
write: 'POST',
|
|
20
|
+
} as const satisfies Record<Intent, 'GET' | 'POST' | 'DELETE'>;`,
|
|
21
|
+
},
|
|
22
|
+
name: 'HTTP method projection covers core intent values',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
rule: ownerProjectionParity,
|
|
26
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { topo } from '@ontrails/core';
|
|
2
|
+
import { deriveTopoGraph } from '@ontrails/topography';
|
|
3
|
+
|
|
4
|
+
import { pendingForce } from '../rules/trail-versioning-topo.js';
|
|
5
|
+
import { wrapTopoRule } from './wrap-rule.js';
|
|
6
|
+
|
|
7
|
+
const emptyTopo = topo('pending-force-clean', {});
|
|
8
|
+
|
|
9
|
+
export const pendingForceTrail = wrapTopoRule({
|
|
10
|
+
examples: [
|
|
11
|
+
{
|
|
12
|
+
expected: { diagnostics: [] },
|
|
13
|
+
input: {
|
|
14
|
+
graph: deriveTopoGraph(emptyTopo),
|
|
15
|
+
topo: emptyTopo,
|
|
16
|
+
},
|
|
17
|
+
name: 'No pending force audit events passes',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
rule: pendingForce,
|
|
21
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Result, topo, trail } from '@ontrails/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
import { permitGovernance } from '../rules/permit-governance.js';
|
|
5
|
+
import { wrapTopoRule } from './wrap-rule.js';
|
|
6
|
+
|
|
7
|
+
const destroyWithoutPermit = trail('entity.delete', {
|
|
8
|
+
implementation: () => Result.ok({ ok: true }),
|
|
9
|
+
input: z.object({}),
|
|
10
|
+
intent: 'destroy',
|
|
11
|
+
output: z.object({ ok: z.boolean() }),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const scopedDestroy = trail('entity.delete', {
|
|
15
|
+
implementation: () => Result.ok({ ok: true }),
|
|
16
|
+
input: z.object({}),
|
|
17
|
+
intent: 'destroy',
|
|
18
|
+
output: z.object({ ok: z.boolean() }),
|
|
19
|
+
permit: { scopes: ['entity:delete'] },
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const permitGovernanceTrail = wrapTopoRule({
|
|
23
|
+
examples: [
|
|
24
|
+
{
|
|
25
|
+
expected: {
|
|
26
|
+
diagnostics: [
|
|
27
|
+
{
|
|
28
|
+
filePath: '<topo>',
|
|
29
|
+
line: 1,
|
|
30
|
+
message:
|
|
31
|
+
'Trail "entity.delete" has intent \'destroy\' but no permit declaration',
|
|
32
|
+
rule: 'permit.destroyWithoutPermit',
|
|
33
|
+
severity: 'error',
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
input: {
|
|
38
|
+
topo: topo('trl-377-missing-permit', { destroyWithoutPermit }),
|
|
39
|
+
},
|
|
40
|
+
name: 'Destroy trails without permits emit an error',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
expected: { diagnostics: [] },
|
|
44
|
+
input: {
|
|
45
|
+
topo: topo('trl-377-scoped-destroy', { scopedDestroy }),
|
|
46
|
+
},
|
|
47
|
+
name: 'Scoped destroy permits keep permit governance clean',
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
rule: permitGovernance,
|
|
51
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { preferSchemaInference } from '../rules/prefer-schema-inference.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const preferSchemaInferenceTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'clean.ts',
|
|
10
|
+
sourceCode: `trail("entity.show", {
|
|
11
|
+
input: z.object({ name: z.string() }),
|
|
12
|
+
implementation: async (input, ctx) => {
|
|
13
|
+
return Result.ok({ name: input.name });
|
|
14
|
+
}
|
|
15
|
+
})`,
|
|
16
|
+
},
|
|
17
|
+
name: 'Trail without redundant field overrides',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
rule: preferSchemaInference,
|
|
21
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { publicExportExampleCoverage } from '../rules/public-export-example-coverage.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const publicExportExampleCoverageTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'packages/other-pkg/src/index.ts',
|
|
10
|
+
sourceCode: `export { somethingElse } from './other.js';\n`,
|
|
11
|
+
},
|
|
12
|
+
name: 'Ignores barrels outside the public API example policy',
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
rule: publicExportExampleCoverage,
|
|
16
|
+
});
|