@openauditmodel/cli 0.1.0
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/LICENSE +215 -0
- package/README.md +647 -0
- package/dist/conformance/src/cli.d.ts +20 -0
- package/dist/conformance/src/cli.js +560 -0
- package/dist/conformance/src/cli.js.map +1 -0
- package/dist/conformance/src/format-errors.d.ts +20 -0
- package/dist/conformance/src/format-errors.js +116 -0
- package/dist/conformance/src/format-errors.js.map +1 -0
- package/dist/conformance/src/integrity/canonicalize.d.ts +25 -0
- package/dist/conformance/src/integrity/canonicalize.js +100 -0
- package/dist/conformance/src/integrity/canonicalize.js.map +1 -0
- package/dist/conformance/src/integrity/digest.d.ts +54 -0
- package/dist/conformance/src/integrity/digest.js +126 -0
- package/dist/conformance/src/integrity/digest.js.map +1 -0
- package/dist/conformance/src/integrity/types.d.ts +86 -0
- package/dist/conformance/src/integrity/types.js +36 -0
- package/dist/conformance/src/integrity/types.js.map +1 -0
- package/dist/conformance/src/integrity/verify-chain.d.ts +23 -0
- package/dist/conformance/src/integrity/verify-chain.js +235 -0
- package/dist/conformance/src/integrity/verify-chain.js.map +1 -0
- package/dist/conformance/src/integrity/verify-event.d.ts +29 -0
- package/dist/conformance/src/integrity/verify-event.js +140 -0
- package/dist/conformance/src/integrity/verify-event.js.map +1 -0
- package/dist/conformance/src/privacy/entropy.d.ts +14 -0
- package/dist/conformance/src/privacy/entropy.js +86 -0
- package/dist/conformance/src/privacy/entropy.js.map +1 -0
- package/dist/conformance/src/privacy/field-names.d.ts +36 -0
- package/dist/conformance/src/privacy/field-names.js +121 -0
- package/dist/conformance/src/privacy/field-names.js.map +1 -0
- package/dist/conformance/src/privacy/lint-event.d.ts +18 -0
- package/dist/conformance/src/privacy/lint-event.js +272 -0
- package/dist/conformance/src/privacy/lint-event.js.map +1 -0
- package/dist/conformance/src/privacy/rules.d.ts +44 -0
- package/dist/conformance/src/privacy/rules.js +161 -0
- package/dist/conformance/src/privacy/rules.js.map +1 -0
- package/dist/conformance/src/privacy/safe-formats.d.ts +32 -0
- package/dist/conformance/src/privacy/safe-formats.js +118 -0
- package/dist/conformance/src/privacy/safe-formats.js.map +1 -0
- package/dist/conformance/src/privacy/size-analysis.d.ts +36 -0
- package/dist/conformance/src/privacy/size-analysis.js +87 -0
- package/dist/conformance/src/privacy/size-analysis.js.map +1 -0
- package/dist/conformance/src/privacy/token-patterns.d.ts +24 -0
- package/dist/conformance/src/privacy/token-patterns.js +126 -0
- package/dist/conformance/src/privacy/token-patterns.js.map +1 -0
- package/dist/conformance/src/privacy/traverse.d.ts +33 -0
- package/dist/conformance/src/privacy/traverse.js +61 -0
- package/dist/conformance/src/privacy/traverse.js.map +1 -0
- package/dist/conformance/src/privacy/types.d.ts +58 -0
- package/dist/conformance/src/privacy/types.js +32 -0
- package/dist/conformance/src/privacy/types.js.map +1 -0
- package/dist/conformance/src/privacy/url-analysis.d.ts +40 -0
- package/dist/conformance/src/privacy/url-analysis.js +148 -0
- package/dist/conformance/src/privacy/url-analysis.js.map +1 -0
- package/dist/conformance/src/profiles/check-profile.d.ts +22 -0
- package/dist/conformance/src/profiles/check-profile.js +72 -0
- package/dist/conformance/src/profiles/check-profile.js.map +1 -0
- package/dist/conformance/src/profiles/evaluate-rule.d.ts +27 -0
- package/dist/conformance/src/profiles/evaluate-rule.js +87 -0
- package/dist/conformance/src/profiles/evaluate-rule.js.map +1 -0
- package/dist/conformance/src/profiles/load-profile.d.ts +30 -0
- package/dist/conformance/src/profiles/load-profile.js +95 -0
- package/dist/conformance/src/profiles/load-profile.js.map +1 -0
- package/dist/conformance/src/profiles/resolve-pointer.d.ts +44 -0
- package/dist/conformance/src/profiles/resolve-pointer.js +123 -0
- package/dist/conformance/src/profiles/resolve-pointer.js.map +1 -0
- package/dist/conformance/src/profiles/select-rules.d.ts +13 -0
- package/dist/conformance/src/profiles/select-rules.js +42 -0
- package/dist/conformance/src/profiles/select-rules.js.map +1 -0
- package/dist/conformance/src/profiles/types.d.ts +101 -0
- package/dist/conformance/src/profiles/types.js +31 -0
- package/dist/conformance/src/profiles/types.js.map +1 -0
- package/dist/conformance/src/profiles/validate-profile-definition.d.ts +12 -0
- package/dist/conformance/src/profiles/validate-profile-definition.js +47 -0
- package/dist/conformance/src/profiles/validate-profile-definition.js.map +1 -0
- package/dist/conformance/src/sources.d.ts +53 -0
- package/dist/conformance/src/sources.js +153 -0
- package/dist/conformance/src/sources.js.map +1 -0
- package/dist/conformance/src/validate-core.d.ts +30 -0
- package/dist/conformance/src/validate-core.js +62 -0
- package/dist/conformance/src/validate-core.js.map +1 -0
- package/dist/conformance/src/validate.d.ts +34 -0
- package/dist/conformance/src/validate.js +62 -0
- package/dist/conformance/src/validate.js.map +1 -0
- package/dist/conformance/src/validator-interface.d.ts +35 -0
- package/dist/conformance/src/validator-interface.js +19 -0
- package/dist/conformance/src/validator-interface.js.map +1 -0
- package/package.json +85 -0
- package/profiles/README.md +201 -0
- package/profiles/api-and-integration-management/README.md +271 -0
- package/profiles/api-and-integration-management/profile.json +258 -0
- package/profiles/backup-and-recovery/README.md +318 -0
- package/profiles/backup-and-recovery/profile.json +178 -0
- package/profiles/customer-and-account-management/README.md +340 -0
- package/profiles/customer-and-account-management/profile.json +237 -0
- package/profiles/deployment-and-change-management/README.md +359 -0
- package/profiles/deployment-and-change-management/profile.json +298 -0
- package/profiles/document-management/README.md +120 -0
- package/profiles/document-management/profile.json +170 -0
- package/profiles/financial-transaction-management/README.md +339 -0
- package/profiles/financial-transaction-management/profile.json +247 -0
- package/profiles/identity-and-access-management/README.md +112 -0
- package/profiles/identity-and-access-management/profile.json +120 -0
- package/profiles/incident-management/README.md +338 -0
- package/profiles/incident-management/profile.json +256 -0
- package/profiles/message-broker-management/README.md +344 -0
- package/profiles/message-broker-management/profile.json +399 -0
- package/profiles/profile-definition.schema.json +213 -0
- package/profiles/secrets-and-key-management/README.md +331 -0
- package/profiles/secrets-and-key-management/profile.json +219 -0
- package/schemas/v0.1/audit-event.schema.json +911 -0
- package/semantic-conventions/README.md +52 -0
- package/semantic-conventions/authentication.md +111 -0
- package/semantic-conventions/configuration-and-change.md +122 -0
- package/semantic-conventions/correlation-and-tracing.md +387 -0
- package/semantic-conventions/data-access.md +115 -0
- package/semantic-conventions/event-naming.md +114 -0
- package/semantic-conventions/identity-and-access.md +112 -0
- package/semantic-conventions/privileged-operations.md +110 -0
- package/semantic-conventions/workflow-and-approval.md +109 -0
- package/specification/actor-model.md +135 -0
- package/specification/approval-and-delegation.md +150 -0
- package/specification/authentication.md +116 -0
- package/specification/authorization.md +117 -0
- package/specification/change-model.md +134 -0
- package/specification/delivery.md +136 -0
- package/specification/design-principles.md +137 -0
- package/specification/event-model.md +387 -0
- package/specification/evidence-model.md +109 -0
- package/specification/extension-model.md +159 -0
- package/specification/integrity.md +314 -0
- package/specification/overview.md +172 -0
- package/specification/privacy.md +386 -0
- package/specification/resource-model.md +116 -0
- package/specification/terminology.md +135 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 6901 JSON Pointer resolution.
|
|
3
|
+
*
|
|
4
|
+
* Resolution reads own enumerable properties only. It never walks a prototype,
|
|
5
|
+
* never invokes an accessor and never evaluates anything: a profile definition
|
|
6
|
+
* is data, and a pointer taken from one must not be able to reach `__proto__`,
|
|
7
|
+
* `constructor` or any inherited member of the event being checked.
|
|
8
|
+
*/
|
|
9
|
+
/** Deepest pointer accepted, so that a hostile pointer cannot drive unbounded work. */
|
|
10
|
+
export const MAX_POINTER_DEPTH = 32;
|
|
11
|
+
const NOT_FOUND = { found: false, value: undefined };
|
|
12
|
+
/** Decodes one reference token: `~1` to `/`, then `~0` to `~`, in that order. */
|
|
13
|
+
export function decodeReferenceToken(token) {
|
|
14
|
+
return token.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
15
|
+
}
|
|
16
|
+
/** Splits a pointer into its decoded reference tokens, or `undefined` if malformed. */
|
|
17
|
+
export function pointerTokens(pointer) {
|
|
18
|
+
if (pointer === "") {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
if (!pointer.startsWith("/")) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const tokens = pointer.slice(1).split("/");
|
|
25
|
+
if (tokens.length > MAX_POINTER_DEPTH) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return tokens.map(decodeReferenceToken);
|
|
29
|
+
}
|
|
30
|
+
/** Joins a base pointer and a relative pointer, as `/metadata` + `/role/id`. */
|
|
31
|
+
export function concatPointer(base, relative) {
|
|
32
|
+
return `${base}${relative}`;
|
|
33
|
+
}
|
|
34
|
+
function isIndex(token) {
|
|
35
|
+
return token === "0" || /^[1-9][0-9]{0,9}$/.test(token);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolves a JSON Pointer against a parsed JSON value.
|
|
39
|
+
*
|
|
40
|
+
* Returns `found: false` for a malformed pointer, a member that does not exist,
|
|
41
|
+
* an out-of-range array index, or any attempt to traverse a non-container.
|
|
42
|
+
*/
|
|
43
|
+
export function resolvePointer(root, pointer) {
|
|
44
|
+
const tokens = pointerTokens(pointer);
|
|
45
|
+
if (tokens === undefined) {
|
|
46
|
+
return NOT_FOUND;
|
|
47
|
+
}
|
|
48
|
+
let current = root;
|
|
49
|
+
for (const token of tokens) {
|
|
50
|
+
if (current === null || typeof current !== "object") {
|
|
51
|
+
return NOT_FOUND;
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(current)) {
|
|
54
|
+
if (!isIndex(token)) {
|
|
55
|
+
return NOT_FOUND;
|
|
56
|
+
}
|
|
57
|
+
const index = Number(token);
|
|
58
|
+
if (index >= current.length) {
|
|
59
|
+
return NOT_FOUND;
|
|
60
|
+
}
|
|
61
|
+
current = current[index];
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
// `Object.hasOwn` keeps inherited and prototype members unreachable.
|
|
65
|
+
if (!Object.hasOwn(current, token)) {
|
|
66
|
+
return NOT_FOUND;
|
|
67
|
+
}
|
|
68
|
+
current = current[token];
|
|
69
|
+
}
|
|
70
|
+
return { found: true, value: current };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Whether a resolved value counts as **present** for a profile requirement.
|
|
74
|
+
*
|
|
75
|
+
* Present means: the member exists, is not null, and is not an empty string,
|
|
76
|
+
* array or object. `false` and `0` are present — they are answers, not absences,
|
|
77
|
+
* and a profile that could not require `mfa: false` to be recorded would be
|
|
78
|
+
* unable to distinguish "not recorded" from "recorded as false".
|
|
79
|
+
*
|
|
80
|
+
* This differs deliberately from the privacy linter's notion of a populated
|
|
81
|
+
* value, which excludes booleans because a credential cannot be `true`.
|
|
82
|
+
*/
|
|
83
|
+
export function isPresent(resolution) {
|
|
84
|
+
if (!resolution.found) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
const { value } = resolution;
|
|
88
|
+
if (value === null || value === undefined) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (typeof value === "string") {
|
|
92
|
+
return value.length > 0;
|
|
93
|
+
}
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
return value.length > 0;
|
|
96
|
+
}
|
|
97
|
+
if (typeof value === "object") {
|
|
98
|
+
return Object.keys(value).length > 0;
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
/** JSON type of a value, using the profile metadata type vocabulary. */
|
|
103
|
+
export function jsonTypeOf(value) {
|
|
104
|
+
if (value === null) {
|
|
105
|
+
return "null";
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(value)) {
|
|
108
|
+
return "array";
|
|
109
|
+
}
|
|
110
|
+
if (Number.isInteger(value)) {
|
|
111
|
+
return "integer";
|
|
112
|
+
}
|
|
113
|
+
return typeof value;
|
|
114
|
+
}
|
|
115
|
+
/** Whether a value matches a declared metadata type. `integer` is a subset of `number`. */
|
|
116
|
+
export function matchesMetadataType(value, expected) {
|
|
117
|
+
const actual = jsonTypeOf(value);
|
|
118
|
+
if (expected === "number") {
|
|
119
|
+
return actual === "number" || actual === "integer";
|
|
120
|
+
}
|
|
121
|
+
return actual === expected;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=resolve-pointer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-pointer.js","sourceRoot":"","sources":["../../../../conformance/src/profiles/resolve-pointer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProfileDefinition, ProfileRule } from "./types.js";
|
|
2
|
+
/** Reads `/event/name` from an event, or `undefined`. */
|
|
3
|
+
export declare function eventName(event: unknown): string | undefined;
|
|
4
|
+
/** Whether a rule's selector matches an event name. */
|
|
5
|
+
export declare function ruleMatches(rule: ProfileRule, name: string): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Selects the rules that apply to an event name, in definition order.
|
|
8
|
+
*
|
|
9
|
+
* Order is the order the rules appear in the profile document, which makes a
|
|
10
|
+
* report reproducible and reviewable against the definition. A rule matched by
|
|
11
|
+
* both an exact name and a prefix is selected once.
|
|
12
|
+
*/
|
|
13
|
+
export declare function selectRules(profile: ProfileDefinition, name: string): ProfileRule[];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule selection.
|
|
3
|
+
*
|
|
4
|
+
* Selection is by exact event name or by dotted prefix, and by nothing else.
|
|
5
|
+
* There is no regular expression selector: a profile is a published contract
|
|
6
|
+
* about which events it governs, and a pattern language turns "does this rule
|
|
7
|
+
* apply to my event?" into a question only a tool can answer.
|
|
8
|
+
*/
|
|
9
|
+
import { resolvePointer } from "./resolve-pointer.js";
|
|
10
|
+
/** Reads `/event/name` from an event, or `undefined`. */
|
|
11
|
+
export function eventName(event) {
|
|
12
|
+
const resolved = resolvePointer(event, "/event/name");
|
|
13
|
+
return typeof resolved.value === "string" ? resolved.value : undefined;
|
|
14
|
+
}
|
|
15
|
+
/** Whether a rule's selector matches an event name. */
|
|
16
|
+
export function ruleMatches(rule, name) {
|
|
17
|
+
if (rule.events?.includes(name) === true) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
// Prefixes end with a dot, so `identity.role.` cannot match `identity.roles-export`.
|
|
21
|
+
return rule.eventPrefixes?.some((prefix) => name.startsWith(prefix)) === true;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Selects the rules that apply to an event name, in definition order.
|
|
25
|
+
*
|
|
26
|
+
* Order is the order the rules appear in the profile document, which makes a
|
|
27
|
+
* report reproducible and reviewable against the definition. A rule matched by
|
|
28
|
+
* both an exact name and a prefix is selected once.
|
|
29
|
+
*/
|
|
30
|
+
export function selectRules(profile, name) {
|
|
31
|
+
const selected = [];
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
for (const rule of profile.rules) {
|
|
34
|
+
if (!ruleMatches(rule, name) || seen.has(rule.id)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
seen.add(rule.id);
|
|
38
|
+
selected.push(rule);
|
|
39
|
+
}
|
|
40
|
+
return selected;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=select-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-rules.js","sourceRoot":"","sources":["../../../../conformance/src/profiles/select-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,yDAAyD;AACzD,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IACtD,OAAO,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACzE,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,WAAW,CAAC,IAAiB,EAAE,IAAY;IACzD,IAAI,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QACzC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,qFAAqF;IACrF,OAAO,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC;AAChF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,OAA0B,EAAE,IAAY;IAClE,MAAM,QAAQ,GAAkB,EAAE,CAAC;IACnC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACjC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAClD,SAAS;QACX,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for declarative profile conformance.
|
|
3
|
+
*
|
|
4
|
+
* A profile adds requirements to the core model for a specific domain. It can
|
|
5
|
+
* only ever add: the rule vocabulary can require a value to be present or equal
|
|
6
|
+
* to a scalar, and can do nothing else. There is no way to express "this core
|
|
7
|
+
* field is optional here", because there is no keyword that removes anything.
|
|
8
|
+
*
|
|
9
|
+
* Profile conformance is not legal or regulatory compliance, and a profile is
|
|
10
|
+
* not a regulatory mapping. See decisions/0008-declarative-profile-conformance.md.
|
|
11
|
+
*/
|
|
12
|
+
/** How a rule reports its violations. Unrelated to the privacy linter's severities. */
|
|
13
|
+
export type RuleSeverity = "info" | "warning" | "error";
|
|
14
|
+
/** A scalar a condition or required value compares against. */
|
|
15
|
+
export type Scalar = string | number | boolean | null;
|
|
16
|
+
export interface Condition {
|
|
17
|
+
readonly path: string;
|
|
18
|
+
readonly equals: Scalar;
|
|
19
|
+
}
|
|
20
|
+
export interface RequiredValue {
|
|
21
|
+
readonly path: string;
|
|
22
|
+
readonly equals: Scalar;
|
|
23
|
+
}
|
|
24
|
+
export type MetadataType = "string" | "number" | "integer" | "boolean" | "object" | "array";
|
|
25
|
+
export interface RequiredMetadata {
|
|
26
|
+
/** JSON Pointer relative to `/metadata`. */
|
|
27
|
+
readonly path: string;
|
|
28
|
+
readonly type: MetadataType;
|
|
29
|
+
}
|
|
30
|
+
export interface ProfileRule {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly description: string;
|
|
33
|
+
readonly rationale?: string;
|
|
34
|
+
readonly severity?: RuleSeverity;
|
|
35
|
+
readonly events?: readonly string[];
|
|
36
|
+
readonly eventPrefixes?: readonly string[];
|
|
37
|
+
readonly when?: Condition;
|
|
38
|
+
readonly requiredPaths?: readonly string[];
|
|
39
|
+
readonly requiredMetadata?: readonly RequiredMetadata[];
|
|
40
|
+
readonly requiredValues?: readonly RequiredValue[];
|
|
41
|
+
readonly recommendedPaths?: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
export interface ProfileDefinition {
|
|
44
|
+
readonly profileVersion: string;
|
|
45
|
+
readonly name: string;
|
|
46
|
+
readonly version: string;
|
|
47
|
+
readonly status: "experimental" | "stable" | "deprecated";
|
|
48
|
+
readonly coreVersions: readonly string[];
|
|
49
|
+
readonly title: string;
|
|
50
|
+
readonly description: string;
|
|
51
|
+
readonly rules: readonly ProfileRule[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Outcome of checking one event.
|
|
55
|
+
*
|
|
56
|
+
* `not-applicable` exists so that the tool never claims a document-sharing event
|
|
57
|
+
* conforms to an identity profile. Silence is not conformance.
|
|
58
|
+
*/
|
|
59
|
+
export type ProfileStatus = "conforming" | "violations" | "not-applicable" | "core-invalid";
|
|
60
|
+
/** A profile requirement that was not met. Never carries an event value. */
|
|
61
|
+
export interface ProfileFinding {
|
|
62
|
+
readonly ruleId: string;
|
|
63
|
+
/** JSON Pointer to the location the requirement concerns. */
|
|
64
|
+
readonly path: string;
|
|
65
|
+
readonly message: string;
|
|
66
|
+
readonly severity: RuleSeverity;
|
|
67
|
+
}
|
|
68
|
+
export interface ProfileCheckResult {
|
|
69
|
+
readonly label: string;
|
|
70
|
+
readonly eventId?: string;
|
|
71
|
+
readonly status: ProfileStatus;
|
|
72
|
+
/** Whether the event validates against the canonical core schema. */
|
|
73
|
+
readonly coreValid: boolean;
|
|
74
|
+
/** Whether the event satisfies every applicable profile requirement. */
|
|
75
|
+
readonly profileValid: boolean;
|
|
76
|
+
readonly profile: {
|
|
77
|
+
readonly name: string;
|
|
78
|
+
readonly version: string;
|
|
79
|
+
};
|
|
80
|
+
readonly matchedRules: readonly string[];
|
|
81
|
+
readonly errors: readonly ProfileFinding[];
|
|
82
|
+
readonly warnings: readonly ProfileFinding[];
|
|
83
|
+
/** Rendered core schema issues, present only when core validation failed. */
|
|
84
|
+
readonly coreIssues: readonly string[];
|
|
85
|
+
}
|
|
86
|
+
export interface ProfileCheckSummary {
|
|
87
|
+
readonly events: number;
|
|
88
|
+
readonly conforming: number;
|
|
89
|
+
readonly violations: number;
|
|
90
|
+
readonly notApplicable: number;
|
|
91
|
+
readonly coreInvalid: number;
|
|
92
|
+
readonly errors: number;
|
|
93
|
+
readonly warnings: number;
|
|
94
|
+
}
|
|
95
|
+
export declare function summariseProfileResults(results: readonly ProfileCheckResult[]): ProfileCheckSummary;
|
|
96
|
+
/**
|
|
97
|
+
* Whether a profile applies to an event declaring a given core specification
|
|
98
|
+
* version. Lives here rather than beside profile loading so that the
|
|
99
|
+
* conformance engine has no filesystem dependency at runtime.
|
|
100
|
+
*/
|
|
101
|
+
export declare function supportsCoreVersion(profile: ProfileDefinition, specVersion: unknown): boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for declarative profile conformance.
|
|
3
|
+
*
|
|
4
|
+
* A profile adds requirements to the core model for a specific domain. It can
|
|
5
|
+
* only ever add: the rule vocabulary can require a value to be present or equal
|
|
6
|
+
* to a scalar, and can do nothing else. There is no way to express "this core
|
|
7
|
+
* field is optional here", because there is no keyword that removes anything.
|
|
8
|
+
*
|
|
9
|
+
* Profile conformance is not legal or regulatory compliance, and a profile is
|
|
10
|
+
* not a regulatory mapping. See decisions/0008-declarative-profile-conformance.md.
|
|
11
|
+
*/
|
|
12
|
+
export function summariseProfileResults(results) {
|
|
13
|
+
return {
|
|
14
|
+
events: results.length,
|
|
15
|
+
conforming: results.filter((result) => result.status === "conforming").length,
|
|
16
|
+
violations: results.filter((result) => result.status === "violations").length,
|
|
17
|
+
notApplicable: results.filter((result) => result.status === "not-applicable").length,
|
|
18
|
+
coreInvalid: results.filter((result) => result.status === "core-invalid").length,
|
|
19
|
+
errors: results.reduce((total, result) => total + result.errors.length, 0),
|
|
20
|
+
warnings: results.reduce((total, result) => total + result.warnings.length, 0),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Whether a profile applies to an event declaring a given core specification
|
|
25
|
+
* version. Lives here rather than beside profile loading so that the
|
|
26
|
+
* conformance engine has no filesystem dependency at runtime.
|
|
27
|
+
*/
|
|
28
|
+
export function supportsCoreVersion(profile, specVersion) {
|
|
29
|
+
return typeof specVersion === "string" && profile.coreVersions.includes(specVersion);
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../conformance/src/profiles/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA8FH,MAAM,UAAU,uBAAuB,CACrC,OAAsC;IAEtC,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,MAAM;QAC7E,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,MAAM;QAC7E,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,gBAAgB,CAAC,CAAC,MAAM;QACpF,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,cAAc,CAAC,CAAC,MAAM;QAChF,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1E,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;KAC/E,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAA0B,EAAE,WAAoB;IAClF,OAAO,OAAO,WAAW,KAAK,QAAQ,IAAI,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACvF,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AnySchemaObject } from "ajv";
|
|
2
|
+
import { type ValidationIssue } from "../format-errors.js";
|
|
3
|
+
export declare const PROFILE_DEFINITION_SCHEMA_ID = "https://openauditmodel.org/schemas/profile-definition/0.1/schema.json";
|
|
4
|
+
export declare const PROFILE_DEFINITION_SCHEMA_RELATIVE_PATH: string;
|
|
5
|
+
/** Repository root, derived from the location of the canonical schema. */
|
|
6
|
+
export declare function repositoryRoot(): string;
|
|
7
|
+
/** Absolute path of the profile definition schema. */
|
|
8
|
+
export declare function resolveProfileDefinitionSchemaPath(): string;
|
|
9
|
+
/** Reads the profile definition schema document. */
|
|
10
|
+
export declare function loadProfileDefinitionSchema(): AnySchemaObject;
|
|
11
|
+
/** Validates a parsed profile definition. Returns the issues found. */
|
|
12
|
+
export declare function validateProfileDefinition(definition: unknown): ValidationIssue[];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation of profile definition documents.
|
|
3
|
+
*
|
|
4
|
+
* A profile definition is validated against its own JSON Schema, which is
|
|
5
|
+
* closed at every level: an unrecognised rule property, selector or operator is
|
|
6
|
+
* a rejection, not something ignored. A profile that silently ignored what it
|
|
7
|
+
* did not understand would let a typo turn a requirement into nothing.
|
|
8
|
+
*
|
|
9
|
+
* This schema is not part of the canonical audit event schema and never
|
|
10
|
+
* constrains an audit event.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { createAjv, resolveSchemaPath } from "../validate.js";
|
|
15
|
+
import { toIssues } from "../format-errors.js";
|
|
16
|
+
export const PROFILE_DEFINITION_SCHEMA_ID = "https://openauditmodel.org/schemas/profile-definition/0.1/schema.json";
|
|
17
|
+
export const PROFILE_DEFINITION_SCHEMA_RELATIVE_PATH = path.join("profiles", "profile-definition.schema.json");
|
|
18
|
+
/** Repository root, derived from the location of the canonical schema. */
|
|
19
|
+
export function repositoryRoot() {
|
|
20
|
+
const schemaPath = resolveSchemaPath();
|
|
21
|
+
return path.dirname(path.dirname(path.dirname(schemaPath)));
|
|
22
|
+
}
|
|
23
|
+
/** Absolute path of the profile definition schema. */
|
|
24
|
+
export function resolveProfileDefinitionSchemaPath() {
|
|
25
|
+
const candidate = path.join(repositoryRoot(), PROFILE_DEFINITION_SCHEMA_RELATIVE_PATH);
|
|
26
|
+
if (!existsSync(candidate)) {
|
|
27
|
+
throw new Error(`Unable to locate ${PROFILE_DEFINITION_SCHEMA_RELATIVE_PATH}.`);
|
|
28
|
+
}
|
|
29
|
+
return candidate;
|
|
30
|
+
}
|
|
31
|
+
/** Reads the profile definition schema document. */
|
|
32
|
+
export function loadProfileDefinitionSchema() {
|
|
33
|
+
return JSON.parse(readFileSync(resolveProfileDefinitionSchemaPath(), "utf8"));
|
|
34
|
+
}
|
|
35
|
+
let compiled;
|
|
36
|
+
function definitionValidator() {
|
|
37
|
+
if (compiled === undefined) {
|
|
38
|
+
compiled = createAjv().compile(loadProfileDefinitionSchema());
|
|
39
|
+
}
|
|
40
|
+
return compiled;
|
|
41
|
+
}
|
|
42
|
+
/** Validates a parsed profile definition. Returns the issues found. */
|
|
43
|
+
export function validateProfileDefinition(definition) {
|
|
44
|
+
const validate = definitionValidator();
|
|
45
|
+
return validate(definition) ? [] : toIssues(validate.errors);
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=validate-profile-definition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-profile-definition.js","sourceRoot":"","sources":["../../../../conformance/src/profiles/validate-profile-definition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAwB,MAAM,qBAAqB,CAAC;AAErE,MAAM,CAAC,MAAM,4BAA4B,GACvC,uEAAuE,CAAC;AAE1E,MAAM,CAAC,MAAM,uCAAuC,GAAG,IAAI,CAAC,IAAI,CAC9D,UAAU,EACV,gCAAgC,CACjC,CAAC;AAEF,0EAA0E;AAC1E,MAAM,UAAU,cAAc;IAC5B,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC;IACvC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,kCAAkC;IAChD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,uCAAuC,CAAC,CAAC;IACvF,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,oBAAoB,uCAAuC,GAAG,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,2BAA2B;IACzC,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,kCAAkC,EAAE,EAAE,MAAM,CAAC,CAAoB,CAAC;AACnG,CAAC;AAED,IAAI,QAAsC,CAAC;AAE3C,SAAS,mBAAmB;IAC1B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,QAAQ,GAAG,SAAS,EAAE,CAAC,OAAO,CAAC,2BAA2B,EAAE,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,yBAAyB,CAAC,UAAmB;IAC3D,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAC;IACvC,OAAO,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Largest document the tooling will read. An audit event is a small structured
|
|
3
|
+
* record; a file above this size is a mistake or an attempt to exhaust memory,
|
|
4
|
+
* and is refused rather than parsed.
|
|
5
|
+
*/
|
|
6
|
+
export declare const MAX_DOCUMENT_BYTES: number;
|
|
7
|
+
/** One event, together with where it came from. */
|
|
8
|
+
export interface EventDocument {
|
|
9
|
+
/** Path of the file the event was read from. */
|
|
10
|
+
readonly file: string;
|
|
11
|
+
/** Position of the event within that file. `undefined` for a single-event file. */
|
|
12
|
+
readonly index?: number;
|
|
13
|
+
/** Display label, `file` or `file#index`, used in reports. */
|
|
14
|
+
readonly label: string;
|
|
15
|
+
/** The parsed event. Not yet validated. */
|
|
16
|
+
readonly event: unknown;
|
|
17
|
+
}
|
|
18
|
+
/** A file that could not be read or parsed. */
|
|
19
|
+
export interface DocumentLoadFailure {
|
|
20
|
+
readonly file: string;
|
|
21
|
+
readonly error: string;
|
|
22
|
+
}
|
|
23
|
+
export interface DocumentLoadResult {
|
|
24
|
+
readonly documents: readonly EventDocument[];
|
|
25
|
+
readonly failures: readonly DocumentLoadFailure[];
|
|
26
|
+
}
|
|
27
|
+
/** Successful read of a single JSON document. */
|
|
28
|
+
export interface JsonReadSuccess {
|
|
29
|
+
readonly ok: true;
|
|
30
|
+
readonly value: unknown;
|
|
31
|
+
}
|
|
32
|
+
export interface JsonReadFailure {
|
|
33
|
+
readonly ok: false;
|
|
34
|
+
readonly error: string;
|
|
35
|
+
}
|
|
36
|
+
export type JsonReadResult = JsonReadSuccess | JsonReadFailure;
|
|
37
|
+
/**
|
|
38
|
+
* Reads and parses one JSON file. The returned failure text never contains file
|
|
39
|
+
* content, only the parser's positional message.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readJsonFile(file: string): JsonReadResult;
|
|
42
|
+
/**
|
|
43
|
+
* Expands input paths into the list of files to read. Directories are expanded
|
|
44
|
+
* one level deep and sorted, so that reports are stable across platforms.
|
|
45
|
+
*/
|
|
46
|
+
export declare function expandInputPaths(inputs: readonly string[]): string[];
|
|
47
|
+
/**
|
|
48
|
+
* Reads every event contained in one file. A file may hold a single event
|
|
49
|
+
* object, an array of events, or one event per line for `.jsonl` and `.ndjson`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function readEventDocuments(file: string): DocumentLoadResult;
|
|
52
|
+
/** Expands input paths and reads every event they contain. */
|
|
53
|
+
export declare function loadEventDocuments(inputs: readonly string[]): DocumentLoadResult;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading of audit event documents from the file system.
|
|
3
|
+
*
|
|
4
|
+
* Every command reads events through this module so that the size limit, the
|
|
5
|
+
* parse behaviour and the error wording are identical everywhere. Nothing here
|
|
6
|
+
* resolves remote references or follows anything contained in an event.
|
|
7
|
+
*/
|
|
8
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
/**
|
|
11
|
+
* Largest document the tooling will read. An audit event is a small structured
|
|
12
|
+
* record; a file above this size is a mistake or an attempt to exhaust memory,
|
|
13
|
+
* and is refused rather than parsed.
|
|
14
|
+
*/
|
|
15
|
+
export const MAX_DOCUMENT_BYTES = 8 * 1024 * 1024;
|
|
16
|
+
/** File extensions treated as JSON Lines rather than a single JSON document. */
|
|
17
|
+
const JSON_LINES_EXTENSIONS = [".jsonl", ".ndjson"];
|
|
18
|
+
/** File extensions collected when a directory is given. */
|
|
19
|
+
const EVENT_EXTENSIONS = [".json", ...JSON_LINES_EXTENSIONS];
|
|
20
|
+
function isJsonLines(file) {
|
|
21
|
+
const extension = path.extname(file).toLowerCase();
|
|
22
|
+
return JSON_LINES_EXTENSIONS.includes(extension);
|
|
23
|
+
}
|
|
24
|
+
function readTextFile(file) {
|
|
25
|
+
let size;
|
|
26
|
+
try {
|
|
27
|
+
size = statSync(file).size;
|
|
28
|
+
}
|
|
29
|
+
catch (cause) {
|
|
30
|
+
return { ok: false, error: `cannot read file: ${cause.message}` };
|
|
31
|
+
}
|
|
32
|
+
if (size > MAX_DOCUMENT_BYTES) {
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
error: `file is ${size} bytes, above the ${MAX_DOCUMENT_BYTES} byte limit`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return { ok: true, value: readFileSync(file, "utf8") };
|
|
40
|
+
}
|
|
41
|
+
catch (cause) {
|
|
42
|
+
return { ok: false, error: `cannot read file: ${cause.message}` };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Reads and parses one JSON file. The returned failure text never contains file
|
|
47
|
+
* content, only the parser's positional message.
|
|
48
|
+
*/
|
|
49
|
+
export function readJsonFile(file) {
|
|
50
|
+
const text = readTextFile(file);
|
|
51
|
+
if (!text.ok) {
|
|
52
|
+
return text;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return { ok: true, value: JSON.parse(text.value) };
|
|
56
|
+
}
|
|
57
|
+
catch (cause) {
|
|
58
|
+
return { ok: false, error: `cannot parse JSON: ${cause.message}` };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Expands input paths into the list of files to read. Directories are expanded
|
|
63
|
+
* one level deep and sorted, so that reports are stable across platforms.
|
|
64
|
+
*/
|
|
65
|
+
export function expandInputPaths(inputs) {
|
|
66
|
+
const files = [];
|
|
67
|
+
for (const input of inputs) {
|
|
68
|
+
const stats = statSync(input);
|
|
69
|
+
if (stats.isDirectory()) {
|
|
70
|
+
const entries = readdirSync(input)
|
|
71
|
+
.filter((entry) => EVENT_EXTENSIONS.includes(path.extname(entry).toLowerCase()))
|
|
72
|
+
.sort((left, right) => left.localeCompare(right, "en"));
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
files.push(path.join(input, entry));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
files.push(input);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return files;
|
|
82
|
+
}
|
|
83
|
+
function documentsFromJsonLines(file, text) {
|
|
84
|
+
const documents = [];
|
|
85
|
+
const lines = text.split(/\r?\n/);
|
|
86
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
87
|
+
const trimmed = line.trim();
|
|
88
|
+
if (trimmed === "") {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
documents.push({
|
|
93
|
+
file,
|
|
94
|
+
index: lineIndex + 1,
|
|
95
|
+
label: `${file}#${lineIndex + 1}`,
|
|
96
|
+
event: JSON.parse(trimmed),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (cause) {
|
|
100
|
+
return {
|
|
101
|
+
documents: [],
|
|
102
|
+
failures: [
|
|
103
|
+
{
|
|
104
|
+
file,
|
|
105
|
+
error: `cannot parse JSON on line ${lineIndex + 1}: ${cause.message}`,
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return { documents, failures: [] };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Reads every event contained in one file. A file may hold a single event
|
|
115
|
+
* object, an array of events, or one event per line for `.jsonl` and `.ndjson`.
|
|
116
|
+
*/
|
|
117
|
+
export function readEventDocuments(file) {
|
|
118
|
+
if (isJsonLines(file)) {
|
|
119
|
+
const text = readTextFile(file);
|
|
120
|
+
if (!text.ok) {
|
|
121
|
+
return { documents: [], failures: [{ file, error: text.error }] };
|
|
122
|
+
}
|
|
123
|
+
return documentsFromJsonLines(file, text.value);
|
|
124
|
+
}
|
|
125
|
+
const parsed = readJsonFile(file);
|
|
126
|
+
if (!parsed.ok) {
|
|
127
|
+
return { documents: [], failures: [{ file, error: parsed.error }] };
|
|
128
|
+
}
|
|
129
|
+
if (Array.isArray(parsed.value)) {
|
|
130
|
+
return {
|
|
131
|
+
documents: parsed.value.map((event, index) => ({
|
|
132
|
+
file,
|
|
133
|
+
index,
|
|
134
|
+
label: `${file}#${index}`,
|
|
135
|
+
event,
|
|
136
|
+
})),
|
|
137
|
+
failures: [],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return { documents: [{ file, label: file, event: parsed.value }], failures: [] };
|
|
141
|
+
}
|
|
142
|
+
/** Expands input paths and reads every event they contain. */
|
|
143
|
+
export function loadEventDocuments(inputs) {
|
|
144
|
+
const documents = [];
|
|
145
|
+
const failures = [];
|
|
146
|
+
for (const file of expandInputPaths(inputs)) {
|
|
147
|
+
const result = readEventDocuments(file);
|
|
148
|
+
documents.push(...result.documents);
|
|
149
|
+
failures.push(...result.failures);
|
|
150
|
+
}
|
|
151
|
+
return { documents, failures };
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=sources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources.js","sourceRoot":"","sources":["../../../conformance/src/sources.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema validation with no filesystem dependency.
|
|
3
|
+
*
|
|
4
|
+
* This module is the boundary that lets the conformance engines run unchanged
|
|
5
|
+
* in environments that do not read the schema from disk — the MCP server in
|
|
6
|
+
* particular, where the schema is compiled in at build time.
|
|
7
|
+
*
|
|
8
|
+
* Everything here operates on a schema **object**. Locating and reading schema
|
|
9
|
+
* files lives in `validate.ts`, which imports this module.
|
|
10
|
+
*/
|
|
11
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
12
|
+
import type { AnySchemaObject } from "ajv";
|
|
13
|
+
import { type ValidationIssue } from "./format-errors.js";
|
|
14
|
+
import { createValidatorFromCompiled, SCHEMA_ID, SPEC_VERSION, type EventValidator } from "./validator-interface.js";
|
|
15
|
+
export { createValidatorFromCompiled, SCHEMA_ID, SPEC_VERSION };
|
|
16
|
+
export type { EventValidator };
|
|
17
|
+
/** Creates an Ajv instance configured the way OpenAuditModel conformance requires. */
|
|
18
|
+
export declare function createAjv(): Ajv2020;
|
|
19
|
+
/** Validates a schema document itself against the Draft 2020-12 meta-schema. */
|
|
20
|
+
export declare function validateSchemaDocument(schema: AnySchemaObject): ValidationIssue[];
|
|
21
|
+
/**
|
|
22
|
+
* Compiles a schema object into a reusable validator.
|
|
23
|
+
*
|
|
24
|
+
* Used directly where the schema is already in memory. Environments that forbid
|
|
25
|
+
* dynamic code generation cannot call this — Ajv compiles with `new Function` —
|
|
26
|
+
* and supply a precompiled validation function instead; see
|
|
27
|
+
* {@link createValidatorFromCompiled}.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createValidatorFromSchema(schema: AnySchemaObject): EventValidator;
|
|
30
|
+
export type { ValidationIssue };
|