104 lines
3.3 KiB
TypeScript
104 lines
3.3 KiB
TypeScript
/**
|
|
* A policy that must hold in the running codebase, not merely in a document.
|
|
*
|
|
* Policy gates written as shell scripts tend to share one engine and differ
|
|
* only in their patterns. An org-scoping gate copied across several apps can
|
|
* legitimately end up with a distinct copy per app, and that divergence is
|
|
* CORRECT, not drift: one app scopes by an `org_id` tenant column, another has
|
|
* no tenant columns at all and authorises via RBAC capability checks. Forcing
|
|
* one script would break one of them.
|
|
*
|
|
* So the engine is shared and the policy is data.
|
|
*/
|
|
|
|
export interface PolicyRule {
|
|
/** Stable id, used in output and to reference a rule. */
|
|
id: string
|
|
/** One line: what must be true. */
|
|
description: string
|
|
|
|
/**
|
|
* Only files whose path matches one of these substrings/patterns are
|
|
* considered. Kept deliberately simple — a rule that needs a real glob
|
|
* engine is usually a rule that wants narrowing instead.
|
|
*/
|
|
pathIncludes: string[]
|
|
|
|
/**
|
|
* The file is SUBJECT to this rule only if its content matches. This is the
|
|
* risky construct — a service-role client, an unbounded `.in()`, and so on.
|
|
*/
|
|
trigger: RegExp
|
|
|
|
/**
|
|
* Any match here makes the file compliant. Multiple entries are OR-ed,
|
|
* because there is usually more than one legitimate way to satisfy a policy.
|
|
*/
|
|
allow: RegExp[]
|
|
|
|
/**
|
|
* A comment marker that documents a deliberate exemption, e.g.
|
|
* `org-scoping-exempt:`. Exemptions are part of a workable gate: without one,
|
|
* the legitimate cross-tenant cron job forces someone to disable the check
|
|
* entirely.
|
|
*/
|
|
exemptMarker: string
|
|
|
|
/** Lines printed under a violation telling the author how to fix it. */
|
|
remedy: string[]
|
|
}
|
|
|
|
export interface SourceFile {
|
|
path: string
|
|
content: string
|
|
}
|
|
|
|
export interface Violation {
|
|
ruleId: string
|
|
path: string
|
|
/** Why it failed, in one line. */
|
|
reason: string
|
|
/**
|
|
* 1-indexed line where the trigger matched — the construct that made this
|
|
* file subject to the rule. A violation that names only the file sends the
|
|
* reader hunting; this points at the line that did it.
|
|
*/
|
|
line?: number
|
|
/** The matched line, trimmed. */
|
|
excerpt?: string
|
|
}
|
|
|
|
export interface RuleResult {
|
|
rule: PolicyRule
|
|
/** Files the rule actually examined (matched pathIncludes). */
|
|
considered: number
|
|
/** Files that were subject to it (matched the trigger). */
|
|
subject: number
|
|
/** Files excused by an exemption marker. */
|
|
exempted: number
|
|
violations: Violation[]
|
|
/**
|
|
* Paths that were subject to the rule and satisfied it.
|
|
*
|
|
* Retained so a PASS can cite what it checked. A gate that reports "✓ 3
|
|
* files checked" without naming them is an asserted result, and this
|
|
* library's whole premise is that written policy and enforced policy are
|
|
* different things.
|
|
*/
|
|
compliantPaths: string[]
|
|
/** Paths excused by the exemption marker — counted, never hidden. */
|
|
exemptedPaths: string[]
|
|
}
|
|
|
|
export interface PolicyReport {
|
|
results: RuleResult[]
|
|
violations: Violation[]
|
|
passed: boolean
|
|
/**
|
|
* True when there was nothing to check. Distinguished from `passed` on
|
|
* purpose: "no changed files matched" is not evidence the policy holds, and
|
|
* a report that conflates them is the same lie as a test that cannot fail.
|
|
*/
|
|
vacuous: boolean
|
|
}
|