@transitrix/diagrams 1.6.0 → 1.8.1
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/dist/activities/layout.js +7 -7
- package/dist/activities/types.d.ts +4 -0
- package/dist/capability-map/dsm-schema.d.ts +1 -1
- package/dist/capability-map/dsm-schema.js +1 -1
- package/dist/capability-map/layout-tree.d.ts +5 -1
- package/dist/capability-map/layout-tree.js +11 -9
- package/dist/capability-map/render-capability-tree.d.ts +2 -9
- package/dist/capability-map/render-capability-tree.js +16 -28
- package/dist/codex/__tests__/example.test.d.ts +1 -0
- package/dist/codex/__tests__/example.test.js +33 -0
- package/dist/codex/__tests__/validate.test.d.ts +1 -0
- package/dist/codex/__tests__/validate.test.js +68 -0
- package/dist/codex/index.d.ts +4 -0
- package/dist/codex/index.js +2 -0
- package/dist/codex/types.d.ts +7 -0
- package/dist/codex/types.js +8 -0
- package/dist/codex/validate.d.ts +11 -0
- package/dist/codex/validate.js +103 -0
- package/dist/compliance/__tests__/canon-catalog.test.d.ts +1 -0
- package/dist/compliance/__tests__/canon-catalog.test.js +60 -0
- package/dist/compliance/__tests__/view-resolution.test.d.ts +1 -0
- package/dist/compliance/__tests__/view-resolution.test.js +59 -0
- package/dist/compliance/canon-catalog.d.ts +19 -0
- package/dist/compliance/canon-catalog.js +59 -0
- package/dist/compliance/coverage-metric.d.ts +12 -0
- package/dist/compliance/coverage-metric.js +35 -0
- package/dist/compliance/impact.d.ts +7 -1
- package/dist/compliance/impact.js +53 -0
- package/dist/compliance/index.d.ts +6 -4
- package/dist/compliance/index.js +3 -2
- package/dist/constraint/__tests__/example.test.d.ts +1 -0
- package/dist/constraint/__tests__/example.test.js +14 -0
- package/dist/constraint/__tests__/validate.test.d.ts +1 -0
- package/dist/constraint/__tests__/validate.test.js +58 -0
- package/dist/constraint/index.d.ts +4 -0
- package/dist/constraint/index.js +2 -0
- package/dist/constraint/types.d.ts +4 -0
- package/dist/constraint/types.js +2 -0
- package/dist/constraint/validate.d.ts +7 -0
- package/dist/constraint/validate.js +115 -0
- package/dist/fgca/__tests__/parse-canonical.test.js +31 -0
- package/dist/fgca/parse-canonical.d.ts +1 -1
- package/dist/fgca/parse-canonical.js +18 -4
- package/dist/fgca/preview-layout.d.ts +8 -4
- package/dist/fgca/preview-layout.js +13 -13
- package/dist/goals/GoalNode.d.ts +2 -1
- package/dist/goals/GoalNode.js +7 -2
- package/dist/goals/GoalTreeView.d.ts +3 -1
- package/dist/goals/GoalTreeView.js +31 -14
- package/dist/goals/__tests__/GoalTreeView.test.js +41 -0
- package/dist/goals/__tests__/mutations.test.js +39 -0
- package/dist/goals/__tests__/parse-canonical.test.js +2 -0
- package/dist/goals/layout.js +2 -1
- package/dist/goals/mutations.js +6 -6
- package/dist/goals/parse-canonical.d.ts +5 -0
- package/dist/goals/parse-canonical.js +5 -0
- package/dist/goals/types.d.ts +2 -0
- package/dist/node-size-presets.d.ts +50 -0
- package/dist/node-size-presets.js +103 -0
- package/dist/process-blueprint/layout.js +4 -47
- package/dist/repo-validate/types.js +2 -3
- package/dist/webview/__tests__/entity-text-layout.test.d.ts +1 -0
- package/dist/webview/__tests__/entity-text-layout.test.js +43 -0
- package/dist/webview/__tests__/entry.test.js +3 -3
- package/dist/webview/__tests__/render-blocks.test.js +1 -7
- package/dist/webview/__tests__/render-goals.test.js +17 -3
- package/dist/webview/entity-text-layout.d.ts +95 -0
- package/dist/webview/entity-text-layout.js +279 -0
- package/dist/webview/entry.js +7 -3
- package/dist/webview/notation-style.d.ts +1 -1
- package/dist/webview/notation-style.js +1 -1
- package/dist/webview/render-activities.d.ts +3 -1
- package/dist/webview/render-activities.js +29 -32
- package/dist/webview/render-blocks.d.ts +4 -1
- package/dist/webview/render-blocks.js +27 -108
- package/dist/webview/render-fgca.d.ts +15 -8
- package/dist/webview/render-fgca.js +41 -44
- package/dist/webview/render-goals.d.ts +2 -0
- package/dist/webview/render-goals.js +23 -37
- package/dist/webview/render-process-blueprint.d.ts +4 -10
- package/dist/webview/render-process-blueprint.js +18 -64
- package/package.json +1 -1
- package/src/activities/layout.ts +12 -7
- package/src/activities/types.ts +4 -0
- package/src/capability-map/dsm-schema.ts +1 -1
- package/src/capability-map/index.ts +1 -1
- package/src/capability-map/layout-tree.ts +16 -8
- package/src/capability-map/render-capability-tree.ts +18 -30
- package/src/codex/__tests__/example.test.ts +40 -0
- package/src/codex/__tests__/validate.test.ts +84 -0
- package/src/codex/index.ts +4 -0
- package/src/codex/types.ts +13 -0
- package/src/codex/validate.ts +125 -0
- package/src/compliance/__tests__/canon-catalog.test.ts +68 -0
- package/src/compliance/__tests__/view-resolution.test.ts +80 -0
- package/src/compliance/canon-catalog.ts +76 -0
- package/src/compliance/coverage-metric.ts +50 -0
- package/src/compliance/impact.ts +67 -1
- package/src/compliance/index.ts +10 -1
- package/src/constraint/__tests__/example.test.ts +19 -0
- package/src/constraint/__tests__/validate.test.ts +68 -0
- package/src/constraint/index.ts +4 -0
- package/src/constraint/types.ts +5 -0
- package/src/constraint/validate.ts +131 -0
- package/src/fgca/__tests__/parse-canonical.test.ts +34 -0
- package/src/fgca/parse-canonical.ts +17 -4
- package/src/fgca/preview-layout.ts +21 -15
- package/src/goals/GoalNode.tsx +8 -1
- package/src/goals/GoalTreeView.tsx +61 -27
- package/src/goals/__tests__/GoalTreeView.test.tsx +48 -0
- package/src/goals/__tests__/mutations.test.ts +42 -0
- package/src/goals/__tests__/parse-canonical.test.ts +2 -0
- package/src/goals/layout.ts +2 -1
- package/src/goals/mutations.ts +6 -6
- package/src/goals/parse-canonical.ts +6 -0
- package/src/goals/types.ts +2 -0
- package/src/node-size-presets.ts +157 -0
- package/src/process-blueprint/layout.ts +4 -48
- package/src/repo-validate/types.ts +2 -3
- package/src/webview/__tests__/entity-text-layout.test.ts +52 -0
- package/src/webview/__tests__/entry.test.ts +3 -3
- package/src/webview/__tests__/render-blocks.test.ts +1 -9
- package/src/webview/__tests__/render-goals.test.ts +17 -3
- package/src/webview/entity-text-layout.ts +355 -0
- package/src/webview/entry.ts +8 -4
- package/src/webview/notation-style.ts +1 -1
- package/src/webview/render-activities.ts +38 -27
- package/src/webview/render-blocks.ts +35 -118
- package/src/webview/render-fgca.ts +59 -44
- package/src/webview/render-goals.ts +24 -35
- package/src/webview/render-process-blueprint.ts +30 -65
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// CanonCatalog builder — repo-scope cross-reference resolution (#518 Phase C3).
|
|
2
|
+
//
|
|
3
|
+
// Scans admitted YAML documents and builds the `{ typeOf }` catalogue that
|
|
4
|
+
// `validateRequirement` / `validateAssertion` accept for REQ-002 and
|
|
5
|
+
// ASSERT-002..005 resolution.
|
|
6
|
+
import { typeOfId } from '../typed-id.js';
|
|
7
|
+
import { emptyCanon, ingestComplianceDoc } from './classify.js';
|
|
8
|
+
function strId(doc) {
|
|
9
|
+
if (doc === null || typeof doc !== 'object' || Array.isArray(doc))
|
|
10
|
+
return undefined;
|
|
11
|
+
const id = doc.id;
|
|
12
|
+
return typeof id === 'string' && id.trim() !== '' ? id.trim() : undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Admit one parsed document into the id → TYPE map. */
|
|
15
|
+
export function admitDocumentToCatalog(map, doc) {
|
|
16
|
+
const id = strId(doc);
|
|
17
|
+
if (!id)
|
|
18
|
+
return undefined;
|
|
19
|
+
const fromPrefix = typeOfId(id);
|
|
20
|
+
if (fromPrefix) {
|
|
21
|
+
map.set(id, fromPrefix);
|
|
22
|
+
return id;
|
|
23
|
+
}
|
|
24
|
+
if (doc !== null && typeof doc === 'object' && !Array.isArray(doc)) {
|
|
25
|
+
const d = doc;
|
|
26
|
+
if (d.zone === 'codex') {
|
|
27
|
+
const explicit = typeof d.type === 'string' ? d.type.trim().toUpperCase() : undefined;
|
|
28
|
+
if (explicit) {
|
|
29
|
+
map.set(id, explicit);
|
|
30
|
+
return id;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
/** Build a read-only `CanonCatalog` from a populated id → TYPE map. */
|
|
37
|
+
export function catalogFromMap(map) {
|
|
38
|
+
return { typeOf: (id) => map.get(id) };
|
|
39
|
+
}
|
|
40
|
+
/** Ingest compliance projections and admit cross-reference targets from a batch
|
|
41
|
+
* of already-parsed YAML documents (no IO). */
|
|
42
|
+
export function buildComplianceScan(docs) {
|
|
43
|
+
const complianceCanon = emptyCanon();
|
|
44
|
+
const catalogMap = new Map();
|
|
45
|
+
const pathById = new Map();
|
|
46
|
+
for (const { path, data } of docs) {
|
|
47
|
+
const ingested = ingestComplianceDoc(complianceCanon, data);
|
|
48
|
+
if (ingested)
|
|
49
|
+
pathById.set(ingested, path);
|
|
50
|
+
const admitted = admitDocumentToCatalog(catalogMap, data);
|
|
51
|
+
if (admitted && !pathById.has(admitted))
|
|
52
|
+
pathById.set(admitted, path);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
complianceCanon,
|
|
56
|
+
catalog: catalogFromMap(catalogMap),
|
|
57
|
+
pathById,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ComplianceCanon } from './classify.js';
|
|
2
|
+
import type { CanonCatalog } from '../typed-id.js';
|
|
2
3
|
/** Declarative filter resolved at build time from the canon's codex catalogue. */
|
|
3
4
|
export interface CoverageMetricRegimesFilter {
|
|
4
5
|
jurisdiction?: string[];
|
|
@@ -73,6 +74,17 @@ export interface CoverageMatrix {
|
|
|
73
74
|
rows: CoverageRow[];
|
|
74
75
|
thresholds: CoverageMetricThresholds;
|
|
75
76
|
}
|
|
77
|
+
/** Repo-scope cross-reference finding for a coverage-metric view (#518 C3). */
|
|
78
|
+
export interface CoverageViewFinding {
|
|
79
|
+
code: string;
|
|
80
|
+
severity: 'error' | 'warning';
|
|
81
|
+
message: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Repo-scope cross-reference checks for a parsed coverage-metric view config.
|
|
85
|
+
* Surfaces unresolved product and regime ids before `buildCoverageMatrix` runs.
|
|
86
|
+
*/
|
|
87
|
+
export declare function collectCoverageViewResolutionFindings(config: CoverageMetricConfig, catalog: CanonCatalog, complianceCanon: ComplianceCanon): CoverageViewFinding[];
|
|
76
88
|
/**
|
|
77
89
|
* Parse and validate a raw (YAML-parsed) document into a `CoverageMetricConfig`.
|
|
78
90
|
*
|
|
@@ -5,6 +5,41 @@
|
|
|
5
5
|
// across the scoped products, and what is the RAG status against the configured
|
|
6
6
|
// thresholds.
|
|
7
7
|
import { buildComplianceIndex } from './reverse-index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Repo-scope cross-reference checks for a parsed coverage-metric view config.
|
|
10
|
+
* Surfaces unresolved product and regime ids before `buildCoverageMatrix` runs.
|
|
11
|
+
*/
|
|
12
|
+
export function collectCoverageViewResolutionFindings(config, catalog, complianceCanon) {
|
|
13
|
+
const findings = [];
|
|
14
|
+
const codexIds = new Set(complianceCanon.codex.map((c) => c.id));
|
|
15
|
+
for (const id of config.subjects?.products ?? []) {
|
|
16
|
+
const t = catalog.typeOf(id);
|
|
17
|
+
if (t === undefined) {
|
|
18
|
+
findings.push({
|
|
19
|
+
code: 'COVMET-REF',
|
|
20
|
+
severity: 'error',
|
|
21
|
+
message: `subjects.products: "${id}" does not resolve to an admitted artefact.`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
else if (t !== 'PRODUCT') {
|
|
25
|
+
findings.push({
|
|
26
|
+
code: 'COVMET-REF',
|
|
27
|
+
severity: 'error',
|
|
28
|
+
message: `subjects.products: "${id}" resolves to ${t}, not PRODUCT.`,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const id of config.regimes?.include ?? []) {
|
|
33
|
+
if (!codexIds.has(id)) {
|
|
34
|
+
findings.push({
|
|
35
|
+
code: 'COVMET-REF',
|
|
36
|
+
severity: 'error',
|
|
37
|
+
message: `regimes.include: "${id}" does not resolve to an admitted codex artefact.`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return findings;
|
|
42
|
+
}
|
|
8
43
|
// ── Parser ───────────────────────────────────────────────────────────────────
|
|
9
44
|
function parseRegimes(raw) {
|
|
10
45
|
const warnings = [];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AssertionStatus } from '../assertion/types.js';
|
|
2
2
|
import type { ComplianceCanon } from './classify.js';
|
|
3
|
+
import type { CanonCatalog } from '../typed-id.js';
|
|
3
4
|
import type { IndexAssertion, IndexRequirement, ObjectDetailInput, DeadlineStatus } from './types.js';
|
|
4
5
|
/** Filter selecting REQUIREMENTs by codex source (jurisdiction / regime keys
|
|
5
6
|
* are accepted for forward compatibility but not yet honoured — the canon
|
|
@@ -57,12 +58,17 @@ export interface ImpactColumn {
|
|
|
57
58
|
* Used to enforce the subject type label invariant (§5.2, COMPIMP-010). */
|
|
58
59
|
subjectType: 'product' | 'process' | 'capability';
|
|
59
60
|
}
|
|
60
|
-
/** A structural validation finding emitted by the matrix builder. */
|
|
61
61
|
export interface ImpactFinding {
|
|
62
62
|
code: string;
|
|
63
63
|
severity: 'error' | 'warning';
|
|
64
64
|
message: string;
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Repo-scope cross-reference checks for a parsed compliance-impact view config
|
|
68
|
+
* (#518 Phase C3). Surfaces unresolved subject, obligation, and regime ids before
|
|
69
|
+
* `buildImpactMatrix` runs.
|
|
70
|
+
*/
|
|
71
|
+
export declare function collectImpactViewResolutionFindings(config: ImpactViewConfig, catalog: CanonCatalog, complianceCanon: ComplianceCanon): ImpactFinding[];
|
|
66
72
|
/**
|
|
67
73
|
* Grouping options for matrix columns.
|
|
68
74
|
*
|
|
@@ -7,6 +7,59 @@
|
|
|
7
7
|
// (`product` columns × `obligation` rows). Stage / task grouping is planned
|
|
8
8
|
// for CV-3a and intentionally out of scope here.
|
|
9
9
|
import { buildComplianceIndex } from './reverse-index.js';
|
|
10
|
+
/**
|
|
11
|
+
* Repo-scope cross-reference checks for a parsed compliance-impact view config
|
|
12
|
+
* (#518 Phase C3). Surfaces unresolved subject, obligation, and regime ids before
|
|
13
|
+
* `buildImpactMatrix` runs.
|
|
14
|
+
*/
|
|
15
|
+
export function collectImpactViewResolutionFindings(config, catalog, complianceCanon) {
|
|
16
|
+
const findings = [];
|
|
17
|
+
const codexIds = new Set(complianceCanon.codex.map((c) => c.id));
|
|
18
|
+
const subjectChecks = [
|
|
19
|
+
{ ids: config.subjects.products, expectedType: 'PRODUCT', path: 'subjects.products' },
|
|
20
|
+
{ ids: config.subjects.processes, expectedType: 'PROCESS', path: 'subjects.processes' },
|
|
21
|
+
{ ids: config.subjects.capabilities, expectedType: 'CAPABILITY', path: 'subjects.capabilities' },
|
|
22
|
+
];
|
|
23
|
+
for (const { ids, expectedType, path } of subjectChecks) {
|
|
24
|
+
for (const id of ids ?? []) {
|
|
25
|
+
const t = catalog.typeOf(id);
|
|
26
|
+
if (t === undefined) {
|
|
27
|
+
findings.push({
|
|
28
|
+
code: 'COMPIMP-REF',
|
|
29
|
+
severity: 'error',
|
|
30
|
+
message: `${path}: "${id}" does not resolve to an admitted artefact.`,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
else if (t !== expectedType) {
|
|
34
|
+
findings.push({
|
|
35
|
+
code: 'COMPIMP-REF',
|
|
36
|
+
severity: 'error',
|
|
37
|
+
message: `${path}: "${id}" resolves to ${t}, not ${expectedType}.`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const id of config.obligations.include ?? []) {
|
|
43
|
+
const t = catalog.typeOf(id);
|
|
44
|
+
if (t === undefined || t !== 'REQUIREMENT') {
|
|
45
|
+
findings.push({
|
|
46
|
+
code: 'COMPIMP-REF',
|
|
47
|
+
severity: 'error',
|
|
48
|
+
message: `obligations.include: "${id}" does not resolve to an admitted REQUIREMENT.`,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const id of config.obligations.filter?.derived_from_codex ?? []) {
|
|
53
|
+
if (!codexIds.has(id)) {
|
|
54
|
+
findings.push({
|
|
55
|
+
code: 'COMPIMP-REF',
|
|
56
|
+
severity: 'error',
|
|
57
|
+
message: `obligations.filter.derived_from_codex: "${id}" does not resolve to an admitted codex artefact.`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return findings;
|
|
62
|
+
}
|
|
10
63
|
// ── Pinned defaults ─────────────────────────────────────────────────────────
|
|
11
64
|
/**
|
|
12
65
|
* Explicit defaults for every optional field in ImpactViewConfig.
|
|
@@ -5,13 +5,15 @@ export { buildGapReport } from './gap-report.js';
|
|
|
5
5
|
export type { GapReport, GapReportOptions } from './gap-report.js';
|
|
6
6
|
export { emptyCanon, ingestComplianceDoc } from './classify.js';
|
|
7
7
|
export type { ComplianceCanon, ComplianceProduct, ComplianceCodexDoc } from './classify.js';
|
|
8
|
+
export { admitDocumentToCatalog, buildComplianceScan, catalogFromMap, } from './canon-catalog.js';
|
|
9
|
+
export type { ComplianceScanResult, ScannedYamlDoc } from './canon-catalog.js';
|
|
8
10
|
export { renderComplianceMarkdown } from './markdown.js';
|
|
9
11
|
export type { ReportScope, MarkdownOptions } from './markdown.js';
|
|
10
12
|
export { renderComplianceHtml, renderImpactMatrixHtml } from './html.js';
|
|
11
13
|
export type { HtmlOptions, ImpactMatrixHtmlOptions } from './html.js';
|
|
12
|
-
export { buildImpactMatrix, renderImpactMarkdown, parseImpactViewConfig, COMPLIANCE_IMPACT_DEFAULTS, extractObjectDetails, extractProcessFlowTasks, mergeStageTaskDetails, computeDeadlineStatus } from './impact.js';
|
|
13
|
-
export type { ImpactCell, ImpactColumn, ImpactEmptyCellLabels, ImpactGrouping, ImpactMatrix, ImpactObligationFilter, ImpactStatusDisplay, ImpactSubjects, ImpactViewConfig, ParseImpactViewConfigResult, } from './impact.js';
|
|
14
|
+
export { buildImpactMatrix, renderImpactMarkdown, parseImpactViewConfig, collectImpactViewResolutionFindings, COMPLIANCE_IMPACT_DEFAULTS, extractObjectDetails, extractProcessFlowTasks, mergeStageTaskDetails, computeDeadlineStatus } from './impact.js';
|
|
15
|
+
export type { ImpactCell, ImpactColumn, ImpactEmptyCellLabels, ImpactFinding, ImpactGrouping, ImpactMatrix, ImpactObligationFilter, ImpactStatusDisplay, ImpactSubjects, ImpactViewConfig, ParseImpactViewConfigResult, } from './impact.js';
|
|
14
16
|
export { assertionToScoringElement, complianceConfidenceHeader, requirementToScoringElement, scoreComplianceView, } from './confidence.js';
|
|
15
|
-
export { parseCoverageMetricConfig, buildCoverageMatrix, } from './coverage-metric.js';
|
|
16
|
-
export type { CoverageMetricConfig, CoverageMetricRegimes, CoverageMetricRegimesFilter, CoverageMetricSubjects, CoverageMetricThresholds, CoverageMatrix, CoverageRow, ParseCoverageMetricResult, RagStatus, } from './coverage-metric.js';
|
|
17
|
+
export { parseCoverageMetricConfig, buildCoverageMatrix, collectCoverageViewResolutionFindings, } from './coverage-metric.js';
|
|
18
|
+
export type { CoverageMetricConfig, CoverageMetricRegimes, CoverageMetricRegimesFilter, CoverageMetricSubjects, CoverageMetricThresholds, CoverageMatrix, CoverageRow, CoverageViewFinding, ParseCoverageMetricResult, RagStatus, } from './coverage-metric.js';
|
|
17
19
|
export type { ComplianceIndex, ComplianceIndexInput, DeadlineStatus, IndexRequirement, IndexAssertion, LawTree, LawTreeRequirement, ProductView, ProductRequirementStatus, ObjectDetailDef, ObjectDetailInput, RequirementTrace, TraceAssertionRow, TraceElementCatalog, TraceElementRef, TraceSourceRef, } from './types.js';
|
package/dist/compliance/index.js
CHANGED
|
@@ -3,8 +3,9 @@ export { buildLawTree, buildProductView } from './views.js';
|
|
|
3
3
|
export { buildRequirementTrace, buildTraceElementCatalog } from './trace.js';
|
|
4
4
|
export { buildGapReport } from './gap-report.js';
|
|
5
5
|
export { emptyCanon, ingestComplianceDoc } from './classify.js';
|
|
6
|
+
export { admitDocumentToCatalog, buildComplianceScan, catalogFromMap, } from './canon-catalog.js';
|
|
6
7
|
export { renderComplianceMarkdown } from './markdown.js';
|
|
7
8
|
export { renderComplianceHtml, renderImpactMatrixHtml } from './html.js';
|
|
8
|
-
export { buildImpactMatrix, renderImpactMarkdown, parseImpactViewConfig, COMPLIANCE_IMPACT_DEFAULTS, extractObjectDetails, extractProcessFlowTasks, mergeStageTaskDetails, computeDeadlineStatus } from './impact.js';
|
|
9
|
+
export { buildImpactMatrix, renderImpactMarkdown, parseImpactViewConfig, collectImpactViewResolutionFindings, COMPLIANCE_IMPACT_DEFAULTS, extractObjectDetails, extractProcessFlowTasks, mergeStageTaskDetails, computeDeadlineStatus } from './impact.js';
|
|
9
10
|
export { assertionToScoringElement, complianceConfidenceHeader, requirementToScoringElement, scoreComplianceView, } from './confidence.js';
|
|
10
|
-
export { parseCoverageMetricConfig, buildCoverageMatrix, } from './coverage-metric.js';
|
|
11
|
+
export { parseCoverageMetricConfig, buildCoverageMatrix, collectCoverageViewResolutionFindings, } from './coverage-metric.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import yaml from 'js-yaml';
|
|
6
|
+
import { validateConstraint } from '../validate.js';
|
|
7
|
+
const dir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../../tests/fixtures/notation-corpus/constraint');
|
|
8
|
+
describe('constraint worked example (notation-corpus)', () => {
|
|
9
|
+
it('validates CONSTRAINT-GDPR-RESIDENCY-1.yaml without error', () => {
|
|
10
|
+
const data = yaml.load(readFileSync(path.join(dir, 'CONSTRAINT-GDPR-RESIDENCY-1.yaml'), 'utf-8'));
|
|
11
|
+
const v = validateConstraint(data);
|
|
12
|
+
expect(v.valid, JSON.stringify(v.errors)).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { validateConstraint } from '../validate.js';
|
|
3
|
+
function valid() {
|
|
4
|
+
return {
|
|
5
|
+
notation: 'constraint',
|
|
6
|
+
id: 'CONSTRAINT-GDPR-RESIDENCY-1',
|
|
7
|
+
name: 'EU customer personal data must be stored within EU/EEA jurisdictions',
|
|
8
|
+
statement: 'Personal data of EU customers MUST NOT be persisted outside EU / EEA jurisdictions.',
|
|
9
|
+
status: 'active',
|
|
10
|
+
zone: 'canon',
|
|
11
|
+
admitted_at: '2026-05-29',
|
|
12
|
+
admitted_by: 'v.korobeinikov',
|
|
13
|
+
gate_checks: { uniqueness: 'pass', consistency: 'pass', completeness: 'pass' },
|
|
14
|
+
valid_from: '2018-05-25',
|
|
15
|
+
valid_to: null,
|
|
16
|
+
severity: 'mandatory',
|
|
17
|
+
owner_role: 'ROLE-DPO-1',
|
|
18
|
+
applies_to: ['APPLICATION-CRM-1'],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const codes = (input, opts) => validateConstraint(input, opts).errors.map((e) => e.code);
|
|
22
|
+
describe('validateConstraint — positive', () => {
|
|
23
|
+
it('accepts a well-formed constraint', () => {
|
|
24
|
+
const r = validateConstraint(valid());
|
|
25
|
+
expect(r.valid, JSON.stringify(r.errors)).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe('validateConstraint — CONST-001', () => {
|
|
29
|
+
it('flags missing required fields and wrong notation', () => {
|
|
30
|
+
const r = valid();
|
|
31
|
+
delete r.statement;
|
|
32
|
+
expect(codes(r)).toContain('CONST-001');
|
|
33
|
+
expect(codes({ ...valid(), notation: 'requirement' })).toContain('CONST-001');
|
|
34
|
+
expect(codes({ ...valid(), id: 'CONSTRAINT-001' })).toContain('CONST-001');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('validateConstraint — CONST-002/003', () => {
|
|
38
|
+
it('flags invalid status and severity', () => {
|
|
39
|
+
expect(codes({ ...valid(), status: 'enabled' })).toContain('CONST-002');
|
|
40
|
+
expect(codes({ ...valid(), severity: 'critical' })).toContain('CONST-003');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('validateConstraint — CONST-004/005 (catalog)', () => {
|
|
44
|
+
it('flags unresolved applies_to and owner_role with catalog', () => {
|
|
45
|
+
const catalog = { typeOf: () => undefined };
|
|
46
|
+
expect(codes(valid(), { catalog })).toContain('CONST-004');
|
|
47
|
+
expect(codes(valid(), { catalog })).toContain('CONST-005');
|
|
48
|
+
});
|
|
49
|
+
it('does not flag CONST-004 without catalog for well-formed applies_to', () => {
|
|
50
|
+
expect(codes(valid())).not.toContain('CONST-004');
|
|
51
|
+
});
|
|
52
|
+
it('resolves applies_to when catalogue admits the target', () => {
|
|
53
|
+
const catalog = {
|
|
54
|
+
typeOf: (id) => (id === 'APPLICATION-CRM-1' ? 'APPLICATION' : id === 'ROLE-DPO-1' ? 'ROLE' : undefined),
|
|
55
|
+
};
|
|
56
|
+
expect(validateConstraint(valid(), { catalog }).valid).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const CONSTRAINT_STATUSES: readonly ["active", "proposed", "deprecated", "retired"];
|
|
2
|
+
export type ConstraintStatus = (typeof CONSTRAINT_STATUSES)[number];
|
|
3
|
+
export declare const CONSTRAINT_SEVERITIES: readonly ["mandatory", "recommended", "advisory"];
|
|
4
|
+
export type ConstraintSeverity = (typeof CONSTRAINT_SEVERITIES)[number];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ValidationResult } from '../validation-types.js';
|
|
2
|
+
import { type CanonCatalog } from '../typed-id.js';
|
|
3
|
+
export interface ConstraintValidateOptions {
|
|
4
|
+
/** When provided, CONST-004/005 enforce artefact existence for cross-refs. */
|
|
5
|
+
catalog?: CanonCatalog;
|
|
6
|
+
}
|
|
7
|
+
export declare function validateConstraint(input: unknown, options?: ConstraintValidateOptions): ValidationResult;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// CONSTRAINT validator — methodology ELEMENT_PRIMITIVES.md §7.13 (sibling to
|
|
2
|
+
// 15-requirement.md). Implements CONST-001..005 for the compliance suite (#518 C4).
|
|
3
|
+
import { typeOfId, isCanonicalIdOfType } from '../typed-id.js';
|
|
4
|
+
import { CONSTRAINT_STATUSES, CONSTRAINT_SEVERITIES } from './types.js';
|
|
5
|
+
const REQUIRED_FIELDS = [
|
|
6
|
+
'notation',
|
|
7
|
+
'name',
|
|
8
|
+
'statement',
|
|
9
|
+
'status',
|
|
10
|
+
'zone',
|
|
11
|
+
'admitted_at',
|
|
12
|
+
'admitted_by',
|
|
13
|
+
'gate_checks',
|
|
14
|
+
'valid_from',
|
|
15
|
+
];
|
|
16
|
+
export function validateConstraint(input, options = {}) {
|
|
17
|
+
const errors = [];
|
|
18
|
+
const warnings = [];
|
|
19
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
20
|
+
errors.push({ code: 'CONST-001', message: 'Constraint must be a YAML mapping.' });
|
|
21
|
+
return { valid: false, errors, warnings };
|
|
22
|
+
}
|
|
23
|
+
const c = input;
|
|
24
|
+
if (!isCanonicalIdOfType(c.id, 'CONSTRAINT')) {
|
|
25
|
+
errors.push({
|
|
26
|
+
code: 'CONST-001',
|
|
27
|
+
message: `id "${String(c.id)}" must match CONSTRAINT-[<middle>-]<INTEGER>.`,
|
|
28
|
+
path: 'id',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (c.notation !== 'constraint') {
|
|
32
|
+
errors.push({ code: 'CONST-001', message: 'notation must be the fixed value "constraint".', path: 'notation' });
|
|
33
|
+
}
|
|
34
|
+
for (const f of REQUIRED_FIELDS) {
|
|
35
|
+
if (f === 'notation')
|
|
36
|
+
continue;
|
|
37
|
+
if (f === 'gate_checks') {
|
|
38
|
+
if (c.gate_checks === null || typeof c.gate_checks !== 'object') {
|
|
39
|
+
errors.push({ code: 'CONST-001', message: 'gate_checks is required and must be a mapping.', path: 'gate_checks' });
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const v = c[f];
|
|
44
|
+
if (typeof v !== 'string' || v.trim() === '') {
|
|
45
|
+
errors.push({ code: 'CONST-001', message: `${f} is required.`, path: f });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (c.zone !== 'canon') {
|
|
49
|
+
errors.push({ code: 'CONST-001', message: 'zone must be "canon" for a CONSTRAINT.', path: 'zone' });
|
|
50
|
+
}
|
|
51
|
+
if (!('valid_to' in c) || !(typeof c.valid_to === 'string' || c.valid_to === null)) {
|
|
52
|
+
errors.push({ code: 'CONST-001', message: 'valid_to is required (an ISO date string or null).', path: 'valid_to' });
|
|
53
|
+
}
|
|
54
|
+
const status = typeof c.status === 'string' ? c.status.trim() : '';
|
|
55
|
+
if (status && !CONSTRAINT_STATUSES.includes(status)) {
|
|
56
|
+
errors.push({
|
|
57
|
+
code: 'CONST-002',
|
|
58
|
+
message: `status must be one of ${CONSTRAINT_STATUSES.join(', ')}.`,
|
|
59
|
+
path: 'status',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (c.severity !== undefined) {
|
|
63
|
+
const severity = typeof c.severity === 'string' ? c.severity.trim() : '';
|
|
64
|
+
if (!severity || !CONSTRAINT_SEVERITIES.includes(severity)) {
|
|
65
|
+
errors.push({
|
|
66
|
+
code: 'CONST-003',
|
|
67
|
+
message: `severity must be one of ${CONSTRAINT_SEVERITIES.join(', ')}.`,
|
|
68
|
+
path: 'severity',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (c.applies_to !== undefined) {
|
|
73
|
+
if (!Array.isArray(c.applies_to)) {
|
|
74
|
+
errors.push({ code: 'CONST-001', message: 'applies_to must be a list of typed IDs.', path: 'applies_to' });
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
c.applies_to.forEach((ref, i) => {
|
|
78
|
+
const type = typeOfId(ref);
|
|
79
|
+
if (!type) {
|
|
80
|
+
errors.push({
|
|
81
|
+
code: 'CONST-004',
|
|
82
|
+
message: `applies_to[${i}] "${String(ref)}" is not a resolvable typed ID.`,
|
|
83
|
+
path: `applies_to[${i}]`,
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (options.catalog && options.catalog.typeOf(ref) === undefined) {
|
|
88
|
+
errors.push({
|
|
89
|
+
code: 'CONST-004',
|
|
90
|
+
message: `applies_to[${i}] "${String(ref)}" does not resolve to an admitted artefact.`,
|
|
91
|
+
path: `applies_to[${i}]`,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (c.owner_role !== undefined) {
|
|
98
|
+
const role = c.owner_role;
|
|
99
|
+
if (typeof role !== 'string' || !isCanonicalIdOfType(role, 'ROLE')) {
|
|
100
|
+
errors.push({
|
|
101
|
+
code: 'CONST-005',
|
|
102
|
+
message: `owner_role "${String(role)}" must be a typed ROLE id.`,
|
|
103
|
+
path: 'owner_role',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else if (options.catalog && options.catalog.typeOf(role) === undefined) {
|
|
107
|
+
errors.push({
|
|
108
|
+
code: 'CONST-005',
|
|
109
|
+
message: `owner_role "${role}" does not resolve to an admitted artefact.`,
|
|
110
|
+
path: 'owner_role',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
115
|
+
}
|
|
@@ -32,6 +32,29 @@ describe('parseCanonicalFGCA', () => {
|
|
|
32
32
|
expect(r.parsed?.changes).toHaveLength(1);
|
|
33
33
|
expect(r.parsed?.activities).toHaveLength(1);
|
|
34
34
|
});
|
|
35
|
+
it('accepts ACTION-* ids (methodology 1.0) alongside legacy ACTIVITY-*', () => {
|
|
36
|
+
const r = parseCanonicalFGCA({
|
|
37
|
+
...VALID,
|
|
38
|
+
actions: [
|
|
39
|
+
{ id: 'ACTION-CRM-EU-1', name: 'CRM rollout', changes: ['CHANGE-1'] },
|
|
40
|
+
{ id: 'ACTIVITY-2', name: 'Legacy workstream', changes: ['CHANGE-1'] },
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
expect(r.valid, JSON.stringify(r.errors)).toBe(true);
|
|
44
|
+
expect(r.parsed?.activities.map((a) => a.id)).toEqual(['ACTION-CRM-EU-1', 'ACTIVITY-2']);
|
|
45
|
+
});
|
|
46
|
+
it('defaults changes to [] when view_config.layers.changes is off (DGA-in-dgca)', () => {
|
|
47
|
+
const r = parseCanonicalFGCA({
|
|
48
|
+
...VALID,
|
|
49
|
+
changes: undefined,
|
|
50
|
+
view_config: { layers: { changes: 'off' } },
|
|
51
|
+
actions: [
|
|
52
|
+
{ id: 'ACTION-DISCOVERY-1', name: 'Gap assessment', goals: ['GOAL-1'] },
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
expect(r.valid, JSON.stringify(r.errors)).toBe(true);
|
|
56
|
+
expect(r.parsed?.changes).toEqual([]);
|
|
57
|
+
});
|
|
35
58
|
it('FGCA-001: rejects non-object input', () => {
|
|
36
59
|
expect(parseCanonicalFGCA(null).errors[0].code).toBe('FGCA-001');
|
|
37
60
|
expect(parseCanonicalFGCA('string').errors[0].code).toBe('FGCA-001');
|
|
@@ -178,6 +201,14 @@ describe('parseCanonicalFGA', () => {
|
|
|
178
201
|
expect(r.parsed?.goals).toHaveLength(1);
|
|
179
202
|
expect(r.parsed?.activities).toHaveLength(1);
|
|
180
203
|
});
|
|
204
|
+
it('accepts ACTION-* ids in FGA mode', () => {
|
|
205
|
+
const r = parseCanonicalFGA({
|
|
206
|
+
...VALID_FGA,
|
|
207
|
+
actions: [{ id: 'ACTION-GDPR-DSR-WORKFLOW-1', name: 'DSR workflow', goals: ['GOAL-1'] }],
|
|
208
|
+
});
|
|
209
|
+
expect(r.valid, JSON.stringify(r.errors)).toBe(true);
|
|
210
|
+
expect(r.parsed?.activities[0].id).toBe('ACTION-GDPR-DSR-WORKFLOW-1');
|
|
211
|
+
});
|
|
181
212
|
it('populates activity.goal_id from activity.goals[] (the FGA edge-driving field)', () => {
|
|
182
213
|
// The "FGA nodes render, no edges" bug: activities had no resolvable
|
|
183
214
|
// goal_id, so the renderer drew no goal → activity edges.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* - Flat top-level arrays, no `fgca:` wrapper.
|
|
8
8
|
* - Typed string IDs per `IDS_AND_REFERENCES.md` (`FACTOR-1`,
|
|
9
|
-
* `GOAL-RET-1`, `CHANGE-1`, `
|
|
9
|
+
* `GOAL-RET-1`, `CHANGE-1`, `ACTION-ONBOARD-1` (legacy `ACTIVITY-*` accepted).
|
|
10
10
|
* - Plural canonical-direction cross-references: `goal.factors`,
|
|
11
11
|
* `change.goals`, `activity.changes`, `activity.goals`.
|
|
12
12
|
* - Per-notation validation codes `FGCA-001 … FGCA-015`.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* - Flat top-level arrays, no `fgca:` wrapper.
|
|
8
8
|
* - Typed string IDs per `IDS_AND_REFERENCES.md` (`FACTOR-1`,
|
|
9
|
-
* `GOAL-RET-1`, `CHANGE-1`, `
|
|
9
|
+
* `GOAL-RET-1`, `CHANGE-1`, `ACTION-ONBOARD-1` (legacy `ACTIVITY-*` accepted).
|
|
10
10
|
* - Plural canonical-direction cross-references: `goal.factors`,
|
|
11
11
|
* `change.goals`, `activity.changes`, `activity.goals`.
|
|
12
12
|
* - Per-notation validation codes `FGCA-001 … FGCA-015`.
|
|
@@ -31,7 +31,8 @@ const DRIVER_ID_RE = /^DRIVER(-[A-Z0-9][A-Z0-9_]*)*-\d+$/;
|
|
|
31
31
|
const FACTOR_OR_DRIVER_ID_RE = /^(FACTOR|DRIVER)(-[A-Z0-9][A-Z0-9_]*)*-\d+$/;
|
|
32
32
|
const GOAL_ID_RE = /^GOAL(-[A-Z0-9][A-Z0-9_]*)*-\d+$/;
|
|
33
33
|
const CHANGE_ID_RE = /^CHANGE(-[A-Z0-9][A-Z0-9_]*)*-\d+$/;
|
|
34
|
-
|
|
34
|
+
// ACTION is canonical since methodology 1.0; ACTIVITY accepted for legacy docs.
|
|
35
|
+
const ACTION_OR_ACTIVITY_ID_RE = /^(ACTION|ACTIVITY)(-[A-Z0-9][A-Z0-9_]*)*-\d+$/;
|
|
35
36
|
const FGCA_DOC_ID_RE = /^(FGCA|DGCA)(-[A-Z0-9][A-Z0-9_]*)*-\d+$/;
|
|
36
37
|
const CONSTRAINT_ID_RE = /^CONSTRAINT(-[A-Z0-9][A-Z0-9_]*)*-\d+$/;
|
|
37
38
|
function isNonEmptyString(v) {
|
|
@@ -42,6 +43,16 @@ function asObjectArray(v) {
|
|
|
42
43
|
return null;
|
|
43
44
|
return v.map((el) => (el && typeof el === 'object' ? el : null));
|
|
44
45
|
}
|
|
46
|
+
/** DGA mode (`view_config.layers.changes: off`) may omit the changes layer. */
|
|
47
|
+
function isChangesLayerOff(raw) {
|
|
48
|
+
const vc = raw['view_config'];
|
|
49
|
+
if (!vc || typeof vc !== 'object')
|
|
50
|
+
return false;
|
|
51
|
+
const layers = vc['layers'];
|
|
52
|
+
if (!layers || typeof layers !== 'object')
|
|
53
|
+
return false;
|
|
54
|
+
return layers['changes'] === 'off';
|
|
55
|
+
}
|
|
45
56
|
/**
|
|
46
57
|
* Validate canonical FGCA YAML and, on success, return an internal-form
|
|
47
58
|
* `FGCADoc` ready for `buildFGCALayout`.
|
|
@@ -87,7 +98,10 @@ export function parseCanonicalFGCA(input) {
|
|
|
87
98
|
}
|
|
88
99
|
const factorsRaw = asObjectArray(raw['factors']);
|
|
89
100
|
const goalsRaw = asObjectArray(raw['goals']);
|
|
90
|
-
|
|
101
|
+
let changesRaw = asObjectArray(raw['changes']);
|
|
102
|
+
if (changesRaw === null && raw['changes'] === undefined && isChangesLayerOff(raw)) {
|
|
103
|
+
changesRaw = [];
|
|
104
|
+
}
|
|
91
105
|
const activitiesRaw = asObjectArray(raw['actions']);
|
|
92
106
|
if (activitiesRaw === null && 'activities' in raw) {
|
|
93
107
|
errors.push({ code: 'FGCA-004', message: '"activities" key is not accepted — rename to "actions"', path: 'actions' });
|
|
@@ -145,7 +159,7 @@ export function parseCanonicalFGCA(input) {
|
|
|
145
159
|
factors.forEach((el, i) => checkLayerElement(el, `factors[${i}]`, FACTOR_OR_DRIVER_ID_RE, factorIds));
|
|
146
160
|
goals.forEach((el, i) => checkLayerElement(el, `goals[${i}]`, GOAL_ID_RE, goalIds));
|
|
147
161
|
changes.forEach((el, i) => checkLayerElement(el, `changes[${i}]`, CHANGE_ID_RE, changeIds));
|
|
148
|
-
activities.forEach((el, i) => checkLayerElement(el, `activities[${i}]`,
|
|
162
|
+
activities.forEach((el, i) => checkLayerElement(el, `activities[${i}]`, ACTION_OR_ACTIVITY_ID_RE, activityIds));
|
|
149
163
|
if (errors.length > 0)
|
|
150
164
|
return { valid: false, errors, warnings };
|
|
151
165
|
// Cross-refs.
|
|
@@ -23,7 +23,7 @@ export interface FGCAPreviewActivity {
|
|
|
23
23
|
name: string;
|
|
24
24
|
goal_id?: number | string | null;
|
|
25
25
|
}
|
|
26
|
-
/** Structural input the preview layout needs — a subset of the parsed
|
|
26
|
+
/** Structural input the preview layout needs — a subset of the parsed DGCA/DGA doc. */
|
|
27
27
|
export interface FGCAPreviewDoc {
|
|
28
28
|
factors: FGCAPreviewFactor[];
|
|
29
29
|
goals: FGCAPreviewGoal[];
|
|
@@ -31,7 +31,7 @@ export interface FGCAPreviewDoc {
|
|
|
31
31
|
activities: FGCAPreviewActivity[];
|
|
32
32
|
}
|
|
33
33
|
export interface FGCAPreviewLayoutOptions {
|
|
34
|
-
/**
|
|
34
|
+
/** DGA hides the Changes column and links Goal → Activity directly. */
|
|
35
35
|
hideChanges?: boolean;
|
|
36
36
|
/** Horizontal gap (px) between columns. Default matches the historical hardcoded value. */
|
|
37
37
|
colGap?: number;
|
|
@@ -39,11 +39,15 @@ export interface FGCAPreviewLayoutOptions {
|
|
|
39
39
|
rowGap?: number;
|
|
40
40
|
/** Trim to a level cap or a single root goal (vkgeorgia/strategy#77). Defaults to 'all'. */
|
|
41
41
|
scope?: Scope;
|
|
42
|
+
/** Entity node width (px). Default {@link FGCA_NODE_W}. */
|
|
43
|
+
nodeWidth?: number;
|
|
44
|
+
/** Entity node height (px). Default {@link FGCA_NODE_H}. */
|
|
45
|
+
nodeHeight?: number;
|
|
42
46
|
}
|
|
43
47
|
/**
|
|
44
|
-
* Trims
|
|
48
|
+
* Trims a DGCA/DGA doc to a scope.
|
|
45
49
|
*
|
|
46
|
-
*
|
|
50
|
+
* DGCA/DGA goals are flat (no parent_id), so:
|
|
47
51
|
* - 'level' → goals with `(level ?? 0) <= maxLevel`.
|
|
48
52
|
* - 'root' → the single goal whose id matches `rootGoalId` (empty when absent).
|
|
49
53
|
*
|