@ontrails/warden 1.0.0-beta.39 → 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 +125 -0
- package/README.md +5 -5
- package/package.json +10 -12
- package/src/cli.ts +49 -44
- package/src/command.ts +2 -2
- package/src/drift.ts +4 -3
- package/src/index.ts +3 -2
- package/src/project-context.ts +3 -3
- package/src/resolve.ts +2 -2
- package/src/rules/captured-kernel.ts +375 -0
- package/src/rules/circular-refs.ts +12 -16
- package/src/rules/cli-command-route-coherence.ts +1 -1
- package/src/rules/composes-declarations.ts +15 -13
- package/src/rules/context-no-surface-types.ts +4 -3
- package/src/rules/dead-internal-trail.ts +3 -3
- package/src/rules/dead-public-trail.ts +3 -3
- package/src/rules/draft-file-marking.ts +4 -9
- package/src/rules/draft-visible-debt.ts +3 -8
- package/src/rules/duplicate-public-contract.ts +50 -5
- package/src/rules/{contour-exists.ts → entity-exists.ts} +67 -64
- package/src/rules/entity-ids.ts +15 -0
- package/src/rules/error-mapping-completeness.ts +2 -2
- package/src/rules/example-valid.ts +84 -84
- package/src/rules/fires-declarations.ts +27 -22
- package/src/rules/governed-symbol-residue.ts +339 -32
- package/src/rules/implementation-returns-result.ts +8 -8
- package/src/rules/incomplete-accessor-for-standard-op.ts +15 -15
- package/src/rules/incomplete-crud.ts +42 -40
- package/src/rules/index.ts +9 -6
- package/src/rules/intent-propagation.ts +3 -5
- package/src/rules/layer-field-name-drift.ts +2 -2
- package/src/rules/library-projection-coherence.ts +2 -2
- package/src/rules/metadata.ts +37 -15
- package/src/rules/missing-reconcile.ts +3 -4
- package/src/rules/missing-visibility.ts +2 -1
- package/src/rules/no-destructured-compose.ts +14 -12
- package/src/rules/no-direct-implementation-call.ts +12 -7
- package/src/rules/no-legacy-cli-alias-export.ts +2 -2
- package/src/rules/no-native-error-result.ts +2 -2
- package/src/rules/no-redundant-result-error-wrap.ts +10 -10
- package/src/rules/no-sync-result-assumption.ts +84 -78
- package/src/rules/no-throw-in-detour-recover.ts +2 -2
- package/src/rules/no-throw-in-implementation.ts +13 -7
- package/src/rules/no-top-level-surface.ts +2 -2
- package/src/rules/on-references-exist.ts +3 -3
- package/src/rules/orphaned-signal.ts +4 -5
- package/src/rules/owner-projection-parity.ts +2 -2
- package/src/rules/public-export-example-coverage.ts +10 -4
- package/src/rules/public-internal-deep-imports.ts +2 -3
- package/src/rules/read-intent-fires.ts +4 -3
- package/src/rules/reference-exists.ts +20 -21
- package/src/rules/registry-names.ts +6 -4
- package/src/rules/resolved-import-boundary.ts +1 -1
- package/src/rules/resource-declarations.ts +21 -17
- package/src/rules/resource-exists.ts +4 -2
- package/src/rules/resource-id-grammar.ts +2 -2
- package/src/rules/resource-mock-coverage.ts +2 -2
- package/src/rules/retired-vocabulary.ts +242 -5
- 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/static-resource-accessor-preference.ts +15 -11
- package/src/rules/surface-overlay-coherence.ts +1 -1
- package/src/rules/surface-trailhead-coherence.ts +2 -2
- package/src/rules/trail-fork-coaching.ts +27 -18
- package/src/rules/trail-versioning-source.ts +16 -12
- package/src/rules/trail-versioning-topo.ts +2 -2
- package/src/rules/trailhead-override-divergence.ts +2 -2
- package/src/rules/types.ts +7 -7
- package/src/rules/unmaterialized-activation-source.ts +1 -0
- package/src/rules/unreachable-detour-shadowing.ts +2 -2
- package/src/rules/valid-describe-refs.ts +2 -2
- package/src/rules/warden-export-symmetry.ts +3 -3
- package/src/rules/warden-rules-use-ast.ts +12 -17
- package/src/trails/activation-orphan.trail.ts +5 -5
- package/src/trails/captured-kernel.trail.ts +108 -0
- package/src/trails/circular-refs.trail.ts +6 -6
- package/src/trails/cli-command-route-coherence.trail.ts +2 -2
- package/src/trails/composes-declarations.trail.ts +1 -1
- package/src/trails/context-no-surface-types.trail.ts +1 -1
- package/src/trails/dead-internal-trail.trail.ts +2 -2
- package/src/trails/dead-public-trail.trail.ts +1 -1
- package/src/trails/deprecation-without-guidance.trail.ts +1 -1
- package/src/trails/duplicate-public-contract.trail.ts +3 -3
- package/src/trails/entity-exists.trail.ts +21 -0
- package/src/trails/example-valid.trail.ts +3 -3
- package/src/trails/fires-declarations.trail.ts +1 -1
- package/src/trails/{fork-without-preserved-blaze.trail.ts → fork-without-preserved-implementation.trail.ts} +4 -4
- package/src/trails/implementation-returns-result.trail.ts +1 -1
- package/src/trails/incomplete-accessor-for-standard-op.trail.ts +4 -4
- package/src/trails/index.ts +3 -2
- package/src/trails/intent-propagation.trail.ts +2 -2
- package/src/trails/library-projection-coherence.trail.ts +2 -2
- package/src/trails/marker-schema-unsupported.trail.ts +1 -1
- package/src/trails/missing-visibility.trail.ts +1 -1
- package/src/trails/no-destructured-compose.trail.ts +5 -5
- package/src/trails/no-direct-implementation-call.trail.ts +1 -1
- package/src/trails/no-redundant-result-error-wrap.trail.ts +2 -2
- package/src/trails/no-sync-result-assumption.trail.ts +2 -2
- package/src/trails/no-throw-in-detour-recover.trail.ts +1 -1
- package/src/trails/no-throw-in-implementation.trail.ts +1 -1
- package/src/trails/on-references-exist.trail.ts +1 -1
- package/src/trails/pending-force.trail.ts +1 -1
- package/src/trails/permit-governance.trail.ts +2 -2
- package/src/trails/prefer-schema-inference.trail.ts +1 -1
- package/src/trails/public-output-schema.trail.ts +2 -2
- package/src/trails/public-union-output-discriminants.trail.ts +1 -1
- package/src/trails/read-intent-fires.trail.ts +1 -1
- package/src/trails/reference-exists.trail.ts +5 -5
- package/src/trails/resource-declarations.trail.ts +1 -1
- package/src/trails/resource-exists.trail.ts +1 -1
- package/src/trails/run.ts +4 -6
- package/src/trails/scheduled-destroy-intent.trail.ts +2 -2
- package/src/trails/schema.ts +9 -9
- package/src/trails/signal-graph-coaching.trail.ts +2 -2
- package/src/trails/static-resource-accessor-preference.trail.ts +1 -1
- package/src/trails/surface-overlay-coherence.trail.ts +1 -1
- package/src/trails/trail-fork-coaching.trail.ts +1 -1
- package/src/trails/unmaterialized-activation-source.trail.ts +5 -5
- package/src/trails/valid-detour-contract.trail.ts +1 -1
- package/src/trails/version-gap.trail.ts +1 -1
- package/src/trails/version-pinned-compose.trail.ts +1 -1
- package/src/trails/version-without-examples.trail.ts +1 -1
- package/src/trails/warden-rules-use-ast.trail.ts +2 -2
- package/src/trails/webhook-route-collision.trail.ts +2 -2
- package/src/trails/wrap-rule.ts +17 -17
- package/src/ast.ts +0 -137
- package/src/rules/ast.ts +0 -4143
- package/src/rules/contour-ids.ts +0 -15
- package/src/trails/contour-exists.trail.ts +0 -21
|
@@ -7,12 +7,12 @@ export const noSyncResultAssumptionTrail = wrapRule({
|
|
|
7
7
|
expected: { diagnostics: [] },
|
|
8
8
|
input: {
|
|
9
9
|
filePath: 'clean.ts',
|
|
10
|
-
sourceCode: `const result = await myTrail.
|
|
10
|
+
sourceCode: `const result = await myTrail.implementation(input, ctx);
|
|
11
11
|
if (result.isOk()) {
|
|
12
12
|
console.log(result.value);
|
|
13
13
|
}`,
|
|
14
14
|
},
|
|
15
|
-
name: 'Properly awaited .
|
|
15
|
+
name: 'Properly awaited .implementation() call',
|
|
16
16
|
},
|
|
17
17
|
],
|
|
18
18
|
rule: noSyncResultAssumption,
|
|
@@ -14,7 +14,7 @@ export const noThrowInDetourRecoverTrail = wrapRule({
|
|
|
14
14
|
recover: async () => Result.ok({ recovered: true }),
|
|
15
15
|
},
|
|
16
16
|
],
|
|
17
|
-
|
|
17
|
+
implementation: () => Result.err(new ConflictError("conflict")),
|
|
18
18
|
})`,
|
|
19
19
|
},
|
|
20
20
|
name: 'Detour recover without throw',
|
|
@@ -11,7 +11,7 @@ export const onReferencesExistTrail = wrapRule({
|
|
|
11
11
|
knownTrailIds: ['notify'],
|
|
12
12
|
sourceCode: `trail("notify", {
|
|
13
13
|
on: ["entity.created"],
|
|
14
|
-
|
|
14
|
+
implementation: async (input, ctx) => Result.ok({}),
|
|
15
15
|
})`,
|
|
16
16
|
},
|
|
17
17
|
name: 'Resolved on: reference',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { topo } from '@ontrails/core';
|
|
2
|
-
import { deriveTopoGraph } from '@ontrails/
|
|
2
|
+
import { deriveTopoGraph } from '@ontrails/topography';
|
|
3
3
|
|
|
4
4
|
import { pendingForce } from '../rules/trail-versioning-topo.js';
|
|
5
5
|
import { wrapTopoRule } from './wrap-rule.js';
|
|
@@ -5,14 +5,14 @@ import { permitGovernance } from '../rules/permit-governance.js';
|
|
|
5
5
|
import { wrapTopoRule } from './wrap-rule.js';
|
|
6
6
|
|
|
7
7
|
const destroyWithoutPermit = trail('entity.delete', {
|
|
8
|
-
|
|
8
|
+
implementation: () => Result.ok({ ok: true }),
|
|
9
9
|
input: z.object({}),
|
|
10
10
|
intent: 'destroy',
|
|
11
11
|
output: z.object({ ok: z.boolean() }),
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
const scopedDestroy = trail('entity.delete', {
|
|
15
|
-
|
|
15
|
+
implementation: () => Result.ok({ ok: true }),
|
|
16
16
|
input: z.object({}),
|
|
17
17
|
intent: 'destroy',
|
|
18
18
|
output: z.object({ ok: z.boolean() }),
|
|
@@ -9,7 +9,7 @@ export const preferSchemaInferenceTrail = wrapRule({
|
|
|
9
9
|
filePath: 'clean.ts',
|
|
10
10
|
sourceCode: `trail("entity.show", {
|
|
11
11
|
input: z.object({ name: z.string() }),
|
|
12
|
-
|
|
12
|
+
implementation: async (input, ctx) => {
|
|
13
13
|
return Result.ok({ name: input.name });
|
|
14
14
|
}
|
|
15
15
|
})`,
|
|
@@ -5,7 +5,7 @@ import { publicOutputSchema } from '../rules/public-output-schema.js';
|
|
|
5
5
|
import { wrapTopoRule } from './wrap-rule.js';
|
|
6
6
|
|
|
7
7
|
const cleanTrail = trail('report.read', {
|
|
8
|
-
|
|
8
|
+
implementation: () => Result.ok({ ok: true }),
|
|
9
9
|
input: z.object({}),
|
|
10
10
|
output: z.object({ ok: z.boolean() }),
|
|
11
11
|
});
|
|
@@ -15,7 +15,7 @@ const cleanTopo = topo('public-output-schema-clean', {
|
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
const missingOutputTrail = trail('report.missing', {
|
|
18
|
-
|
|
18
|
+
implementation: () => Result.ok({ ok: true }),
|
|
19
19
|
input: z.object({}),
|
|
20
20
|
});
|
|
21
21
|
|
|
@@ -10,7 +10,7 @@ const cleanOutput = z.discriminatedUnion('kind', [
|
|
|
10
10
|
]);
|
|
11
11
|
|
|
12
12
|
const cleanTrail = trail('report.read', {
|
|
13
|
-
|
|
13
|
+
implementation: () => Result.ok({ kind: 'message' as const, message: 'ok' }),
|
|
14
14
|
input: z.object({}),
|
|
15
15
|
output: cleanOutput,
|
|
16
16
|
});
|
|
@@ -10,7 +10,7 @@ export const readIntentFiresTrail = wrapRule({
|
|
|
10
10
|
sourceCode: `const entityLoaded = signal('entity.loaded', { payload: z.object({}) });
|
|
11
11
|
trail('entity.read', {
|
|
12
12
|
intent: 'read',
|
|
13
|
-
|
|
13
|
+
implementation: async () => Result.ok({}),
|
|
14
14
|
});`,
|
|
15
15
|
},
|
|
16
16
|
name: 'Read trails without fires stay quiet',
|
|
@@ -6,19 +6,19 @@ export const referenceExistsTrail = wrapRule({
|
|
|
6
6
|
{
|
|
7
7
|
expected: { diagnostics: [] },
|
|
8
8
|
input: {
|
|
9
|
-
filePath: '
|
|
10
|
-
|
|
9
|
+
filePath: 'entities.ts',
|
|
10
|
+
knownEntityIds: ['gist', 'user'],
|
|
11
11
|
knownTrailIds: [],
|
|
12
|
-
sourceCode: `const user =
|
|
12
|
+
sourceCode: `const user = entity("user", {
|
|
13
13
|
id: z.string().uuid(),
|
|
14
14
|
}, { identity: "id" });
|
|
15
15
|
|
|
16
|
-
const gist =
|
|
16
|
+
const gist = entity("gist", {
|
|
17
17
|
id: z.string().uuid(),
|
|
18
18
|
ownerId: user.id(),
|
|
19
19
|
}, { identity: "id" });`,
|
|
20
20
|
},
|
|
21
|
-
name: '
|
|
21
|
+
name: 'Entity references resolve to known project entities',
|
|
22
22
|
},
|
|
23
23
|
],
|
|
24
24
|
rule: referenceExists,
|
package/src/trails/run.ts
CHANGED
|
@@ -34,13 +34,11 @@ const appendDiagnostics = (
|
|
|
34
34
|
type TrailIntentMap = Readonly<Record<string, Intent>>;
|
|
35
35
|
|
|
36
36
|
interface ProjectRuleOptions {
|
|
37
|
-
readonly
|
|
38
|
-
Record<string, readonly string[]>
|
|
39
|
-
>;
|
|
37
|
+
readonly entityReferencesByName?: Readonly<Record<string, readonly string[]>>;
|
|
40
38
|
readonly composeTargetTrailIds?: readonly string[];
|
|
41
39
|
readonly crudTableIds?: readonly string[];
|
|
42
40
|
readonly crudCoverageByEntity?: Readonly<Record<string, readonly string[]>>;
|
|
43
|
-
readonly
|
|
41
|
+
readonly knownEntityIds?: readonly string[];
|
|
44
42
|
readonly importResolutionsByFile?: Readonly<
|
|
45
43
|
Record<string, readonly WardenImportResolution[]>
|
|
46
44
|
>;
|
|
@@ -57,11 +55,11 @@ interface ProjectRuleOptions {
|
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
const PROJECT_OPTION_KEYS = [
|
|
60
|
-
'
|
|
58
|
+
'entityReferencesByName',
|
|
61
59
|
'composeTargetTrailIds',
|
|
62
60
|
'crudTableIds',
|
|
63
61
|
'crudCoverageByEntity',
|
|
64
|
-
'
|
|
62
|
+
'knownEntityIds',
|
|
65
63
|
'importResolutionsByFile',
|
|
66
64
|
'documentedImportResolutionsByFile',
|
|
67
65
|
'knownResourceIds',
|
|
@@ -5,7 +5,7 @@ import { scheduledDestroyIntent } from '../rules/scheduled-destroy-intent.js';
|
|
|
5
5
|
import { wrapTopoRule } from './wrap-rule.js';
|
|
6
6
|
|
|
7
7
|
export const scheduledDestroyTrail = trail('billing.purge-expired', {
|
|
8
|
-
|
|
8
|
+
implementation: () => Result.ok({ ok: true }),
|
|
9
9
|
input: z.object({}),
|
|
10
10
|
intent: 'destroy',
|
|
11
11
|
on: [
|
|
@@ -18,7 +18,7 @@ export const scheduledDestroyTrail = trail('billing.purge-expired', {
|
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
const scheduledWriteTrail = trail('billing.reconcile', {
|
|
21
|
-
|
|
21
|
+
implementation: () => Result.ok({ ok: true }),
|
|
22
22
|
input: z.object({}),
|
|
23
23
|
on: [schedule('schedule.billing.reconcile', { cron: '0 * * * *' })],
|
|
24
24
|
output: z.object({ ok: z.boolean() }),
|
package/src/trails/schema.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { intentValues } from '@ontrails/core';
|
|
9
9
|
import type { Topo } from '@ontrails/core';
|
|
10
|
-
import type { TopoGraph } from '@ontrails/
|
|
10
|
+
import type { TopoGraph } from '@ontrails/topography';
|
|
11
11
|
import { z } from 'zod';
|
|
12
12
|
import { wardenImportResolutionErrorKinds } from '../resolve.js';
|
|
13
13
|
import { wardenFixClasses, wardenFixSafeties } from '../rules/metadata.js';
|
|
@@ -106,7 +106,7 @@ export const exportedSymbolDefinitionSchema = z.object({
|
|
|
106
106
|
* Extended input for project-aware warden rule trails.
|
|
107
107
|
*
|
|
108
108
|
* Adds `knownTrailIds` so the caller can supply compose-file context and avoid
|
|
109
|
-
* false positives for `@see` references or compose-file
|
|
109
|
+
* false positives for `@see` references or compose-file entity relationships.
|
|
110
110
|
*/
|
|
111
111
|
export const authoredMcpSurfaceBindingSetSchema = z.object({
|
|
112
112
|
appName: z.string().describe('App/topo label the bindings were authored for'),
|
|
@@ -131,10 +131,6 @@ export const projectAwareRuleInput = ruleInput.extend({
|
|
|
131
131
|
.array(z.string())
|
|
132
132
|
.optional()
|
|
133
133
|
.describe('Trail IDs referenced by composes arrays across the project'),
|
|
134
|
-
contourReferencesByName: z
|
|
135
|
-
.record(z.string(), z.array(z.string()))
|
|
136
|
-
.optional()
|
|
137
|
-
.describe('Declared contour references keyed by source contour name'),
|
|
138
134
|
crudCoverageByEntity: z
|
|
139
135
|
.record(z.string(), z.array(z.string()))
|
|
140
136
|
.optional()
|
|
@@ -149,6 +145,10 @@ export const projectAwareRuleInput = ruleInput.extend({
|
|
|
149
145
|
.record(z.string(), z.array(importResolutionSchema))
|
|
150
146
|
.optional()
|
|
151
147
|
.describe('Resolved docs/specifier facts keyed by documentation file path'),
|
|
148
|
+
entityReferencesByName: z
|
|
149
|
+
.record(z.string(), z.array(z.string()))
|
|
150
|
+
.optional()
|
|
151
|
+
.describe('Declared entity references keyed by source entity name'),
|
|
152
152
|
exportedSymbolDefinitionsByName: z
|
|
153
153
|
.record(z.string(), z.array(exportedSymbolDefinitionSchema))
|
|
154
154
|
.optional()
|
|
@@ -157,10 +157,10 @@ export const projectAwareRuleInput = ruleInput.extend({
|
|
|
157
157
|
.record(z.string(), z.array(importResolutionSchema))
|
|
158
158
|
.optional()
|
|
159
159
|
.describe('Resolved import facts keyed by importer file path'),
|
|
160
|
-
|
|
160
|
+
knownEntityIds: z
|
|
161
161
|
.array(z.string())
|
|
162
162
|
.optional()
|
|
163
|
-
.describe('
|
|
163
|
+
.describe('Entity names known across the project'),
|
|
164
164
|
knownResourceIds: z
|
|
165
165
|
.array(z.string())
|
|
166
166
|
.optional()
|
|
@@ -219,7 +219,7 @@ export const topoAwareRuleInput = z.object({
|
|
|
219
219
|
value !== null &&
|
|
220
220
|
'trails' in value &&
|
|
221
221
|
'resources' in value &&
|
|
222
|
-
'
|
|
222
|
+
'entities' in value &&
|
|
223
223
|
'signals' in value,
|
|
224
224
|
{ message: 'Expected a resolved Topo instance' }
|
|
225
225
|
)
|
|
@@ -13,11 +13,11 @@ const producedSignal = signal('invoice.created', {
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
const producerTrail = trail('invoice.create', {
|
|
16
|
-
|
|
16
|
+
fires: [producedSignal],
|
|
17
|
+
implementation: async (_input, ctx) => {
|
|
17
18
|
await ctx.fire?.(producedSignal, { invoiceId: 'inv_1' });
|
|
18
19
|
return Result.ok({ invoiceId: 'inv_1' });
|
|
19
20
|
},
|
|
20
|
-
fires: [producedSignal],
|
|
21
21
|
input: z.object({}),
|
|
22
22
|
output: z.object({ invoiceId: z.string() }),
|
|
23
23
|
});
|
|
@@ -5,7 +5,7 @@ import { surfaceOverlayCoherence } from '../rules/surface-overlay-coherence.js';
|
|
|
5
5
|
import { wrapTopoRule } from './wrap-rule.js';
|
|
6
6
|
|
|
7
7
|
const listTrail = trail('gear.list', {
|
|
8
|
-
|
|
8
|
+
implementation: () => Result.ok([]),
|
|
9
9
|
input: z.object({}),
|
|
10
10
|
output: z.array(z.string()),
|
|
11
11
|
});
|
|
@@ -25,7 +25,7 @@ export const usersManage = trail("users.manage", {
|
|
|
25
25
|
input: z.object({
|
|
26
26
|
action: z.enum(["create", "delete"]),
|
|
27
27
|
}),
|
|
28
|
-
|
|
28
|
+
implementation: async (input) => {
|
|
29
29
|
switch (input.action) {
|
|
30
30
|
case "create":
|
|
31
31
|
return Result.ok({ created: true });
|
|
@@ -10,7 +10,7 @@ const invoicePaidWebhook = webhook('webhook.invoice.paid', {
|
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
const webhookConsumer = trail('invoice.audit-webhook', {
|
|
13
|
-
|
|
13
|
+
implementation: () => Result.ok({ ok: true }),
|
|
14
14
|
input: z.object({ invoiceId: z.string() }),
|
|
15
15
|
on: [invoicePaidWebhook],
|
|
16
16
|
output: z.object({ ok: z.boolean() }),
|
|
@@ -22,24 +22,24 @@ const invoiceCreated = signal('invoice.created', {
|
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
const signalProducer = trail('invoice.create', {
|
|
25
|
-
|
|
25
|
+
fires: [invoiceCreated],
|
|
26
|
+
implementation: async (_input, ctx) => {
|
|
26
27
|
await ctx.fire?.(invoiceCreated, { invoiceId: 'inv_1' });
|
|
27
28
|
return Result.ok({ invoiceId: 'inv_1' });
|
|
28
29
|
},
|
|
29
|
-
fires: [invoiceCreated],
|
|
30
30
|
input: z.object({}),
|
|
31
31
|
output: z.object({ invoiceId: z.string() }),
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
const signalConsumer = trail('invoice.index', {
|
|
35
|
-
|
|
35
|
+
implementation: () => Result.ok({ ok: true }),
|
|
36
36
|
input: z.object({ invoiceId: z.string() }),
|
|
37
37
|
on: [invoiceCreated],
|
|
38
38
|
output: z.object({ ok: z.boolean() }),
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
const scheduleConsumer = trail('invoice.reconcile', {
|
|
42
|
-
|
|
42
|
+
implementation: () => Result.ok({ ok: true }),
|
|
43
43
|
input: z.object({}),
|
|
44
44
|
on: [schedule('schedule.invoice.reconcile', { cron: '0 * * * *' })],
|
|
45
45
|
output: z.object({ ok: z.boolean() }),
|
|
@@ -5,7 +5,6 @@ import { validDetourContract } from '../rules/valid-detour-contract.js';
|
|
|
5
5
|
import { wrapTopoRule } from './wrap-rule.js';
|
|
6
6
|
|
|
7
7
|
const validTrail = trail('entity.save', {
|
|
8
|
-
blaze: () => Result.ok({ ok: true }),
|
|
9
8
|
detours: [
|
|
10
9
|
{
|
|
11
10
|
on: ConflictError,
|
|
@@ -15,6 +14,7 @@ const validTrail = trail('entity.save', {
|
|
|
15
14
|
},
|
|
16
15
|
},
|
|
17
16
|
],
|
|
17
|
+
implementation: () => Result.ok({ ok: true }),
|
|
18
18
|
input: z.object({}),
|
|
19
19
|
output: z.object({ ok: z.boolean() }),
|
|
20
20
|
});
|
|
@@ -5,7 +5,7 @@ import { versionGap } from '../rules/trail-versioning-topo.js';
|
|
|
5
5
|
import { wrapTopoRule } from './wrap-rule.js';
|
|
6
6
|
|
|
7
7
|
const versionedTrail = trail('version.gap.clean', {
|
|
8
|
-
|
|
8
|
+
implementation: () => Result.ok({ ok: true }),
|
|
9
9
|
input: z.object({}),
|
|
10
10
|
output: z.object({ ok: z.boolean() }),
|
|
11
11
|
version: 2,
|
|
@@ -9,7 +9,7 @@ export const versionPinnedComposeTrail = wrapRule({
|
|
|
9
9
|
filePath: 'src/trails/current.ts',
|
|
10
10
|
sourceCode: `
|
|
11
11
|
trail('current.parent', {
|
|
12
|
-
|
|
12
|
+
implementation: async (_input, ctx) => {
|
|
13
13
|
await ctx.compose('current.child', {});
|
|
14
14
|
return Result.ok({});
|
|
15
15
|
},
|
|
@@ -5,7 +5,7 @@ import { versionWithoutExamples } from '../rules/trail-versioning-topo.js';
|
|
|
5
5
|
import { wrapTopoRule } from './wrap-rule.js';
|
|
6
6
|
|
|
7
7
|
const archivedWithoutExamples = trail('version.examples.archived', {
|
|
8
|
-
|
|
8
|
+
implementation: () => Result.ok({ ok: true }),
|
|
9
9
|
input: z.object({}),
|
|
10
10
|
output: z.object({ ok: z.boolean() }),
|
|
11
11
|
version: 2,
|
|
@@ -28,7 +28,7 @@ export const wardenRulesUseAstTrail = wrapRule({
|
|
|
28
28
|
filePath: fakeRulePath,
|
|
29
29
|
line: 1,
|
|
30
30
|
message:
|
|
31
|
-
'warden-rules-use-ast: sourceCode.split(...) treats source text as a string. Warden rules must inspect
|
|
31
|
+
'warden-rules-use-ast: sourceCode.split(...) treats source text as a string. Warden rules must inspect source code via @ontrails/source helpers, not regex-scan raw source text. Use findStringLiterals, findTrailDefinitions, findConfigProperty, or a similar AST walker. Raw-text scanning produces false positives on string literals, template payloads, and docstrings — see TRL-335, ADR-0036.',
|
|
32
32
|
rule: 'warden-rules-use-ast',
|
|
33
33
|
severity: 'error',
|
|
34
34
|
},
|
|
@@ -47,7 +47,7 @@ export const wardenRulesUseAstTrail = wrapRule({
|
|
|
47
47
|
filePath: fakeRulePath,
|
|
48
48
|
line: 1,
|
|
49
49
|
message:
|
|
50
|
-
'warden-rules-use-ast: raw AST node-field cast should use typed helpers from
|
|
50
|
+
'warden-rules-use-ast: raw AST node-field cast should use typed helpers from the shared source helpers (callee -> getNodeCallee). Raw node-field casts drift from the shared source helper surface.',
|
|
51
51
|
rule: 'warden-rules-use-ast',
|
|
52
52
|
severity: 'warn',
|
|
53
53
|
},
|
|
@@ -10,14 +10,14 @@ const paymentWebhook = webhook('webhook.payment.received', {
|
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
const paymentReceiver = trail('payment.receive', {
|
|
13
|
-
|
|
13
|
+
implementation: () => Result.ok({ ok: true }),
|
|
14
14
|
input: z.object({ paymentId: z.string() }),
|
|
15
15
|
on: [paymentWebhook],
|
|
16
16
|
output: z.object({ ok: z.boolean() }),
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
const directRoute = trail('webhooks.payment', {
|
|
20
|
-
|
|
20
|
+
implementation: () => Result.ok({ ok: true }),
|
|
21
21
|
input: z.object({}),
|
|
22
22
|
output: z.object({ ok: z.boolean() }),
|
|
23
23
|
});
|
package/src/trails/wrap-rule.ts
CHANGED
|
@@ -54,10 +54,10 @@ const buildProjectContext = (input: ProjectAwareRuleInput): ProjectContext => ({
|
|
|
54
54
|
...(input.authoredMcpSurfaceBindingSets
|
|
55
55
|
? { authoredMcpSurfaceBindingSets: input.authoredMcpSurfaceBindingSets }
|
|
56
56
|
: {}),
|
|
57
|
-
...(input.
|
|
57
|
+
...(input.entityReferencesByName
|
|
58
58
|
? {
|
|
59
|
-
|
|
60
|
-
Object.entries(input.
|
|
59
|
+
entityReferencesByName: new Map(
|
|
60
|
+
Object.entries(input.entityReferencesByName)
|
|
61
61
|
),
|
|
62
62
|
}
|
|
63
63
|
: {}),
|
|
@@ -74,8 +74,8 @@ const buildProjectContext = (input: ProjectAwareRuleInput): ProjectContext => ({
|
|
|
74
74
|
),
|
|
75
75
|
}
|
|
76
76
|
: {}),
|
|
77
|
-
...(input.
|
|
78
|
-
? {
|
|
77
|
+
...(input.knownEntityIds
|
|
78
|
+
? { knownEntityIds: new Set(input.knownEntityIds) }
|
|
79
79
|
: {}),
|
|
80
80
|
...(input.importResolutionsByFile
|
|
81
81
|
? {
|
|
@@ -145,7 +145,12 @@ export function wrapRule(
|
|
|
145
145
|
if (isProjectAware) {
|
|
146
146
|
const projectAwareRule = rule as ProjectAwareWardenRule;
|
|
147
147
|
return trail(`warden.rule.${rule.name}`, {
|
|
148
|
-
|
|
148
|
+
description: rule.description,
|
|
149
|
+
examples: examples as Trail<
|
|
150
|
+
ProjectAwareRuleInput,
|
|
151
|
+
RuleOutput
|
|
152
|
+
>['examples'],
|
|
153
|
+
implementation: (input: ProjectAwareRuleInput) => {
|
|
149
154
|
const diagnostics = projectAwareRule.checkWithContext(
|
|
150
155
|
input.sourceCode,
|
|
151
156
|
input.filePath,
|
|
@@ -153,11 +158,6 @@ export function wrapRule(
|
|
|
153
158
|
);
|
|
154
159
|
return Result.ok({ diagnostics: [...diagnostics] });
|
|
155
160
|
},
|
|
156
|
-
description: rule.description,
|
|
157
|
-
examples: examples as Trail<
|
|
158
|
-
ProjectAwareRuleInput,
|
|
159
|
-
RuleOutput
|
|
160
|
-
>['examples'],
|
|
161
161
|
input: projectAwareRuleInput,
|
|
162
162
|
intent: 'read',
|
|
163
163
|
meta: buildRuleMeta(rule),
|
|
@@ -166,12 +166,12 @@ export function wrapRule(
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
return trail(`warden.rule.${rule.name}`, {
|
|
169
|
-
|
|
169
|
+
description: rule.description,
|
|
170
|
+
examples: examples as Trail<RuleInput, RuleOutput>['examples'],
|
|
171
|
+
implementation: (input: RuleInput) => {
|
|
170
172
|
const diagnostics = rule.check(input.sourceCode, input.filePath);
|
|
171
173
|
return Result.ok({ diagnostics: [...diagnostics] });
|
|
172
174
|
},
|
|
173
|
-
description: rule.description,
|
|
174
|
-
examples: examples as Trail<RuleInput, RuleOutput>['examples'],
|
|
175
175
|
input: ruleInput,
|
|
176
176
|
intent: 'read',
|
|
177
177
|
meta: buildRuleMeta(rule),
|
|
@@ -198,7 +198,9 @@ export const wrapTopoRule = (
|
|
|
198
198
|
): Trail<TopoAwareRuleInput, RuleOutput> => {
|
|
199
199
|
const { rule, examples } = options;
|
|
200
200
|
return trail(`warden.rule.${rule.name}`, {
|
|
201
|
-
|
|
201
|
+
description: rule.description,
|
|
202
|
+
examples,
|
|
203
|
+
implementation: async (input: TopoAwareRuleInput) => {
|
|
202
204
|
try {
|
|
203
205
|
const diagnostics = await rule.checkTopo(input.topo, {
|
|
204
206
|
graph: input.graph,
|
|
@@ -214,8 +216,6 @@ export const wrapTopoRule = (
|
|
|
214
216
|
);
|
|
215
217
|
}
|
|
216
218
|
},
|
|
217
|
-
description: rule.description,
|
|
218
|
-
examples,
|
|
219
219
|
input: topoAwareRuleInput,
|
|
220
220
|
intent: 'read',
|
|
221
221
|
meta: buildRuleMeta(rule),
|