@transitrix/diagrams 1.9.18 → 1.11.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/dist/activities/resolver.d.ts +3 -0
- package/dist/activities/resolver.js +39 -18
- package/dist/activities/validate.js +59 -0
- package/dist/canon-resolver-utils.d.ts +3 -0
- package/dist/canon-resolver-utils.js +16 -0
- package/dist/codex/index.d.ts +1 -1
- package/dist/codex/index.js +1 -1
- package/dist/codex/types.d.ts +9 -2
- package/dist/codex/types.js +10 -3
- package/dist/codex/validate.js +29 -3
- package/dist/compliance/classify.d.ts +11 -1
- package/dist/compliance/classify.js +31 -17
- package/dist/compliance/impact.d.ts +3 -1
- package/dist/compliance/impact.js +41 -13
- package/dist/compliance/types.d.ts +6 -0
- package/dist/compliance-matrix/build.js +0 -0
- package/dist/compliance-verification-matrix/build.d.ts +17 -0
- package/dist/compliance-verification-matrix/build.js +65 -0
- package/dist/compliance-verification-matrix/csv.d.ts +6 -0
- package/dist/compliance-verification-matrix/csv.js +41 -0
- package/dist/compliance-verification-matrix/index.d.ts +3 -0
- package/dist/compliance-verification-matrix/index.js +2 -0
- package/dist/compliance-verification-matrix/types.d.ts +75 -0
- package/dist/compliance-verification-matrix/types.js +4 -0
- package/dist/fgca/action-change-ids.d.ts +7 -0
- package/dist/fgca/action-change-ids.js +11 -0
- package/dist/fgca/parse-canonical.d.ts +3 -1
- package/dist/fgca/parse-canonical.js +13 -8
- package/dist/fgca/resolver.d.ts +6 -2
- package/dist/fgca/resolver.js +12 -6
- package/dist/fgca/validate.d.ts +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/process-blueprint/index.d.ts +3 -1
- package/dist/process-blueprint/index.js +1 -0
- package/dist/process-blueprint/layout.js +33 -29
- package/dist/process-blueprint/resolve-columns.d.ts +52 -0
- package/dist/process-blueprint/resolve-columns.js +138 -0
- package/dist/process-blueprint/types.d.ts +23 -5
- package/dist/process-blueprint/validate.d.ts +19 -1
- package/dist/process-blueprint/validate.js +73 -21
- package/dist/repo-validate/check-candidate-fields.d.ts +6 -0
- package/dist/repo-validate/check-candidate-fields.js +87 -0
- package/dist/repo-validate/check-process-parent.d.ts +8 -0
- package/dist/repo-validate/check-process-parent.js +172 -0
- package/dist/repo-validate/index.d.ts +1 -0
- package/dist/repo-validate/index.js +1 -0
- package/dist/repo-validate/validate-repo.js +7 -0
- package/dist/requirement/types.d.ts +1 -1
- package/dist/requirement/types.js +1 -1
- package/dist/webview/entry.js +11 -4
- package/dist/webview/render-fgca.js +1 -1
- package/dist/webview/render-process.d.ts +19 -1
- package/dist/webview/render-process.js +75 -42
- package/package.json +1 -1
- package/src/activities/resolver.ts +40 -20
- package/src/activities/validate.ts +58 -0
- package/src/canon-resolver-utils.ts +19 -0
- package/src/codex/index.ts +1 -1
- package/src/codex/types.ts +11 -3
- package/src/codex/validate.ts +30 -2
- package/src/compliance/classify.ts +40 -17
- package/src/compliance/impact.ts +41 -12
- package/src/compliance/types.ts +6 -0
- package/src/compliance-matrix/build.ts +0 -0
- package/src/compliance-verification-matrix/build.ts +84 -0
- package/src/compliance-verification-matrix/csv.ts +47 -0
- package/src/compliance-verification-matrix/index.ts +7 -0
- package/src/compliance-verification-matrix/types.ts +85 -0
- package/src/fgca/action-change-ids.ts +13 -0
- package/src/fgca/parse-canonical.ts +12 -8
- package/src/fgca/resolver.ts +15 -6
- package/src/fgca/validate.ts +2 -0
- package/src/index.ts +1 -0
- package/src/process-blueprint/index.ts +11 -0
- package/src/process-blueprint/layout.ts +48 -29
- package/src/process-blueprint/resolve-columns.ts +147 -0
- package/src/process-blueprint/types.ts +19 -5
- package/src/process-blueprint/validate.ts +107 -21
- package/src/repo-validate/check-candidate-fields.ts +93 -0
- package/src/repo-validate/check-process-parent.ts +184 -0
- package/src/repo-validate/index.ts +5 -0
- package/src/repo-validate/validate-repo.ts +7 -0
- package/src/requirement/types.ts +1 -1
- package/src/webview/entry.ts +13 -6
- package/src/webview/render-fgca.ts +1 -1
- package/src/webview/render-process.ts +94 -49
|
@@ -7,7 +7,7 @@ export interface GateChecks {
|
|
|
7
7
|
completeness?: string;
|
|
8
8
|
}
|
|
9
9
|
/** TYPEs a requirement may be derived from (15-requirement.md §2, REQ-003). */
|
|
10
|
-
export declare const REQUIREMENT_DERIVED_FROM_TYPES: readonly ["LAW", "REGULATION", "POLICY", "INTERNAL_STANDARD"];
|
|
10
|
+
export declare const REQUIREMENT_DERIVED_FROM_TYPES: readonly ["LAW", "REGULATION", "POLICY", "INTERNAL_STANDARD", "PRINCIPLE"];
|
|
11
11
|
/** ISO/IEC/IEEE 29148 specification-tier ladder (15-requirement.md §2.5, REQ-005). */
|
|
12
12
|
export type RequirementLevel = 'stakeholder' | 'system' | 'software';
|
|
13
13
|
export declare const REQUIREMENT_LEVELS: readonly RequirementLevel[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// REQUIREMENT — motivation-layer positive obligation.
|
|
2
2
|
// Schema: methodology notations/elements/15-requirement.md §2.
|
|
3
3
|
/** TYPEs a requirement may be derived from (15-requirement.md §2, REQ-003). */
|
|
4
|
-
export const REQUIREMENT_DERIVED_FROM_TYPES = ['LAW', 'REGULATION', 'POLICY', 'INTERNAL_STANDARD'];
|
|
4
|
+
export const REQUIREMENT_DERIVED_FROM_TYPES = ['LAW', 'REGULATION', 'POLICY', 'INTERNAL_STANDARD', 'PRINCIPLE'];
|
|
5
5
|
export const REQUIREMENT_LEVELS = ['stakeholder', 'system', 'software'];
|
|
6
6
|
export const REQUIREMENT_KINDS = ['functional', 'quality'];
|
package/dist/webview/entry.js
CHANGED
|
@@ -28,7 +28,7 @@ import yaml from 'js-yaml';
|
|
|
28
28
|
import { validateActivities } from '../activities/index.js';
|
|
29
29
|
import { validateActivityCard } from '../activity-card/index.js';
|
|
30
30
|
import { validateApplicationsCatalogue } from '../applications/index.js';
|
|
31
|
-
import {
|
|
31
|
+
import { validateBlocks } from '../blocks/index.js';
|
|
32
32
|
import { validateCapabilityMap } from '../capability-map/validate.js';
|
|
33
33
|
import { parseCanonicalFGCA, parseCanonicalFGA } from '../fgca/parse-canonical.js';
|
|
34
34
|
import { parseCanonicalGoals } from '../goals/parse-canonical.js';
|
|
@@ -40,7 +40,7 @@ import { coerceDatesToIsoStrings } from '../yaml-normalize.js';
|
|
|
40
40
|
import { renderActivitiesSvg } from './render-activities.js';
|
|
41
41
|
import { renderActivityCardSvg } from './render-activity-card.js';
|
|
42
42
|
import { renderApplicationsHtml } from './render-applications.js';
|
|
43
|
-
import { renderBlocksSvg } from './render-blocks.js';
|
|
43
|
+
import { renderBlocksSvg, renderGridSvg } from './render-blocks.js';
|
|
44
44
|
import { renderCapabilityMapHtml } from './render-capability-map.js';
|
|
45
45
|
import { renderFgcaSvg } from './render-fgca.js';
|
|
46
46
|
import { renderGoalsSvg } from './render-goals.js';
|
|
@@ -138,8 +138,15 @@ function dispatchValidate(kind, doc) {
|
|
|
138
138
|
return renderFromValidation('action-card', validateActivityCard(doc), () => renderActivityCardSvg(doc));
|
|
139
139
|
case 'process-blueprint':
|
|
140
140
|
return renderFromValidation('process-blueprint', validateProcessBlueprint(doc), () => renderProcessBlueprintSvg(doc));
|
|
141
|
-
case 'blocks':
|
|
142
|
-
|
|
141
|
+
case 'blocks': {
|
|
142
|
+
// Two alternative root forms (08-blocks.md §4): a `nested_blocks:`
|
|
143
|
+
// containment tree, or a `grid:` matrix subset (§4a). `validateBlocks`
|
|
144
|
+
// is the shared root-form dispatcher (BL-020 on both/neither present);
|
|
145
|
+
// mirrors the VS Code path's `hasGrid` branch in `blocks-preview.ts`.
|
|
146
|
+
const raw = (doc && typeof doc === 'object' ? doc : {});
|
|
147
|
+
const hasGrid = raw['grid'] !== undefined && raw['grid'] !== null;
|
|
148
|
+
return renderFromValidation('blocks', validateBlocks(doc), () => hasGrid ? renderGridSvg(doc) : renderBlocksSvg(doc));
|
|
149
|
+
}
|
|
143
150
|
// Catalogue notations render an HTML fragment from the header nested under
|
|
144
151
|
// their top-level wrapper key (`<thing>_catalogue` / `scenario` / …).
|
|
145
152
|
case 'applications':
|
|
@@ -67,7 +67,7 @@ export function renderFgcaBody(columns, nodes, edges, curvature, entryCurvature,
|
|
|
67
67
|
}
|
|
68
68
|
export function renderFgcaSvg(doc, options = {}) {
|
|
69
69
|
const { variant = 'dgca', title = '', curvature = DEFAULT_EDGE_CURVATURE, entryCurvature, nodeSizePreset = 'normal', layoutOptions, } = options;
|
|
70
|
-
const hideChanges = variant === 'dga';
|
|
70
|
+
const hideChanges = variant === 'dga' || doc.hideChanges === true;
|
|
71
71
|
const nodeSize = resolveDgcaNodeSize(parseNodeSizePreset(nodeSizePreset));
|
|
72
72
|
const { nodes, edges, columns, width, height } = layoutFGCAPreview(doc, {
|
|
73
73
|
hideChanges,
|
|
@@ -64,13 +64,31 @@ export interface ProcessDiagramLayout {
|
|
|
64
64
|
flows: ProcessFlow[];
|
|
65
65
|
associations: ProcessAssociation[];
|
|
66
66
|
}
|
|
67
|
+
export interface BpmnTypography {
|
|
68
|
+
taskFontPx: number;
|
|
69
|
+
headerFontPx: number;
|
|
70
|
+
labelFontPx: number;
|
|
71
|
+
taskChars: number;
|
|
72
|
+
taskLineH: number;
|
|
73
|
+
labelChars: number;
|
|
74
|
+
labelLineH: number;
|
|
75
|
+
headerCharW: number;
|
|
76
|
+
}
|
|
77
|
+
/** Interactive-preview / default-export type sizes. */
|
|
78
|
+
export declare const DEFAULT_BPMN_TYPOGRAPHY: BpmnTypography;
|
|
79
|
+
/** Presentation-export type sizes (20 px floor, wrap tuned to default node boxes). */
|
|
80
|
+
export declare const PRESENTATION_BPMN_TYPOGRAPHY: BpmnTypography;
|
|
67
81
|
export interface RenderProcessOptions {
|
|
68
82
|
/** Raw SVG injected after `<style>` — a title line or full title block. */
|
|
69
83
|
title?: string;
|
|
70
84
|
/** Extra vertical space (px) reserved above the pool for a title block. */
|
|
71
85
|
topInset?: number;
|
|
86
|
+
/** Label metrics. Omit for the interactive-preview defaults. */
|
|
87
|
+
typography?: BpmnTypography;
|
|
72
88
|
}
|
|
73
|
-
export declare
|
|
89
|
+
export declare function bpmnProcessCss(t?: BpmnTypography): string;
|
|
90
|
+
/** Default-preview CSS (11 px / 10 px). Presentation export builds its own via `bpmnProcessCss`. */
|
|
91
|
+
export declare const BPMN_PROCESS_CSS: string;
|
|
74
92
|
/**
|
|
75
93
|
* Canonical body emitter, shared by every host (VS Code, JCEF, static export).
|
|
76
94
|
*
|
|
@@ -14,6 +14,28 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { generateSvgEmbedCss } from '../theme/index.js';
|
|
16
16
|
import { escXml } from './render-util.js';
|
|
17
|
+
/** Interactive-preview / default-export type sizes. */
|
|
18
|
+
export const DEFAULT_BPMN_TYPOGRAPHY = {
|
|
19
|
+
taskFontPx: 11,
|
|
20
|
+
headerFontPx: 11,
|
|
21
|
+
labelFontPx: 10,
|
|
22
|
+
taskChars: 14,
|
|
23
|
+
taskLineH: 14,
|
|
24
|
+
labelChars: 18,
|
|
25
|
+
labelLineH: 13,
|
|
26
|
+
headerCharW: 6.5,
|
|
27
|
+
};
|
|
28
|
+
/** Presentation-export type sizes (20 px floor, wrap tuned to default node boxes). */
|
|
29
|
+
export const PRESENTATION_BPMN_TYPOGRAPHY = {
|
|
30
|
+
taskFontPx: 20,
|
|
31
|
+
headerFontPx: 20,
|
|
32
|
+
labelFontPx: 20,
|
|
33
|
+
taskChars: 9,
|
|
34
|
+
taskLineH: 22,
|
|
35
|
+
labelChars: 14,
|
|
36
|
+
labelLineH: 22,
|
|
37
|
+
headerCharW: 11.5,
|
|
38
|
+
};
|
|
17
39
|
// ---------------------------------------------------------------------------
|
|
18
40
|
// Constants — match DEFAULT_LAYOUT_DIAGRAM_OPTIONS in src/layout-options.ts
|
|
19
41
|
// ---------------------------------------------------------------------------
|
|
@@ -21,43 +43,49 @@ import { escXml } from './render-util.js';
|
|
|
21
43
|
const LANE_LABEL_BAND = 44;
|
|
22
44
|
/** End margin (px) along the header height axis for rotated pool/lane captions. */
|
|
23
45
|
const HEADER_LABEL_AXIS_PAD = 32;
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
/** Active wrap/font metrics for the current render call (sync, restored after). */
|
|
47
|
+
let typo = DEFAULT_BPMN_TYPOGRAPHY;
|
|
48
|
+
function runWithTypography(t, fn) {
|
|
49
|
+
const prev = typo;
|
|
50
|
+
typo = t;
|
|
51
|
+
try {
|
|
52
|
+
return fn();
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
typo = prev;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
34
58
|
// ---------------------------------------------------------------------------
|
|
35
59
|
// BPMN-specific CSS — no font-style:italic per CLAUDE.md design rule
|
|
36
60
|
// ---------------------------------------------------------------------------
|
|
37
|
-
export
|
|
61
|
+
export function bpmnProcessCss(t = DEFAULT_BPMN_TYPOGRAPHY) {
|
|
62
|
+
return `
|
|
38
63
|
.bpmn-pool { fill: var(--ts-bg-surface,#f8fafc); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }
|
|
39
64
|
.bpmn-pool-name { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }
|
|
40
65
|
.bpmn-lane { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }
|
|
41
66
|
.bpmn-lane-header { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }
|
|
42
|
-
.bpmn-pool-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size:
|
|
43
|
-
.bpmn-lane-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size:
|
|
67
|
+
.bpmn-pool-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.headerFontPx}px; font-weight: 700; }
|
|
68
|
+
.bpmn-lane-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.headerFontPx}px; font-weight: 600; }
|
|
44
69
|
.bpmn-task { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }
|
|
45
|
-
.bpmn-task-name { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size:
|
|
70
|
+
.bpmn-task-name { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.taskFontPx}px; text-anchor: middle; }
|
|
46
71
|
.bpmn-event { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); }
|
|
47
72
|
.bpmn-event-start { stroke-width: 1.5; }
|
|
48
73
|
.bpmn-event-end { stroke-width: 4; }
|
|
49
|
-
.bpmn-event-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size:
|
|
74
|
+
.bpmn-event-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.labelFontPx}px; text-anchor: middle; }
|
|
50
75
|
.bpmn-gateway { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }
|
|
51
76
|
.bpmn-gateway-marker { stroke: var(--ts-node-stroke,#004d67); stroke-width: 2.5; stroke-linecap: round; fill: none; }
|
|
52
|
-
.bpmn-gateway-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size:
|
|
77
|
+
.bpmn-gateway-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.labelFontPx}px; text-anchor: middle; }
|
|
53
78
|
.bpmn-seq-flow { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; }
|
|
54
79
|
.bpmn-default-mark { stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; }
|
|
55
80
|
.bpmn-assoc { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1; stroke-dasharray: 4 2; }
|
|
56
81
|
.bpmn-data-obj { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1; }
|
|
57
|
-
.bpmn-data-obj-label { fill: var(--ts-text-secondary,#516970); font-size:
|
|
82
|
+
.bpmn-data-obj-label { fill: var(--ts-text-secondary,#516970); font-size: ${t.labelFontPx}px; text-anchor: middle; }
|
|
58
83
|
.bpmn-event-intermediate { fill: none; stroke: var(--ts-node-stroke,#004d67); stroke-width: 1; }
|
|
59
84
|
.bpmn-event-icon { fill: none; stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
|
|
60
85
|
`;
|
|
86
|
+
}
|
|
87
|
+
/** Default-preview CSS (11 px / 10 px). Presentation export builds its own via `bpmnProcessCss`. */
|
|
88
|
+
export const BPMN_PROCESS_CSS = bpmnProcessCss(DEFAULT_BPMN_TYPOGRAPHY);
|
|
61
89
|
// ---------------------------------------------------------------------------
|
|
62
90
|
// Internal helpers
|
|
63
91
|
// ---------------------------------------------------------------------------
|
|
@@ -73,28 +101,29 @@ function wordTruncate(text, maxChars) {
|
|
|
73
101
|
return (lastSpace > maxChars / 2 ? cut.slice(0, lastSpace) : cut) + '…';
|
|
74
102
|
}
|
|
75
103
|
function fitRotatedHeaderText(name, spanPx) {
|
|
76
|
-
const maxChars = Math.max(4, Math.floor(spanPx /
|
|
104
|
+
const maxChars = Math.max(4, Math.floor(spanPx / typo.headerCharW));
|
|
77
105
|
return wordTruncate(name, maxChars);
|
|
78
106
|
}
|
|
79
107
|
function wrapTaskName(name) {
|
|
80
108
|
if (!name)
|
|
81
109
|
return [];
|
|
110
|
+
const limit = typo.taskChars;
|
|
82
111
|
const words = name.split(/\s+/);
|
|
83
112
|
const lines = [];
|
|
84
113
|
let cur = '';
|
|
85
114
|
for (const w of words) {
|
|
86
|
-
const token = w.length >
|
|
115
|
+
const token = w.length > limit ? w.slice(0, limit - 1) + '…' : w;
|
|
87
116
|
if (!cur) {
|
|
88
117
|
cur = token;
|
|
89
118
|
}
|
|
90
|
-
else if (cur.length + 1 + token.length <=
|
|
119
|
+
else if (cur.length + 1 + token.length <= limit) {
|
|
91
120
|
cur += ' ' + token;
|
|
92
121
|
}
|
|
93
122
|
else {
|
|
94
123
|
lines.push(cur);
|
|
95
124
|
if (lines.length === 3) {
|
|
96
|
-
if (lines[2].length >
|
|
97
|
-
lines[2] = lines[2].slice(0,
|
|
125
|
+
if (lines[2].length > limit - 1)
|
|
126
|
+
lines[2] = lines[2].slice(0, limit - 1) + '…';
|
|
98
127
|
return lines;
|
|
99
128
|
}
|
|
100
129
|
cur = token;
|
|
@@ -103,28 +132,29 @@ function wrapTaskName(name) {
|
|
|
103
132
|
if (cur && lines.length < 3)
|
|
104
133
|
lines.push(cur);
|
|
105
134
|
if (lines.length === 0)
|
|
106
|
-
lines.push(name.slice(0,
|
|
135
|
+
lines.push(name.slice(0, limit));
|
|
107
136
|
return lines;
|
|
108
137
|
}
|
|
109
138
|
function taskNameSvg(name, cx, cy) {
|
|
110
139
|
if (!name)
|
|
111
140
|
return '';
|
|
112
141
|
const lines = wrapTaskName(name);
|
|
113
|
-
const totalH = lines.length *
|
|
114
|
-
const startY = cy - totalH / 2 +
|
|
142
|
+
const totalH = lines.length * typo.taskLineH;
|
|
143
|
+
const startY = cy - totalH / 2 + typo.taskLineH * 0.8;
|
|
115
144
|
const tspans = lines
|
|
116
|
-
.map((ln, i) => `<tspan x="${r(cx)}" y="${r(startY + i *
|
|
145
|
+
.map((ln, i) => `<tspan x="${r(cx)}" y="${r(startY + i * typo.taskLineH)}">${escXml(ln)}</tspan>`)
|
|
117
146
|
.join('');
|
|
118
147
|
return `<text class="bpmn-task-name">${tspans}</text>`;
|
|
119
148
|
}
|
|
120
149
|
function wrapBelowLabel(name) {
|
|
150
|
+
const limit = typo.labelChars;
|
|
121
151
|
const words = name.split(/\s+/).filter(Boolean);
|
|
122
152
|
const allLines = [];
|
|
123
153
|
let cur = '';
|
|
124
154
|
for (const word of words) {
|
|
125
|
-
const tok = word.length >
|
|
155
|
+
const tok = word.length > limit ? word.slice(0, limit - 1) + '…' : word;
|
|
126
156
|
const test = cur ? `${cur} ${tok}` : tok;
|
|
127
|
-
if (test.length <=
|
|
157
|
+
if (test.length <= limit) {
|
|
128
158
|
cur = test;
|
|
129
159
|
}
|
|
130
160
|
else {
|
|
@@ -136,12 +166,12 @@ function wrapBelowLabel(name) {
|
|
|
136
166
|
if (cur)
|
|
137
167
|
allLines.push(cur);
|
|
138
168
|
if (allLines.length === 0)
|
|
139
|
-
return [name.slice(0,
|
|
169
|
+
return [name.slice(0, limit)];
|
|
140
170
|
if (allLines.length <= 3)
|
|
141
171
|
return allLines;
|
|
142
172
|
const lines = allLines.slice(0, 3);
|
|
143
173
|
const last = lines[2];
|
|
144
|
-
lines[2] = last.length + 1 <=
|
|
174
|
+
lines[2] = last.length + 1 <= limit ? last + '…' : last.slice(0, limit - 1) + '…';
|
|
145
175
|
return lines;
|
|
146
176
|
}
|
|
147
177
|
function belowLabelSvg(name, cls, cx, belowY, minX = 0) {
|
|
@@ -155,7 +185,7 @@ function belowLabelSvg(name, cls, cx, belowY, minX = 0) {
|
|
|
155
185
|
return `<text class="${cls}" x="${r(textX)}" y="${startY}">${escXml(lines[0])}</text>`;
|
|
156
186
|
}
|
|
157
187
|
const tspans = lines
|
|
158
|
-
.map((ln, i) => `<tspan x="${r(textX)}" dy="${i === 0 ? 0 :
|
|
188
|
+
.map((ln, i) => `<tspan x="${r(textX)}" dy="${i === 0 ? 0 : typo.labelLineH}">${escXml(ln)}</tspan>`)
|
|
159
189
|
.join('');
|
|
160
190
|
return `<text class="${cls}" x="${r(textX)}" y="${startY}">${tspans}</text>`;
|
|
161
191
|
}
|
|
@@ -378,15 +408,18 @@ export function renderProcessBody(layout, ox, oy) {
|
|
|
378
408
|
*/
|
|
379
409
|
export function renderProcessLayoutSvg(layout, options = {}) {
|
|
380
410
|
const { title = '', topInset = title ? 32 : 0 } = options;
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
(
|
|
390
|
-
|
|
391
|
-
|
|
411
|
+
const t = options.typography ?? DEFAULT_BPMN_TYPOGRAPHY;
|
|
412
|
+
return runWithTypography(t, () => {
|
|
413
|
+
const pb = layout.poolBounds;
|
|
414
|
+
const PAD = 16;
|
|
415
|
+
const svgW = r(pb.x + pb.width + PAD);
|
|
416
|
+
const svgH = r(pb.y + pb.height + PAD + topInset);
|
|
417
|
+
const embedCss = generateSvgEmbedCss('transitrix') + bpmnProcessCss(t);
|
|
418
|
+
const body = renderProcessBody(layout, 0, topInset);
|
|
419
|
+
return (`<svg xmlns="http://www.w3.org/2000/svg" width="${svgW}" height="${svgH}" viewBox="0 0 ${svgW} ${svgH}">\n` +
|
|
420
|
+
`<style>${embedCss}</style>\n` +
|
|
421
|
+
(title ? `${title}\n` : '') +
|
|
422
|
+
`${body}\n` +
|
|
423
|
+
`</svg>`);
|
|
424
|
+
});
|
|
392
425
|
}
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* §2). Filesystem-free — callable from unit tests without vscode.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { isObject, str, strArray,
|
|
15
|
+
import { isObject, str, strArray, stripEnvelope, reachesRootViaParent } from '../canon-resolver-utils.js';
|
|
16
16
|
|
|
17
17
|
export interface ActionViewConfig {
|
|
18
18
|
scope?: {
|
|
@@ -44,6 +44,10 @@ export interface ActionCanonSources {
|
|
|
44
44
|
relations?: unknown[];
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/** In-memory bag of ACTION ids dropped by `root_action` scoping. Consumed by
|
|
48
|
+
* `validateActivities` as `ACT-021`. Not part of the notation. */
|
|
49
|
+
export const ACT_021_OMITTED_KEY = '__act021_omitted';
|
|
50
|
+
|
|
47
51
|
/** ACTION elements by id. `notation: action` is canonical; `notation: activity`
|
|
48
52
|
* is the deprecated pre-2026-06-25 alias (elements/24-action.md §"Deprecated
|
|
49
53
|
* alias") — accepted the same way `collectByNotation` callers elsewhere do. */
|
|
@@ -123,32 +127,26 @@ export function resolveAction(
|
|
|
123
127
|
return relGoals.length > 0 ? relGoals : strArray(el['goals']);
|
|
124
128
|
}
|
|
125
129
|
|
|
126
|
-
// 1.
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
let candidateIds: Set<string>;
|
|
131
|
-
if (rootAction) {
|
|
132
|
-
candidateIds = allActions.has(rootAction) ? descendantsOf(rootAction, allActions) : new Set();
|
|
133
|
-
} else {
|
|
134
|
-
candidateIds = new Set(allActions.keys());
|
|
135
|
-
}
|
|
130
|
+
// 1. Other scope fields first — the set the view would otherwise select
|
|
131
|
+
// (07-action.md §5.1). `root_action` then omits (and warns) rather than
|
|
132
|
+
// silently intersecting first.
|
|
133
|
+
let otherwiseIds: Set<string> = new Set(allActions.keys());
|
|
136
134
|
|
|
137
|
-
//
|
|
135
|
+
// Narrow by goals — only actions linked to at least one listed GOAL
|
|
138
136
|
// (via an active action_goal relation, or inline goals[] when no relation
|
|
139
137
|
// exists for that action).
|
|
140
138
|
const goalsFilter = strArray(scope['goals']);
|
|
141
139
|
if (goalsFilter.length > 0) {
|
|
142
|
-
|
|
143
|
-
[...
|
|
140
|
+
otherwiseIds = new Set(
|
|
141
|
+
[...otherwiseIds].filter((id) => effectiveGoals(id, allActions.get(id)!).some((g) => goalsFilter.includes(g))),
|
|
144
142
|
);
|
|
145
143
|
}
|
|
146
144
|
|
|
147
|
-
//
|
|
145
|
+
// Narrow by type_filter — only actions of a listed type.
|
|
148
146
|
const typeFilter = strArray(scope['type_filter']);
|
|
149
147
|
if (typeFilter.length > 0) {
|
|
150
|
-
|
|
151
|
-
[...
|
|
148
|
+
otherwiseIds = new Set(
|
|
149
|
+
[...otherwiseIds].filter((id) => {
|
|
152
150
|
const el = allActions.get(id);
|
|
153
151
|
const t = str(el?.['type']) ?? str(el?.['activity_type']);
|
|
154
152
|
return t !== undefined && typeFilter.includes(t);
|
|
@@ -156,13 +154,13 @@ export function resolveAction(
|
|
|
156
154
|
);
|
|
157
155
|
}
|
|
158
156
|
|
|
159
|
-
//
|
|
157
|
+
// Narrow by valid_at — only actions in effect on that date. Actions with
|
|
160
158
|
// no valid_from are not lifecycle-tracked and are kept (permissive default,
|
|
161
159
|
// matching the resolver's general "missing = not excluded" stance).
|
|
162
160
|
const validAt = str(scope['valid_at']);
|
|
163
161
|
if (validAt) {
|
|
164
|
-
|
|
165
|
-
[...
|
|
162
|
+
otherwiseIds = new Set(
|
|
163
|
+
[...otherwiseIds].filter((id) => {
|
|
166
164
|
const el = allActions.get(id);
|
|
167
165
|
const from = str(el?.['valid_from']);
|
|
168
166
|
const to = str(el?.['valid_to']);
|
|
@@ -173,6 +171,27 @@ export function resolveAction(
|
|
|
173
171
|
);
|
|
174
172
|
}
|
|
175
173
|
|
|
174
|
+
const rootAction = str(scope['root_action']);
|
|
175
|
+
let candidateIds: Set<string>;
|
|
176
|
+
const omittedOutsideRoot: string[] = [];
|
|
177
|
+
if (rootAction) {
|
|
178
|
+
if (!allActions.has(rootAction)) {
|
|
179
|
+
candidateIds = new Set();
|
|
180
|
+
} else {
|
|
181
|
+
candidateIds = new Set();
|
|
182
|
+
for (const id of otherwiseIds) {
|
|
183
|
+
if (reachesRootViaParent(id, rootAction, allActions)) {
|
|
184
|
+
candidateIds.add(id);
|
|
185
|
+
} else {
|
|
186
|
+
omittedOutsideRoot.push(id);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
omittedOutsideRoot.sort();
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
candidateIds = otherwiseIds;
|
|
193
|
+
}
|
|
194
|
+
|
|
176
195
|
// Element fields map onto Activity fields unchanged, except the canonical
|
|
177
196
|
// `type` (elements/24-action.md §2) which becomes the internal `activity_type`
|
|
178
197
|
// (types.ts `Activity.activity_type`), and `goals` which is resolved via
|
|
@@ -204,5 +223,6 @@ export function resolveAction(
|
|
|
204
223
|
actions: selectedActions,
|
|
205
224
|
};
|
|
206
225
|
if (Object.keys(project).length > 0) out['project'] = project;
|
|
226
|
+
if (omittedOutsideRoot.length > 0) out[ACT_021_OMITTED_KEY] = { root: rootAction, ids: omittedOutsideRoot };
|
|
207
227
|
return out;
|
|
208
228
|
}
|
|
@@ -5,10 +5,62 @@ import type {
|
|
|
5
5
|
ActivityValidationError,
|
|
6
6
|
ActivityValidationWarning,
|
|
7
7
|
} from './types.js';
|
|
8
|
+
import { isObject, str, reachesRootViaParent } from '../canon-resolver-utils.js';
|
|
9
|
+
import { ACT_021_OMITTED_KEY } from './resolver.js';
|
|
8
10
|
|
|
9
11
|
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
10
12
|
const VALID_WEEKDAYS = new Set(['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']);
|
|
11
13
|
|
|
14
|
+
function act021Message(actionId: string, rootId: string): string {
|
|
15
|
+
return `ACTION "${actionId}" is not "${rootId}" and is not reachable from it via parent`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function collectAct021FromProjection(raw: Record<string, unknown>): ActivityValidationWarning[] {
|
|
19
|
+
const bag = raw[ACT_021_OMITTED_KEY];
|
|
20
|
+
delete raw[ACT_021_OMITTED_KEY];
|
|
21
|
+
if (!isObject(bag)) return [];
|
|
22
|
+
const root = str(bag['root']);
|
|
23
|
+
const ids = Array.isArray(bag['ids']) ? bag['ids'] : [];
|
|
24
|
+
if (!root) return [];
|
|
25
|
+
const out: ActivityValidationWarning[] = [];
|
|
26
|
+
for (const id of ids) {
|
|
27
|
+
if (typeof id !== 'string' || !id.trim()) continue;
|
|
28
|
+
out.push({
|
|
29
|
+
code: 'ACT-021',
|
|
30
|
+
message: act021Message(id, root),
|
|
31
|
+
path: 'view_config.scope.root_action',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function collectAct021FromAuthored(
|
|
38
|
+
raw: Record<string, unknown>,
|
|
39
|
+
actions: unknown[],
|
|
40
|
+
): ActivityValidationWarning[] {
|
|
41
|
+
const vc = isObject(raw['view_config']) ? raw['view_config'] : undefined;
|
|
42
|
+
const scope = vc && isObject(vc['scope']) ? vc['scope'] : undefined;
|
|
43
|
+
const root = scope ? str(scope['root_action']) : undefined;
|
|
44
|
+
if (!root) return [];
|
|
45
|
+
const byId = new Map<string, Record<string, unknown>>();
|
|
46
|
+
for (const el of actions) {
|
|
47
|
+
if (!isObject(el)) continue;
|
|
48
|
+
const id = str(el['id']);
|
|
49
|
+
if (id) byId.set(id, el);
|
|
50
|
+
}
|
|
51
|
+
if (!byId.has(root)) return [];
|
|
52
|
+
const out: ActivityValidationWarning[] = [];
|
|
53
|
+
for (const id of byId.keys()) {
|
|
54
|
+
if (reachesRootViaParent(id, root, byId)) continue;
|
|
55
|
+
out.push({
|
|
56
|
+
code: 'ACT-021',
|
|
57
|
+
message: act021Message(id, root),
|
|
58
|
+
path: 'view_config.scope.root_action',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
12
64
|
export function validateActivities(input: unknown): ActivityValidationResult {
|
|
13
65
|
const errors: ActivityValidationError[] = [];
|
|
14
66
|
const warnings: ActivityValidationWarning[] = [];
|
|
@@ -46,6 +98,12 @@ export function validateActivities(input: unknown): ActivityValidationResult {
|
|
|
46
98
|
// Normalise onto raw.activities for the rest of this function (avoids touching downstream code)
|
|
47
99
|
if (!Array.isArray(raw.activities)) raw.activities = raw.actions;
|
|
48
100
|
|
|
101
|
+
const fromProjection = collectAct021FromProjection(raw);
|
|
102
|
+
warnings.push(...fromProjection);
|
|
103
|
+
if (fromProjection.length === 0) {
|
|
104
|
+
warnings.push(...collectAct021FromAuthored(raw, rawArray));
|
|
105
|
+
}
|
|
106
|
+
|
|
49
107
|
// ACT-014 / ACT-015: project.calendar validation (run before per-activity loop;
|
|
50
108
|
// surfaces clearly even when activities also have issues).
|
|
51
109
|
if (raw.project && typeof raw.project === 'object') {
|
|
@@ -48,6 +48,25 @@ export function descendantsOf(
|
|
|
48
48
|
return result;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/** Walk `parent` from `id` toward its ancestors until `rootId` or the chain
|
|
52
|
+
* ends. Cycle-safe: a cycle is not a path to the root. */
|
|
53
|
+
export function reachesRootViaParent(
|
|
54
|
+
id: string,
|
|
55
|
+
rootId: string,
|
|
56
|
+
all: Map<string, Record<string, unknown>>,
|
|
57
|
+
): boolean {
|
|
58
|
+
const seen = new Set<string>();
|
|
59
|
+
let current: string | undefined = id;
|
|
60
|
+
while (current) {
|
|
61
|
+
if (current === rootId) return true;
|
|
62
|
+
if (seen.has(current)) return false;
|
|
63
|
+
seen.add(current);
|
|
64
|
+
const el = all.get(current);
|
|
65
|
+
current = el ? str(el['parent']) : undefined;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
51
70
|
/** Depth of `id` in its `parent` chain within `all` (root = 0, each step up
|
|
52
71
|
* adds 1). Cycle-safe (a cycle resolves the entering node to depth 0) and
|
|
53
72
|
* memoized across calls via the shared `memo` map. */
|
package/src/codex/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { CODEX_ARTEFACT_TYPES, EXTERNAL_CODEX_TYPES, INTERNAL_CODEX_TYPES } from './types.js';
|
|
1
|
+
export { CODEX_ARTEFACT_TYPES, EXTERNAL_CODEX_TYPES, INTERNAL_CODEX_TYPES, PRINCIPLE_CODEX_TYPES } from './types.js';
|
|
2
2
|
export type { CodexArtefactType } from './types.js';
|
|
3
3
|
export { validateCodex, isCodexDoc, folderJurisdictionFromPath } from './validate.js';
|
|
4
4
|
export type { CodexValidateOptions } from './validate.js';
|
package/src/codex/types.ts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
// Codex artefact — external laws/regulations and internal policies/standards.
|
|
1
|
+
// Codex artefact — external laws/regulations and internal policies/standards/principles.
|
|
2
2
|
// Schema: methodology notations/elements/14-codex.md §3–§4.
|
|
3
3
|
|
|
4
4
|
/** TYPE prefixes admitted in the codex zone (REQ-003 / 14-codex.md). */
|
|
5
|
-
export const CODEX_ARTEFACT_TYPES = ['LAW', 'REGULATION', 'POLICY', 'INTERNAL_STANDARD'] as const;
|
|
5
|
+
export const CODEX_ARTEFACT_TYPES = ['LAW', 'REGULATION', 'POLICY', 'INTERNAL_STANDARD', 'PRINCIPLE'] as const;
|
|
6
6
|
|
|
7
7
|
export type CodexArtefactType = (typeof CODEX_ARTEFACT_TYPES)[number];
|
|
8
8
|
|
|
9
9
|
/** External codex artefacts (codex/external/<jurisdiction>/). */
|
|
10
10
|
export const EXTERNAL_CODEX_TYPES: readonly CodexArtefactType[] = ['LAW', 'REGULATION'];
|
|
11
11
|
|
|
12
|
-
/** Internal codex artefacts (codex/internal/). */
|
|
12
|
+
/** Internal codex artefacts (codex/internal/) that carry a named issuing authority (§4). */
|
|
13
13
|
export const INTERNAL_CODEX_TYPES: readonly CodexArtefactType[] = ['POLICY', 'INTERNAL_STANDARD'];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Internal codex artefacts (codex/internal/) with the `PRINCIPLE` shape (§4.1) —
|
|
17
|
+
* `statement` + `rationale` required, `issuing_authority`/`effective_date`/`established_by`
|
|
18
|
+
* optional. Kept distinct from {@link INTERNAL_CODEX_TYPES} because the required-field set
|
|
19
|
+
* differs; both share the same file location.
|
|
20
|
+
*/
|
|
21
|
+
export const PRINCIPLE_CODEX_TYPES: readonly CodexArtefactType[] = ['PRINCIPLE'];
|
package/src/codex/validate.ts
CHANGED
|
@@ -3,9 +3,20 @@
|
|
|
3
3
|
// CODEX-001 — shape, zone, id grammar, admission envelope.
|
|
4
4
|
// CODEX-002 — `type` field matches the id TYPE prefix when present.
|
|
5
5
|
// CODEX-003 — external artefact frontmatter (jurisdiction, effective_date).
|
|
6
|
-
// CODEX-004 — internal artefact frontmatter (issuing_authority, effective_date
|
|
6
|
+
// CODEX-004 — internal artefact frontmatter (issuing_authority, effective_date for
|
|
7
|
+
// POLICY/INTERNAL_STANDARD; statement, rationale for PRINCIPLE — §4.1).
|
|
7
8
|
// CODEX-005 — jurisdiction must match the parent folder under codex/external/
|
|
8
9
|
// (when `folderJurisdiction` is supplied by the repo sweep).
|
|
10
|
+
// CODEX-006 — a PRINCIPLE artefact does not declare `established_by` (§4.1). The spec
|
|
11
|
+
// marks this "info" severity; this module has no info tier, so it is
|
|
12
|
+
// surfaced as a warning — the nearest existing severity that does not fail
|
|
13
|
+
// `valid`.
|
|
14
|
+
//
|
|
15
|
+
// Note: this file's CODEX-00x numbering predates the spec's own v0.2 renumbering
|
|
16
|
+
// (14-codex.md §6, §8.2) and does not track it code-for-code — e.g. the spec's current
|
|
17
|
+
// CODEX-001 is a folder/jurisdiction check this file assigns to CODEX-005. Realigning the
|
|
18
|
+
// full scheme is out of scope here; this change only adds the purely-additive PRINCIPLE
|
|
19
|
+
// TYPE (§4.1) under the existing local numbering.
|
|
9
20
|
|
|
10
21
|
import type { ValidationError, ValidationWarning, ValidationResult } from '../validation-types.js';
|
|
11
22
|
import { isCanonicalId, typeOfId } from '../typed-id.js';
|
|
@@ -13,6 +24,7 @@ import {
|
|
|
13
24
|
CODEX_ARTEFACT_TYPES,
|
|
14
25
|
EXTERNAL_CODEX_TYPES,
|
|
15
26
|
INTERNAL_CODEX_TYPES,
|
|
27
|
+
PRINCIPLE_CODEX_TYPES,
|
|
16
28
|
type CodexArtefactType,
|
|
17
29
|
} from './types.js';
|
|
18
30
|
|
|
@@ -48,7 +60,7 @@ export function validateCodex(input: unknown, options: CodexValidateOptions = {}
|
|
|
48
60
|
if (!isCanonicalId(c.id) || !isCodexType(idType)) {
|
|
49
61
|
errors.push({
|
|
50
62
|
code: 'CODEX-001',
|
|
51
|
-
message: `id "${String(c.id)}" must be a typed codex id (LAW, REGULATION, POLICY, or
|
|
63
|
+
message: `id "${String(c.id)}" must be a typed codex id (LAW, REGULATION, POLICY, INTERNAL_STANDARD, or PRINCIPLE).`,
|
|
52
64
|
path: 'id',
|
|
53
65
|
});
|
|
54
66
|
}
|
|
@@ -106,6 +118,22 @@ export function validateCodex(input: unknown, options: CodexValidateOptions = {}
|
|
|
106
118
|
}
|
|
107
119
|
}
|
|
108
120
|
|
|
121
|
+
if (artefactType && (PRINCIPLE_CODEX_TYPES as readonly string[]).includes(artefactType)) {
|
|
122
|
+
if (typeof c.statement !== 'string' || c.statement.trim() === '') {
|
|
123
|
+
errors.push({ code: 'CODEX-004', message: 'statement is required for a PRINCIPLE artefact.', path: 'statement' });
|
|
124
|
+
}
|
|
125
|
+
if (typeof c.rationale !== 'string' || c.rationale.trim() === '') {
|
|
126
|
+
errors.push({ code: 'CODEX-004', message: 'rationale is required for a PRINCIPLE artefact.', path: 'rationale' });
|
|
127
|
+
}
|
|
128
|
+
if (typeof c.established_by !== 'string' || c.established_by.trim() === '') {
|
|
129
|
+
warnings.push({
|
|
130
|
+
code: 'CODEX-006',
|
|
131
|
+
message: 'established_by is not declared on this PRINCIPLE artefact — admissible, but a review finding for whoever admits it.',
|
|
132
|
+
path: 'established_by',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
109
137
|
return { valid: errors.length === 0, errors, warnings };
|
|
110
138
|
}
|
|
111
139
|
|