@stratum-hq/compliance 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Christian Crank
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # @stratum-hq/compliance
2
+
3
+ A content-free compliance **kernel** for [Stratum](https://github.com/stratum-hq/Stratum): the pure mechanics and type shapes any compliance product needs, with none of the content. Zero runtime dependencies, no database, no network, no provider, and no built-in catalog.
4
+
5
+ It gives you three things:
6
+
7
+ 1. **Coverage scoring** — diff a declared baseline against a resolved state and get a per-control breakdown plus a 0 to 100 score.
8
+ 2. **A finding state machine** — a pure decision for whether a new evaluation outcome should open, resolve, or leave a finding alone.
9
+ 3. **A control type vocabulary** — structural interfaces for describing a catalog of controls.
10
+
11
+ > [!NOTE]
12
+ > This is **not** a batteries-included compliance solution. It ships no frameworks, no controls, no provider mappings, and no thresholds. **Bring your own catalog:** you supply the content and the persistence; this package supplies the arithmetic and the shapes.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @stratum-hq/compliance
18
+ ```
19
+
20
+ ## Coverage scoring
21
+
22
+ `scoreCoverage` compares a `baseline` (what each control is expected to be) against a `resolved` map (what each control actually is). Only baseline keys are scored; extra resolved keys are ignored.
23
+
24
+ ```typescript
25
+ import { scoreCoverage } from "@stratum-hq/compliance";
26
+
27
+ const baseline = { mfaRequired: true, passwordMinLength: 12 };
28
+ const resolved = {
29
+ mfaRequired: { value: "true" }, // resolved config often arrives as strings
30
+ passwordMinLength: { value: 8 },
31
+ // passwordMinLength is present but wrong; a key omitted here would be "missing"
32
+ };
33
+
34
+ const result = scoreCoverage(baseline, resolved);
35
+ // {
36
+ // total: 2, compliant: 1, drift: 1, missing: 0, score: 50,
37
+ // details: [
38
+ // { key: "mfaRequired", expected: true, actual: "true", status: "compliant" },
39
+ // { key: "passwordMinLength", expected: 12, actual: 8, status: "drift" },
40
+ // ],
41
+ // }
42
+ ```
43
+
44
+ - `status` is `"compliant"` (matches), `"drift"` (present but wrong), or `"missing"` (no resolved entry for that key).
45
+ - `score` is `round(compliant / total * 100)`. An **empty baseline scores 100** — nothing is required, so nothing is out of compliance.
46
+
47
+ ### Equality
48
+
49
+ The default comparator is `looseEqual`: booleans, numbers, and strings compare by their **textual form** (`true` equals `"true"`, `5` equals `"5"`), while objects and arrays compare by their **JSON form** (order-sensitive). This tolerates config values that come back as strings. Override it for strict or domain-specific comparison:
50
+
51
+ ```typescript
52
+ import { scoreCoverage, looseEqual } from "@stratum-hq/compliance";
53
+
54
+ scoreCoverage(baseline, resolved, { equals: (e, a) => e === a }); // strict
55
+ looseEqual(true, "true"); // => true
56
+ ```
57
+
58
+ ## Finding state machine
59
+
60
+ `reconcileFinding` decides what should happen to a control's finding when a new evaluation outcome arrives. It is a pure decision — you persist the result.
61
+
62
+ ```typescript
63
+ import { reconcileFinding } from "@stratum-hq/compliance";
64
+
65
+ reconcileFinding("fail", "none"); // { type: "open" } — new gap
66
+ reconcileFinding("fail", "open"); // { type: "noop" } — already tracked
67
+ reconcileFinding("pass", "open"); // { type: "resolve" } — gap closed
68
+ reconcileFinding("pass", "accepted"); // { type: "noop" } — accepted risk untouched
69
+ reconcileFinding("na", "open"); // { type: "noop" } — na/error never change a finding
70
+ ```
71
+
72
+ The rules:
73
+
74
+ - `fail` **opens** a finding, unless one is already active (`open` / `remediating`) or the risk was formally `accepted`.
75
+ - `pass` **resolves** an active finding (`open` / `remediating`), and leaves an `accepted` finding untouched; otherwise there is nothing to resolve.
76
+ - `na` and `error` are always no-ops.
77
+
78
+ Map `{ type: "open" }` and `{ type: "resolve" }` onto your own inserts and updates — the state machine has no opinion about storage.
79
+
80
+ ## Type vocabulary
81
+
82
+ Structural, content-free interfaces for describing a catalog. They carry no data and no provider mapping; populate them with your own content.
83
+
84
+ ```typescript
85
+ import type {
86
+ FieldType, // "boolean" | "number" | "enum"
87
+ Verification, // "verified" | "attestation" | "declared"
88
+ CatalogField,
89
+ CatalogGroup,
90
+ ManualControl,
91
+ ControlDef,
92
+ } from "@stratum-hq/compliance";
93
+ ```
94
+
95
+ ## Design
96
+
97
+ Everything here is a pure function or a type. No side effects, no IO, no global state, and nothing to configure. That is deliberate: the reusable part of compliance is the arithmetic and the vocabulary, so the interesting, product-specific parts (which controls, mapped to which real settings, for which framework, loaded and stored how) stay entirely on your side of the boundary.
98
+
99
+ ## Links
100
+
101
+ - GitHub: https://github.com/stratum-hq/Stratum
102
+
103
+ ## License
104
+
105
+ MIT © Christian Crank
@@ -0,0 +1,24 @@
1
+ /** The outcome of evaluating a single control. */
2
+ export type ControlOutcome = "pass" | "fail" | "na" | "error";
3
+ /** The current state of a control's finding. `none` means no finding exists. */
4
+ export type FindingState = "open" | "remediating" | "resolved" | "accepted" | "none";
5
+ export type FindingAction = {
6
+ type: "open";
7
+ } | {
8
+ type: "resolve";
9
+ } | {
10
+ type: "noop";
11
+ };
12
+ /**
13
+ * Decide what should happen to a control's finding given the new evaluation
14
+ * outcome and the current finding state. Pure.
15
+ *
16
+ * Rules:
17
+ * - `fail` opens a finding, unless one is already active (`open` /
18
+ * `remediating`) or the risk was formally `accepted`.
19
+ * - `pass` resolves an active finding (`open` / `remediating`), and leaves an
20
+ * `accepted` finding untouched; there is nothing to resolve otherwise.
21
+ * - `na` and `error` never change a finding.
22
+ */
23
+ export declare function reconcileFinding(newOutcome: ControlOutcome, currentState: FindingState): FindingAction;
24
+ //# sourceMappingURL=finding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding.d.ts","sourceRoot":"","sources":["../src/finding.ts"],"names":[],"mappings":"AAQA,kDAAkD;AAClD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC;AAE9D,gFAAgF;AAChF,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,aAAa,GACb,UAAU,GACV,UAAU,GACV,MAAM,CAAC;AAEX,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,cAAc,EAC1B,YAAY,EAAE,YAAY,GACzB,aAAa,CAsBf"}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ // Finding state machine.
3
+ //
4
+ // A pure decision function for reconciling a control's finding when a new
5
+ // evaluation outcome arrives. It decides only what should happen (open a
6
+ // finding, resolve the active one, or do nothing); persisting that decision is
7
+ // the caller's job. This keeps the transition rules testable in isolation from
8
+ // any storage.
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.reconcileFinding = reconcileFinding;
11
+ /**
12
+ * Decide what should happen to a control's finding given the new evaluation
13
+ * outcome and the current finding state. Pure.
14
+ *
15
+ * Rules:
16
+ * - `fail` opens a finding, unless one is already active (`open` /
17
+ * `remediating`) or the risk was formally `accepted`.
18
+ * - `pass` resolves an active finding (`open` / `remediating`), and leaves an
19
+ * `accepted` finding untouched; there is nothing to resolve otherwise.
20
+ * - `na` and `error` never change a finding.
21
+ */
22
+ function reconcileFinding(newOutcome, currentState) {
23
+ switch (newOutcome) {
24
+ case "fail":
25
+ if (currentState === "open" ||
26
+ currentState === "remediating" ||
27
+ currentState === "accepted") {
28
+ return { type: "noop" };
29
+ }
30
+ return { type: "open" };
31
+ case "pass":
32
+ if (currentState === "open" || currentState === "remediating") {
33
+ return { type: "resolve" };
34
+ }
35
+ return { type: "noop" };
36
+ case "na":
37
+ case "error":
38
+ return { type: "noop" };
39
+ }
40
+ }
41
+ //# sourceMappingURL=finding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding.js","sourceRoot":"","sources":["../src/finding.ts"],"names":[],"mappings":";AAAA,yBAAyB;AACzB,EAAE;AACF,0EAA0E;AAC1E,yEAAyE;AACzE,+EAA+E;AAC/E,+EAA+E;AAC/E,eAAe;;AA6Bf,4CAyBC;AApCD;;;;;;;;;;GAUG;AACH,SAAgB,gBAAgB,CAC9B,UAA0B,EAC1B,YAA0B;IAE1B,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,MAAM;YACT,IACE,YAAY,KAAK,MAAM;gBACvB,YAAY,KAAK,aAAa;gBAC9B,YAAY,KAAK,UAAU,EAC3B,CAAC;gBACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;YAC1B,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAE1B,KAAK,MAAM;YACT,IAAI,YAAY,KAAK,MAAM,IAAI,YAAY,KAAK,aAAa,EAAE,CAAC;gBAC9D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;YAC7B,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAE1B,KAAK,IAAI,CAAC;QACV,KAAK,OAAO;YACV,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC5B,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { scoreCoverage, looseEqual } from "./scoring.js";
2
+ export type { ControlStatus, ScoredControl, CoverageResult, ScoreOptions, } from "./scoring.js";
3
+ export { reconcileFinding } from "./finding.js";
4
+ export type { ControlOutcome, FindingState, FindingAction } from "./finding.js";
5
+ export type { FieldType, Verification, CatalogField, CatalogGroup, ManualControl, ControlDef, } from "./types.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACzD,YAAY,EACV,aAAa,EACb,aAAa,EACb,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEhF,YAAY,EACV,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,UAAU,GACX,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ // @stratum-hq/compliance
3
+ //
4
+ // A content-free compliance kernel: baseline coverage scoring, a finding state
5
+ // machine, and a control type vocabulary. Pure TypeScript, zero runtime
6
+ // dependencies, no database, no provider, no built-in catalog. Bring your own
7
+ // catalog and persistence; this package supplies only the mechanics and shapes.
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.reconcileFinding = exports.looseEqual = exports.scoreCoverage = void 0;
10
+ var scoring_js_1 = require("./scoring.js");
11
+ Object.defineProperty(exports, "scoreCoverage", { enumerable: true, get: function () { return scoring_js_1.scoreCoverage; } });
12
+ Object.defineProperty(exports, "looseEqual", { enumerable: true, get: function () { return scoring_js_1.looseEqual; } });
13
+ var finding_js_1 = require("./finding.js");
14
+ Object.defineProperty(exports, "reconcileFinding", { enumerable: true, get: function () { return finding_js_1.reconcileFinding; } });
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,yBAAyB;AACzB,EAAE;AACF,+EAA+E;AAC/E,wEAAwE;AACxE,8EAA8E;AAC9E,gFAAgF;;;AAEhF,2CAAyD;AAAhD,2GAAA,aAAa,OAAA;AAAE,wGAAA,UAAU,OAAA;AAQlC,2CAAgD;AAAvC,8GAAA,gBAAgB,OAAA"}
@@ -0,0 +1,52 @@
1
+ /** The computed state of one control against its baseline. */
2
+ export type ControlStatus = "compliant" | "drift" | "missing";
3
+ /** One control's contribution to a {@link CoverageResult}. */
4
+ export interface ScoredControl {
5
+ key: string;
6
+ expected: unknown;
7
+ /** The resolved value, or `undefined` when the control is missing. */
8
+ actual: unknown;
9
+ status: ControlStatus;
10
+ }
11
+ export interface CoverageResult {
12
+ /** Number of controls in the baseline. */
13
+ total: number;
14
+ compliant: number;
15
+ drift: number;
16
+ missing: number;
17
+ /** 0 to 100, the share of baseline controls that are compliant. */
18
+ score: number;
19
+ details: ScoredControl[];
20
+ }
21
+ export interface ScoreOptions {
22
+ /**
23
+ * Value equality. Defaults to {@link looseEqual} (booleans / numbers /
24
+ * strings compare by textual form, objects by JSON). Callers can override,
25
+ * for example to compare strictly or with domain-specific tolerance.
26
+ */
27
+ equals?: (expected: unknown, actual: unknown) => boolean;
28
+ }
29
+ /**
30
+ * The default string-loose comparator, exported for reuse.
31
+ *
32
+ * Objects and arrays compare by their JSON form; every other value (booleans,
33
+ * numbers, strings, `null`, `undefined`) compares by its textual form, so
34
+ * `true` equals `"true"` and `5` equals `"5"`. This tolerates the common case
35
+ * where a resolved config value arrives as a string even though its baseline
36
+ * was declared as a boolean or number. Note JSON comparison is order-sensitive:
37
+ * objects whose keys are in a different order are treated as unequal.
38
+ */
39
+ export declare function looseEqual(expected: unknown, actual: unknown): boolean;
40
+ /**
41
+ * Diff a declared baseline against a resolved value map. Pure. No IO.
42
+ *
43
+ * Only the keys present in `baseline` are scored; extra keys in `resolved` are
44
+ * ignored. A baseline key with no matching entry in `resolved` is `missing`; a
45
+ * matching entry whose value satisfies `equals` is `compliant`, otherwise it is
46
+ * `drift`. An empty baseline scores 100 (nothing is required, so nothing is out
47
+ * of compliance).
48
+ */
49
+ export declare function scoreCoverage(baseline: Record<string, unknown>, resolved: Record<string, {
50
+ value: unknown;
51
+ }>, options?: ScoreOptions): CoverageResult;
52
+ //# sourceMappingURL=scoring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scoring.d.ts","sourceRoot":"","sources":["../src/scoring.ts"],"names":[],"mappings":"AAQA,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS,CAAC;AAE9D,8DAA8D;AAC9D,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,OAAO,CAAC;IAClB,sEAAsE;IACtE,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;CAC1D;AAMD;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAMtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,EAC5C,OAAO,CAAC,EAAE,YAAY,GACrB,cAAc,CA+BhB"}
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ // Baseline coverage scoring.
3
+ //
4
+ // A pure, content-free primitive for any product that measures a resolved state
5
+ // against a declared baseline. Given a baseline map (what each control is
6
+ // expected to be) and a resolved map (what each control actually is), it reports
7
+ // per-control compliant / drift / missing and an overall coverage score. It
8
+ // knows nothing about frameworks, providers, or where the values came from.
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.looseEqual = looseEqual;
11
+ exports.scoreCoverage = scoreCoverage;
12
+ function isObjectLike(value) {
13
+ return typeof value === "object" && value !== null;
14
+ }
15
+ /**
16
+ * The default string-loose comparator, exported for reuse.
17
+ *
18
+ * Objects and arrays compare by their JSON form; every other value (booleans,
19
+ * numbers, strings, `null`, `undefined`) compares by its textual form, so
20
+ * `true` equals `"true"` and `5` equals `"5"`. This tolerates the common case
21
+ * where a resolved config value arrives as a string even though its baseline
22
+ * was declared as a boolean or number. Note JSON comparison is order-sensitive:
23
+ * objects whose keys are in a different order are treated as unequal.
24
+ */
25
+ function looseEqual(expected, actual) {
26
+ if (isObjectLike(expected) || isObjectLike(actual)) {
27
+ if (!isObjectLike(expected) || !isObjectLike(actual))
28
+ return false;
29
+ return JSON.stringify(expected) === JSON.stringify(actual);
30
+ }
31
+ return String(expected) === String(actual);
32
+ }
33
+ /**
34
+ * Diff a declared baseline against a resolved value map. Pure. No IO.
35
+ *
36
+ * Only the keys present in `baseline` are scored; extra keys in `resolved` are
37
+ * ignored. A baseline key with no matching entry in `resolved` is `missing`; a
38
+ * matching entry whose value satisfies `equals` is `compliant`, otherwise it is
39
+ * `drift`. An empty baseline scores 100 (nothing is required, so nothing is out
40
+ * of compliance).
41
+ */
42
+ function scoreCoverage(baseline, resolved, options) {
43
+ const equals = options?.equals ?? looseEqual;
44
+ const details = [];
45
+ let compliant = 0;
46
+ let drift = 0;
47
+ let missing = 0;
48
+ for (const key of Object.keys(baseline)) {
49
+ const expected = baseline[key];
50
+ const entry = resolved[key];
51
+ if (entry === undefined) {
52
+ missing++;
53
+ details.push({ key, expected, actual: undefined, status: "missing" });
54
+ continue;
55
+ }
56
+ const actual = entry.value;
57
+ if (equals(expected, actual)) {
58
+ compliant++;
59
+ details.push({ key, expected, actual, status: "compliant" });
60
+ }
61
+ else {
62
+ drift++;
63
+ details.push({ key, expected, actual, status: "drift" });
64
+ }
65
+ }
66
+ const total = details.length;
67
+ const score = total === 0 ? 100 : Math.round((compliant / total) * 100);
68
+ return { total, compliant, drift, missing, score, details };
69
+ }
70
+ //# sourceMappingURL=scoring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scoring.js","sourceRoot":"","sources":["../src/scoring.ts"],"names":[],"mappings":";AAAA,6BAA6B;AAC7B,EAAE;AACF,gFAAgF;AAChF,0EAA0E;AAC1E,iFAAiF;AACjF,4EAA4E;AAC5E,4EAA4E;;AAgD5E,gCAMC;AAWD,sCAmCC;AAlED,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACrD,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,UAAU,CAAC,QAAiB,EAAE,MAAe;IAC3D,IAAI,YAAY,CAAC,QAAQ,CAAC,IAAI,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;QACnE,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,aAAa,CAC3B,QAAiC,EACjC,QAA4C,EAC5C,OAAsB;IAEtB,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,UAAU,CAAC;IAC7C,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAE5B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,EAAE,CAAC;YACV,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YACtE,SAAS;QACX,CAAC;QAED,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC;QAC3B,IAAI,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;YAC7B,SAAS,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;QAC/D,CAAC;aAAM,CAAC;YACN,KAAK,EAAE,CAAC;YACR,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC;IAC7B,MAAM,KAAK,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;IAExE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,50 @@
1
+ /** The data type of a catalog field's value. */
2
+ export type FieldType = "boolean" | "number" | "enum";
3
+ /**
4
+ * How a control's compliance is established: machine-`verified` from evidence,
5
+ * covered by a signed operator `attestation`, or self-`declared` by the caller.
6
+ */
7
+ export type Verification = "verified" | "attestation" | "declared";
8
+ /** A single configurable control in a catalog. */
9
+ export interface CatalogField {
10
+ key: string;
11
+ label: string;
12
+ /** The {@link CatalogGroup} this field belongs to, by name. */
13
+ group: string;
14
+ type: FieldType;
15
+ /** Choices for an `enum` field. */
16
+ options?: {
17
+ value: string;
18
+ label: string;
19
+ }[];
20
+ /** Suggested values for a `number` field. */
21
+ presets?: {
22
+ value: number;
23
+ label: string;
24
+ }[];
25
+ help: string;
26
+ verification: Verification;
27
+ /** For a `declared` field, the key of the verified control it maps to. */
28
+ verifiedControl?: string;
29
+ }
30
+ /** A named grouping of catalog fields. */
31
+ export interface CatalogGroup {
32
+ name: string;
33
+ blurb: string;
34
+ }
35
+ /** A control that no automated check can prove, covered by an attestation. */
36
+ export interface ManualControl {
37
+ key: string;
38
+ label: string;
39
+ /** The frameworks this manual control satisfies. */
40
+ frameworks: string[];
41
+ description: string;
42
+ }
43
+ /** A verified control definition. */
44
+ export interface ControlDef {
45
+ key: string;
46
+ label: string;
47
+ /** The declared field this control backs, or `null` if it stands alone. */
48
+ declaredKey: string | null;
49
+ }
50
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAOA,gDAAgD;AAChD,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEtD;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,aAAa,GAAG,UAAU,CAAC;AAEnE,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,mCAAmC;IACnC,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,6CAA6C;IAC7C,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,YAAY,CAAC;IAC3B,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,0CAA0C;AAC1C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qCAAqC;AACrC,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ // Control type vocabulary.
3
+ //
4
+ // Structural, content-free shapes for describing a catalog of controls: how a
5
+ // field is typed, how it is verified, and how declared controls relate to
6
+ // verified ones. These interfaces carry no data and no provider mapping. Bring
7
+ // your own catalog: populate them with your product's own content.
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,2BAA2B;AAC3B,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,+EAA+E;AAC/E,mEAAmE"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@stratum-hq/compliance",
3
+ "version": "0.1.0",
4
+ "description": "Content-free compliance kernel for Stratum: baseline coverage scoring, a finding state machine, and a control type vocabulary",
5
+ "keywords": [
6
+ "compliance",
7
+ "coverage",
8
+ "posture",
9
+ "drift",
10
+ "state-machine",
11
+ "control",
12
+ "audit",
13
+ "typescript",
14
+ "stratum"
15
+ ],
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "test": "vitest run",
32
+ "typecheck": "tsc --noEmit",
33
+ "lint": "eslint .",
34
+ "clean": "rm -rf dist"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "registry": "https://registry.npmjs.org/"
39
+ },
40
+ "devDependencies": {
41
+ "typescript": "^5.4.0",
42
+ "vitest": "^1.6.0"
43
+ },
44
+ "license": "MIT",
45
+ "author": "Christian Crank",
46
+ "homepage": "https://github.com/stratum-hq/Stratum/tree/main/packages/compliance#readme",
47
+ "bugs": "https://github.com/stratum-hq/Stratum/issues",
48
+ "engines": {
49
+ "node": ">=20.0.0"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/stratum-hq/Stratum.git",
54
+ "directory": "packages/compliance"
55
+ }
56
+ }