@ontrails/warden 1.0.0-beta.42 → 1.0.0-beta.45
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 +69 -0
- package/package.json +15 -10
- package/src/adapter-check.ts +1 -1
- package/src/cli.ts +81 -5
- package/src/index.ts +17 -4
- package/src/regrade-history.ts +599 -0
- package/src/rules/cli-command-route-coherence.ts +6 -6
- package/src/rules/draft-visible-debt.ts +1 -1
- package/src/rules/duplicate-exported-symbol.ts +4 -43
- package/src/rules/governed-symbol-residue.ts +146 -53
- package/src/rules/governed-vocabulary-permutation-watch.ts +76 -0
- package/src/rules/implementation-returns-result.ts +99 -9
- package/src/rules/index.ts +17 -6
- package/src/rules/layer-field-name-drift.ts +2 -2
- package/src/rules/{library-projection-coherence.ts → library-render-coherence.ts} +11 -14
- package/src/rules/metadata.ts +51 -14
- package/src/rules/no-redundant-result-error-wrap.ts +15 -17
- package/src/rules/no-retired-cross-vocabulary.ts +0 -1
- package/src/rules/{owner-projection-parity.ts → owner-render-parity.ts} +18 -18
- package/src/rules/public-internal-deep-imports.ts +1 -3
- package/src/rules/public-output-schema.ts +1 -1
- package/src/rules/registry-names.ts +6 -4
- package/src/rules/retired-vocabulary.ts +775 -41
- package/src/rules/surface-overlay-coherence.ts +1 -1
- package/src/rules/trail-versioning-source.ts +1 -1
- package/src/rules/trailhead-override-divergence.ts +4 -4
- package/src/rules/types.ts +51 -5
- package/src/trails/governed-vocabulary-permutation-watch.trail.ts +16 -0
- package/src/trails/index.ts +3 -2
- package/src/trails/layer-field-name-drift.trail.ts +1 -1
- package/src/trails/{library-projection-coherence.trail.ts → library-render-coherence.trail.ts} +6 -6
- package/src/trails/{owner-projection-parity.trail.ts → owner-render-parity.trail.ts} +4 -4
- package/src/trails/public-output-schema.trail.ts +1 -1
- package/src/trails/run.ts +44 -2
- package/src/trails/schema.ts +41 -0
- package/src/trails/wrap-rule.ts +44 -2
|
@@ -147,7 +147,7 @@ const cliCollisionDiagnostics = (
|
|
|
147
147
|
routeByName.set(segment, { kind: 'canonical', trailId: entry.id });
|
|
148
148
|
}
|
|
149
149
|
for (const route of entry.cli?.routes ?? []) {
|
|
150
|
-
// Surface-sourced alias routes are the
|
|
150
|
+
// Surface-sourced alias routes are the renderings of the overlay
|
|
151
151
|
// bindings themselves (TRL-1207), so a binding can never "shadow"
|
|
152
152
|
// one — only canonical paths and trail-owned aliases are real
|
|
153
153
|
// entries a binding name could collide with.
|
|
@@ -1070,7 +1070,7 @@ export const markerSchemaUnsupported: WardenRule = {
|
|
|
1070
1070
|
return diagnostics;
|
|
1071
1071
|
},
|
|
1072
1072
|
description:
|
|
1073
|
-
'Reject versioned trail schema constructs that cannot be
|
|
1073
|
+
'Reject versioned trail schema constructs that cannot be rendered into stable marker contracts.',
|
|
1074
1074
|
name: MARKER_SCHEMA_UNSUPPORTED,
|
|
1075
1075
|
severity: 'error',
|
|
1076
1076
|
};
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* The overlay is the authored, lockable default; a call-site trailhead map
|
|
6
6
|
* is a supported override-in-context that wins at runtime. Divergence between
|
|
7
7
|
* the two is legal but must be visible: an agent reading the committed lock
|
|
8
|
-
* would otherwise trust grouped entries the running surface does not
|
|
8
|
+
* would otherwise trust grouped entries the running surface does not render.
|
|
9
9
|
*
|
|
10
10
|
* Rule-kind note: no Warden rule kind currently sees both the serialized
|
|
11
11
|
* graph overlays and source ASTs directly. This rule is the closest honest
|
|
12
|
-
* shape — a
|
|
12
|
+
* shape — a render-aware source rule whose `ProjectContext` carries
|
|
13
13
|
* per-app authored `mcp` binding sets resolved from the run's topo targets
|
|
14
14
|
* (graph overlays when available, else app-module overlay registrations).
|
|
15
15
|
* A call-site map is attributed to an app only when one of its literal
|
|
@@ -256,7 +256,7 @@ const diagnoseTrailheadMap = (
|
|
|
256
256
|
sourceCode,
|
|
257
257
|
filePath,
|
|
258
258
|
trailheadMap,
|
|
259
|
-
`App "${set.appName}"'s surfaceOverlay mcp binding "${name}" is not carried by this call-site trailhead map, so it will not be
|
|
259
|
+
`App "${set.appName}"'s surfaceOverlay mcp binding "${name}" is not carried by this call-site trailhead map, so it will not be rendered at runtime while the committed lock still advertises it. Add "${name}" to the call-site map or remove it from the authored overlay.`
|
|
260
260
|
)
|
|
261
261
|
);
|
|
262
262
|
}
|
|
@@ -271,7 +271,7 @@ const diagnoseTrailheadMap = (
|
|
|
271
271
|
*/
|
|
272
272
|
export const trailheadOverrideDivergence: ProjectAwareWardenRule = {
|
|
273
273
|
check(): readonly WardenDiagnostic[] {
|
|
274
|
-
// Without
|
|
274
|
+
// Without render context there are no authored bindings to compare
|
|
275
275
|
// against; stay silent instead of guessing.
|
|
276
276
|
return [];
|
|
277
277
|
},
|
package/src/rules/types.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { TopoGraph } from '@ontrails/topography';
|
|
|
11
11
|
import type { WardenDepth } from '../config.js';
|
|
12
12
|
import type { WardenImportResolution } from '../resolve.js';
|
|
13
13
|
import type { WardenPublicWorkspace } from '../workspaces.js';
|
|
14
|
+
import type { GovernedVocabularyHistoryProvenance } from './retired-vocabulary.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Severity level for warden diagnostics.
|
|
@@ -83,7 +84,7 @@ export interface WardenGuidanceLink {
|
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
/**
|
|
86
|
-
* Structured remediation guidance that can be rendered for humans or
|
|
87
|
+
* Structured remediation guidance that can be rendered for humans or rendered
|
|
87
88
|
* into agent-facing manifests without scraping diagnostic prose.
|
|
88
89
|
*/
|
|
89
90
|
export interface WardenGuidance {
|
|
@@ -143,7 +144,7 @@ export interface WardenFixEdit {
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
/**
|
|
146
|
-
* Source targets a fix class can inspect when
|
|
147
|
+
* Source targets a fix class can inspect when rendered into downstream tools.
|
|
147
148
|
*
|
|
148
149
|
* Warden itself decides which committed files it scans. This metadata is for
|
|
149
150
|
* consumers such as Regrade that need to derive a narrower collection before
|
|
@@ -179,7 +180,7 @@ export interface WardenFix {
|
|
|
179
180
|
}
|
|
180
181
|
|
|
181
182
|
/**
|
|
182
|
-
* Per-rule fix capability,
|
|
183
|
+
* Per-rule fix capability, rendered into the guide/manifest.
|
|
183
184
|
*
|
|
184
185
|
* Declares that a rule can emit {@link WardenFix} metadata and the default
|
|
185
186
|
* safety for its fixes, so `warden --help`, the guide, and agent surfaces can
|
|
@@ -191,7 +192,7 @@ export interface WardenFixCapability {
|
|
|
191
192
|
readonly class: WardenFixClass;
|
|
192
193
|
/** Default safety for fixes this rule emits. */
|
|
193
194
|
readonly safety: WardenFixSafety;
|
|
194
|
-
/** Downstream scan targets for tools that
|
|
195
|
+
/** Downstream scan targets for tools that render this fix capability. */
|
|
195
196
|
readonly scanTargets?: WardenFixScanTargets | undefined;
|
|
196
197
|
}
|
|
197
198
|
|
|
@@ -213,7 +214,7 @@ export interface WardenRuleMetadata {
|
|
|
213
214
|
readonly tier: WardenRuleTier;
|
|
214
215
|
/** Structured remediation guidance for diagnostics emitted by this rule. */
|
|
215
216
|
readonly guidance?: WardenGuidance | undefined;
|
|
216
|
-
/** Declares that this rule can emit fix metadata, for guide
|
|
217
|
+
/** Declares that this rule can emit fix metadata, for guide rendering. */
|
|
217
218
|
readonly fix?: WardenFixCapability | undefined;
|
|
218
219
|
}
|
|
219
220
|
|
|
@@ -280,6 +281,10 @@ export interface WardenRule {
|
|
|
280
281
|
readonly description: string;
|
|
281
282
|
/** Optional inline classification. Built-ins are classified by registry. */
|
|
282
283
|
readonly metadata?: WardenRuleMetadata | undefined;
|
|
284
|
+
/** Source file kinds this rule explicitly supports beyond TypeScript. */
|
|
285
|
+
readonly sourceKinds?:
|
|
286
|
+
| readonly ('documentation' | 'text' | 'typescript')[]
|
|
287
|
+
| undefined;
|
|
283
288
|
/** Run the rule against source code and return any diagnostics */
|
|
284
289
|
readonly check: (
|
|
285
290
|
sourceCode: string,
|
|
@@ -304,6 +309,16 @@ export interface AuthoredMcpSurfaceBindingSet {
|
|
|
304
309
|
* Options for compose-file rules that need knowledge of all trail IDs in a project.
|
|
305
310
|
*/
|
|
306
311
|
export interface ProjectContext {
|
|
312
|
+
/** Whether this project owns the governed transition registry and must prove completed migrations. */
|
|
313
|
+
readonly governedVocabularyHistoryRequired?: boolean | undefined;
|
|
314
|
+
/** Validated committed Regrade history evidence keyed by governed transition. */
|
|
315
|
+
readonly governedVocabularyHistoryByTransitionId?:
|
|
316
|
+
| ReadonlyMap<string, GovernedVocabularyHistoryEvidence>
|
|
317
|
+
| undefined;
|
|
318
|
+
/** Invalid committed history artifacts observed while building context. */
|
|
319
|
+
readonly governedVocabularyHistoryIssues?:
|
|
320
|
+
| readonly GovernedVocabularyHistoryIssue[]
|
|
321
|
+
| undefined;
|
|
307
322
|
/**
|
|
308
323
|
* Per-app authored `mcp` surface bindings, resolved from the project's
|
|
309
324
|
* topo targets (the serialized graph overlays when available, else the
|
|
@@ -368,10 +383,41 @@ export interface ProjectContext {
|
|
|
368
383
|
readonly crudCoverageByEntity?: ReadonlyMap<string, ReadonlySet<string>>;
|
|
369
384
|
}
|
|
370
385
|
|
|
386
|
+
export interface GovernedVocabularyHistoryEvidence {
|
|
387
|
+
readonly caseSensitive: boolean;
|
|
388
|
+
readonly id: string;
|
|
389
|
+
readonly latestFormObservations: readonly GovernedVocabularyHistoryFormObservation[];
|
|
390
|
+
readonly path: string;
|
|
391
|
+
readonly provenance?: GovernedVocabularyHistoryProvenance;
|
|
392
|
+
readonly runCount: number;
|
|
393
|
+
readonly transitionId: string;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** One vocabulary-form observation preserved in committed Regrade history. */
|
|
397
|
+
export interface GovernedVocabularyHistoryFormObservation {
|
|
398
|
+
readonly disposition: string;
|
|
399
|
+
readonly form: string;
|
|
400
|
+
readonly line: number;
|
|
401
|
+
readonly path: string;
|
|
402
|
+
readonly reason: string;
|
|
403
|
+
readonly scopeTier?: 'in-scope' | 'policy-classified' | undefined;
|
|
404
|
+
readonly verdict: 'applied' | 'deferred' | 'modified' | 'skipped';
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export interface GovernedVocabularyHistoryIssue {
|
|
408
|
+
readonly message: string;
|
|
409
|
+
readonly path: string;
|
|
410
|
+
readonly transitionId?: string;
|
|
411
|
+
}
|
|
412
|
+
|
|
371
413
|
/**
|
|
372
414
|
* A project-aware rule that requires knowledge of all trail IDs.
|
|
373
415
|
*/
|
|
374
416
|
export interface ProjectAwareWardenRule extends WardenRule {
|
|
417
|
+
/** Run project-wide validation once, independently of any source file. */
|
|
418
|
+
readonly checkProject?: (
|
|
419
|
+
context: ProjectContext
|
|
420
|
+
) => readonly WardenDiagnostic[];
|
|
375
421
|
/** Run the rule with project-level context */
|
|
376
422
|
readonly checkWithContext: (
|
|
377
423
|
sourceCode: string,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { governedVocabularyPermutationWatch } from '../rules/governed-vocabulary-permutation-watch.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const governedVocabularyPermutationWatchTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'packages/example/src/trails/play.ts',
|
|
10
|
+
sourceCode: 'const entityId = "track";\n',
|
|
11
|
+
},
|
|
12
|
+
name: 'Project history watch runs at the project boundary',
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
rule: governedVocabularyPermutationWatch,
|
|
16
|
+
});
|
package/src/trails/index.ts
CHANGED
|
@@ -17,12 +17,13 @@ export { exampleValidTrail } from './example-valid.trail.js';
|
|
|
17
17
|
export { firesDeclarationsTrail } from './fires-declarations.trail.js';
|
|
18
18
|
export { forkWithoutPreservedImplementationTrail } from './fork-without-preserved-implementation.trail.js';
|
|
19
19
|
export { governedSymbolResidueTrail } from './governed-symbol-residue.trail.js';
|
|
20
|
+
export { governedVocabularyPermutationWatchTrail } from './governed-vocabulary-permutation-watch.trail.js';
|
|
20
21
|
export { implementationReturnsResultTrail } from './implementation-returns-result.trail.js';
|
|
21
22
|
export { incompleteAccessorForStandardOpTrail } from './incomplete-accessor-for-standard-op.trail.js';
|
|
22
23
|
export { incompleteCrudTrail } from './incomplete-crud.trail.js';
|
|
23
24
|
export { intentPropagationTrail } from './intent-propagation.trail.js';
|
|
24
25
|
export { layerFieldNameDriftTrail } from './layer-field-name-drift.trail.js';
|
|
25
|
-
export {
|
|
26
|
+
export { libraryRenderCoherenceTrail } from './library-render-coherence.trail.js';
|
|
26
27
|
export { markerSchemaUnsupportedTrail } from './marker-schema-unsupported.trail.js';
|
|
27
28
|
export { missingVisibilityTrail } from './missing-visibility.trail.js';
|
|
28
29
|
export { missingReconcileTrail } from './missing-reconcile.trail.js';
|
|
@@ -40,7 +41,7 @@ export { noThrowInDetourRecoverTrail } from './no-throw-in-detour-recover.trail.
|
|
|
40
41
|
export { noThrowInImplementationTrail } from './no-throw-in-implementation.trail.js';
|
|
41
42
|
export { noTopLevelSurfaceTrail } from './no-top-level-surface.trail.js';
|
|
42
43
|
export { orphanedSignalTrail } from './orphaned-signal.trail.js';
|
|
43
|
-
export {
|
|
44
|
+
export { ownerRenderParityTrail } from './owner-render-parity.trail.js';
|
|
44
45
|
export { pendingForceTrail } from './pending-force.trail.js';
|
|
45
46
|
export { permitGovernanceTrail } from './permit-governance.trail.js';
|
|
46
47
|
export { preferSchemaInferenceTrail } from './prefer-schema-inference.trail.js';
|
|
@@ -21,7 +21,7 @@ const collides = LAYER_FIELD_RESERVED_NAMES.has('all');
|
|
|
21
21
|
filePath: 'packages/cli/src/build.ts',
|
|
22
22
|
line: 1,
|
|
23
23
|
message:
|
|
24
|
-
'layer-field-name-drift: surface-local reserved name set "META_FLAG_CANDIDATES" can make layer input fields
|
|
24
|
+
'layer-field-name-drift: surface-local reserved name set "META_FLAG_CANDIDATES" can make layer input fields render differently across surfaces. Import LAYER_FIELD_RESERVED_NAMES from @ontrails/core instead.',
|
|
25
25
|
rule: 'layer-field-name-drift',
|
|
26
26
|
severity: 'error',
|
|
27
27
|
},
|
package/src/trails/{library-projection-coherence.trail.ts → library-render-coherence.trail.ts}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Result, topo, trail } from '@ontrails/core';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { libraryRenderCoherence } from '../rules/library-render-coherence.js';
|
|
5
5
|
import { wrapTopoRule } from './wrap-rule.js';
|
|
6
6
|
|
|
7
7
|
const output = z.object({ ok: z.boolean() });
|
|
@@ -18,7 +18,7 @@ const kebab = trail('widget-ping', {
|
|
|
18
18
|
output,
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
export const
|
|
21
|
+
export const libraryRenderCoherenceTrail = wrapTopoRule({
|
|
22
22
|
examples: [
|
|
23
23
|
{
|
|
24
24
|
expected: {
|
|
@@ -27,17 +27,17 @@ export const libraryProjectionCoherenceTrail = wrapTopoRule({
|
|
|
27
27
|
filePath: '<topo>',
|
|
28
28
|
line: 1,
|
|
29
29
|
message:
|
|
30
|
-
'Library
|
|
31
|
-
rule: 'library-
|
|
30
|
+
'Library rendering export collision on "widgetPing": trails "widget-ping", "widget.ping" derive the same package export. Rename one trail or add a library export override before materializing the generated package.',
|
|
31
|
+
rule: 'library-render-coherence',
|
|
32
32
|
severity: 'error',
|
|
33
33
|
},
|
|
34
34
|
],
|
|
35
35
|
},
|
|
36
36
|
input: {
|
|
37
|
-
topo: topo('library-
|
|
37
|
+
topo: topo('library-render-coherence', { dotted, kebab }),
|
|
38
38
|
},
|
|
39
39
|
name: 'Library export collision',
|
|
40
40
|
},
|
|
41
41
|
],
|
|
42
|
-
rule:
|
|
42
|
+
rule: libraryRenderCoherence,
|
|
43
43
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ownerRenderParity } from '../rules/owner-render-parity.js';
|
|
4
4
|
import { wrapRule } from './wrap-rule.js';
|
|
5
5
|
|
|
6
|
-
export const
|
|
6
|
+
export const ownerRenderParityTrail = wrapRule({
|
|
7
7
|
examples: [
|
|
8
8
|
{
|
|
9
9
|
expected: { diagnostics: [] },
|
|
@@ -19,8 +19,8 @@ export const httpMethodByIntent = {
|
|
|
19
19
|
write: 'POST',
|
|
20
20
|
} as const satisfies Record<Intent, 'GET' | 'POST' | 'DELETE'>;`,
|
|
21
21
|
},
|
|
22
|
-
name: 'HTTP method
|
|
22
|
+
name: 'HTTP method rendering covers core intent values',
|
|
23
23
|
},
|
|
24
24
|
],
|
|
25
|
-
rule:
|
|
25
|
+
rule: ownerRenderParity,
|
|
26
26
|
});
|
|
@@ -39,7 +39,7 @@ export const publicOutputSchemaTrail = wrapTopoRule({
|
|
|
39
39
|
filePath: '<topo>',
|
|
40
40
|
line: 1,
|
|
41
41
|
message:
|
|
42
|
-
'Trail "report.missing" is visible to public MCP/HTTP surface
|
|
42
|
+
'Trail "report.missing" is visible to public MCP/HTTP surface rendering but does not declare an output schema. Add an explicit output schema, or mark the trail visibility as internal if it is composition-only.',
|
|
43
43
|
rule: 'public-output-schema',
|
|
44
44
|
severity: 'error',
|
|
45
45
|
},
|
package/src/trails/run.ts
CHANGED
|
@@ -9,12 +9,20 @@
|
|
|
9
9
|
import type { Intent, Topo } from '@ontrails/core';
|
|
10
10
|
import { run } from '@ontrails/core';
|
|
11
11
|
|
|
12
|
-
import { wardenTopoRules } from '../rules/index.js';
|
|
13
|
-
import type {
|
|
12
|
+
import { wardenRules, wardenTopoRules } from '../rules/index.js';
|
|
13
|
+
import type {
|
|
14
|
+
GovernedVocabularyHistoryEvidence,
|
|
15
|
+
GovernedVocabularyHistoryIssue,
|
|
16
|
+
ProjectAwareWardenRule,
|
|
17
|
+
WardenDiagnostic,
|
|
18
|
+
WardenRule,
|
|
19
|
+
} from '../rules/types.js';
|
|
14
20
|
import type { WardenImportResolution } from '../resolve.js';
|
|
15
21
|
import type { WardenPublicWorkspace } from '../workspaces.js';
|
|
22
|
+
import { projectAwareRuleInput } from './schema.js';
|
|
16
23
|
import type { RuleOutput } from './schema.js';
|
|
17
24
|
import { wardenTopo } from './topo.js';
|
|
25
|
+
import { buildProjectContext } from './wrap-rule.js';
|
|
18
26
|
|
|
19
27
|
/**
|
|
20
28
|
* Run all file-scoped warden rule trails for a given file and collect diagnostics.
|
|
@@ -34,6 +42,9 @@ const appendDiagnostics = (
|
|
|
34
42
|
type TrailIntentMap = Readonly<Record<string, Intent>>;
|
|
35
43
|
|
|
36
44
|
interface ProjectRuleOptions {
|
|
45
|
+
readonly governedVocabularyHistories?: readonly GovernedVocabularyHistoryEvidence[];
|
|
46
|
+
readonly governedVocabularyHistoryIssues?: readonly GovernedVocabularyHistoryIssue[];
|
|
47
|
+
readonly governedVocabularyHistoryRequired?: boolean;
|
|
37
48
|
readonly entityReferencesByName?: Readonly<Record<string, readonly string[]>>;
|
|
38
49
|
readonly composeTargetTrailIds?: readonly string[];
|
|
39
50
|
readonly crudTableIds?: readonly string[];
|
|
@@ -55,6 +66,9 @@ interface ProjectRuleOptions {
|
|
|
55
66
|
}
|
|
56
67
|
|
|
57
68
|
const PROJECT_OPTION_KEYS = [
|
|
69
|
+
'governedVocabularyHistories',
|
|
70
|
+
'governedVocabularyHistoryIssues',
|
|
71
|
+
'governedVocabularyHistoryRequired',
|
|
58
72
|
'entityReferencesByName',
|
|
59
73
|
'composeTargetTrailIds',
|
|
60
74
|
'crudTableIds',
|
|
@@ -135,6 +149,34 @@ export const runWardenTrails = async (
|
|
|
135
149
|
return allDiagnostics;
|
|
136
150
|
};
|
|
137
151
|
|
|
152
|
+
const isProjectAwareRule = (rule: WardenRule): rule is ProjectAwareWardenRule =>
|
|
153
|
+
'checkWithContext' in rule;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Run project-wide built-in Warden diagnostics once for one project context.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* const diagnostics = runProjectWardenRules({
|
|
161
|
+
* governedVocabularyHistories: histories,
|
|
162
|
+
* });
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
export const runProjectWardenRules = (
|
|
166
|
+
options?: ProjectRuleOptions
|
|
167
|
+
): readonly WardenDiagnostic[] => {
|
|
168
|
+
const context = buildProjectContext(
|
|
169
|
+
projectAwareRuleInput.parse(buildRuleInput('<project>', '', options))
|
|
170
|
+
);
|
|
171
|
+
const diagnostics: WardenDiagnostic[] = [];
|
|
172
|
+
for (const rule of wardenRules.values()) {
|
|
173
|
+
if (isProjectAwareRule(rule) && rule.checkProject !== undefined) {
|
|
174
|
+
appendDiagnostics(diagnostics, rule.checkProject(context));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return diagnostics;
|
|
178
|
+
};
|
|
179
|
+
|
|
138
180
|
/**
|
|
139
181
|
* Run the built-in topo-aware warden rule trails once against a resolved topo.
|
|
140
182
|
*
|
package/src/trails/schema.ts
CHANGED
|
@@ -119,6 +119,27 @@ export const authoredMcpSurfaceBindingSetSchema = z.object({
|
|
|
119
119
|
.describe('Trail ids registered in the owning app topo'),
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
+
const governedVocabularyHistoryObservationSchema = z.object({
|
|
123
|
+
disposition: z.string(),
|
|
124
|
+
form: z.string(),
|
|
125
|
+
line: z.number().int().positive(),
|
|
126
|
+
path: z.string(),
|
|
127
|
+
reason: z.string(),
|
|
128
|
+
scopeTier: z.enum(['in-scope', 'policy-classified']).optional(),
|
|
129
|
+
verdict: z.enum(['applied', 'deferred', 'modified', 'skipped']),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const governedVocabularyHistoryEvidenceSchema = z.object({
|
|
133
|
+
caseSensitive: z.boolean(),
|
|
134
|
+
id: z.string(),
|
|
135
|
+
latestFormObservations: z
|
|
136
|
+
.array(governedVocabularyHistoryObservationSchema)
|
|
137
|
+
.readonly(),
|
|
138
|
+
path: z.string(),
|
|
139
|
+
runCount: z.number().int().nonnegative(),
|
|
140
|
+
transitionId: z.string(),
|
|
141
|
+
});
|
|
142
|
+
|
|
122
143
|
export const projectAwareRuleInput = ruleInput.extend({
|
|
123
144
|
authoredMcpSurfaceBindingSets: z
|
|
124
145
|
.array(authoredMcpSurfaceBindingSetSchema)
|
|
@@ -153,6 +174,26 @@ export const projectAwareRuleInput = ruleInput.extend({
|
|
|
153
174
|
.record(z.string(), z.array(exportedSymbolDefinitionSchema))
|
|
154
175
|
.optional()
|
|
155
176
|
.describe('Public exported symbol definitions grouped by exported name'),
|
|
177
|
+
governedVocabularyHistories: z
|
|
178
|
+
.array(governedVocabularyHistoryEvidenceSchema)
|
|
179
|
+
.readonly()
|
|
180
|
+
.optional()
|
|
181
|
+
.describe('Validated committed Regrade histories by governed transition'),
|
|
182
|
+
governedVocabularyHistoryIssues: z
|
|
183
|
+
.array(
|
|
184
|
+
z.object({
|
|
185
|
+
message: z.string(),
|
|
186
|
+
path: z.string(),
|
|
187
|
+
transitionId: z.string().optional(),
|
|
188
|
+
})
|
|
189
|
+
)
|
|
190
|
+
.readonly()
|
|
191
|
+
.optional()
|
|
192
|
+
.describe('Invalid committed Regrade history artifacts'),
|
|
193
|
+
governedVocabularyHistoryRequired: z
|
|
194
|
+
.boolean()
|
|
195
|
+
.optional()
|
|
196
|
+
.describe('Whether the project must prove completed governed migrations'),
|
|
156
197
|
importResolutionsByFile: z
|
|
157
198
|
.record(z.string(), z.array(importResolutionSchema))
|
|
158
199
|
.optional()
|
package/src/trails/wrap-rule.ts
CHANGED
|
@@ -50,7 +50,47 @@ const buildRuleMeta = (rule: WardenRule | TopoAwareWardenRule) => {
|
|
|
50
50
|
};
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
-
const
|
|
53
|
+
const buildGovernedHistoryContext = (
|
|
54
|
+
input: ProjectAwareRuleInput
|
|
55
|
+
): Pick<
|
|
56
|
+
ProjectContext,
|
|
57
|
+
| 'governedVocabularyHistoryByTransitionId'
|
|
58
|
+
| 'governedVocabularyHistoryIssues'
|
|
59
|
+
| 'governedVocabularyHistoryRequired'
|
|
60
|
+
> => ({
|
|
61
|
+
...(input.governedVocabularyHistories
|
|
62
|
+
? {
|
|
63
|
+
governedVocabularyHistoryByTransitionId: new Map(
|
|
64
|
+
input.governedVocabularyHistories.map((history) => [
|
|
65
|
+
history.transitionId,
|
|
66
|
+
history,
|
|
67
|
+
])
|
|
68
|
+
),
|
|
69
|
+
}
|
|
70
|
+
: {}),
|
|
71
|
+
...(input.governedVocabularyHistoryIssues
|
|
72
|
+
? {
|
|
73
|
+
governedVocabularyHistoryIssues:
|
|
74
|
+
input.governedVocabularyHistoryIssues.map((issue) => ({
|
|
75
|
+
message: issue.message,
|
|
76
|
+
path: issue.path,
|
|
77
|
+
...(issue.transitionId === undefined
|
|
78
|
+
? {}
|
|
79
|
+
: { transitionId: issue.transitionId }),
|
|
80
|
+
})),
|
|
81
|
+
}
|
|
82
|
+
: {}),
|
|
83
|
+
...(input.governedVocabularyHistoryRequired === undefined
|
|
84
|
+
? {}
|
|
85
|
+
: {
|
|
86
|
+
governedVocabularyHistoryRequired:
|
|
87
|
+
input.governedVocabularyHistoryRequired,
|
|
88
|
+
}),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const buildProjectContext = (
|
|
92
|
+
input: ProjectAwareRuleInput
|
|
93
|
+
): ProjectContext => ({
|
|
54
94
|
...(input.authoredMcpSurfaceBindingSets
|
|
55
95
|
? { authoredMcpSurfaceBindingSets: input.authoredMcpSurfaceBindingSets }
|
|
56
96
|
: {}),
|
|
@@ -98,6 +138,7 @@ const buildProjectContext = (input: ProjectAwareRuleInput): ProjectContext => ({
|
|
|
98
138
|
),
|
|
99
139
|
}
|
|
100
140
|
: {}),
|
|
141
|
+
...buildGovernedHistoryContext(input),
|
|
101
142
|
...(input.publicWorkspaces
|
|
102
143
|
? { publicWorkspaces: new Map(Object.entries(input.publicWorkspaces)) }
|
|
103
144
|
: {}),
|
|
@@ -151,10 +192,11 @@ export function wrapRule(
|
|
|
151
192
|
RuleOutput
|
|
152
193
|
>['examples'],
|
|
153
194
|
implementation: (input: ProjectAwareRuleInput) => {
|
|
195
|
+
const context = buildProjectContext(input);
|
|
154
196
|
const diagnostics = projectAwareRule.checkWithContext(
|
|
155
197
|
input.sourceCode,
|
|
156
198
|
input.filePath,
|
|
157
|
-
|
|
199
|
+
context
|
|
158
200
|
);
|
|
159
201
|
return Result.ok({ diagnostics: [...diagnostics] });
|
|
160
202
|
},
|