@transitrix/diagrams 1.8.21 → 1.9.18
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/activity-card/types.js +1 -1
- package/dist/agreement.d.ts +15 -0
- package/dist/agreement.js +61 -0
- package/dist/applications/index.d.ts +2 -0
- package/dist/applications/index.js +1 -0
- package/dist/applications/resolve-maturity.d.ts +16 -0
- package/dist/applications/resolve-maturity.js +57 -0
- package/dist/applications/validate.js +20 -0
- package/dist/capability-map/CapabilityCardNode.d.ts +1 -1
- package/dist/capability-map/CapabilityRootNode.d.ts +1 -1
- package/dist/capability-map/index.d.ts +2 -0
- package/dist/capability-map/index.js +1 -0
- package/dist/capability-map/resolve-maturity.d.ts +20 -0
- package/dist/capability-map/resolve-maturity.js +72 -0
- package/dist/capability-map/validate.js +1 -2
- package/dist/compliance/classify.d.ts +10 -1
- package/dist/compliance/classify.js +44 -2
- package/dist/compliance/confidence.js +1 -1
- package/dist/compliance/gap-report.d.ts +31 -1
- package/dist/compliance/gap-report.js +55 -3
- package/dist/compliance/html.js +14 -6
- package/dist/compliance/impact.js +1 -1
- package/dist/compliance/index.d.ts +3 -1
- package/dist/compliance/index.js +2 -0
- package/dist/compliance/markdown.js +26 -4
- package/dist/compliance/reverse-index.d.ts +6 -1
- package/dist/compliance/reverse-index.js +27 -3
- package/dist/compliance/trace.js +5 -1
- package/dist/compliance/types.d.ts +70 -0
- package/dist/compliance/types.js +1 -2
- package/dist/compliance/views.js +1 -1
- package/dist/compliance-matrix/types.d.ts +1 -1
- package/dist/compliance-matrix/types.js +1 -1
- package/dist/confidence/score.js +1 -1
- package/dist/constraint/validate.js +3 -0
- package/dist/edge-path.js +1 -1
- package/dist/factor/validate.js +2 -2
- package/dist/fgca/chain-table.js +1 -1
- package/dist/fgca/nodeTypes.d.ts +4 -4
- package/dist/fgca/nodes/FGCAActivityNode.d.ts +1 -1
- package/dist/fgca/nodes/FGCAChangeNode.d.ts +1 -1
- package/dist/fgca/nodes/FGCAFactorNode.d.ts +1 -1
- package/dist/fgca/nodes/FGCAGoalNode.d.ts +1 -1
- package/dist/fgca/parse-canonical.d.ts +2 -2
- package/dist/fgca/parse-canonical.js +2 -2
- package/dist/fgca/preview-layout.d.ts +1 -1
- package/dist/goals/GoalNode.d.ts +1 -1
- package/dist/goals/layout.d.ts +1 -1
- package/dist/goals/layout.js +1 -1
- package/dist/goals/parse-canonical.d.ts +1 -1
- package/dist/goals/parse-canonical.js +1 -1
- package/dist/goals/types.d.ts +1 -1
- package/dist/goals/validate.js +2 -2
- package/dist/link-suspicion.d.ts +43 -0
- package/dist/link-suspicion.js +156 -0
- package/dist/metric/index.d.ts +4 -0
- package/dist/metric/index.js +2 -0
- package/dist/metric/types.d.ts +24 -0
- package/dist/metric/types.js +7 -0
- package/dist/metric/validate.d.ts +7 -0
- package/dist/metric/validate.js +90 -0
- package/dist/need/index.d.ts +3 -0
- package/dist/need/index.js +1 -0
- package/dist/need/types.d.ts +21 -0
- package/dist/need/types.js +3 -0
- package/dist/need/validate.d.ts +7 -0
- package/dist/need/validate.js +60 -0
- package/dist/repo-validate/check-versioned-attributes.d.ts +4 -0
- package/dist/repo-validate/check-versioned-attributes.js +105 -0
- package/dist/repo-validate/validate-repo.js +3 -0
- package/dist/requirement/index.d.ts +2 -2
- package/dist/requirement/index.js +1 -1
- package/dist/requirement/types.d.ts +18 -0
- package/dist/requirement/types.js +2 -0
- package/dist/requirement/validate.js +22 -1
- package/dist/risk/index.d.ts +4 -0
- package/dist/risk/index.js +2 -0
- package/dist/risk/types.d.ts +25 -0
- package/dist/risk/types.js +3 -0
- package/dist/risk/validate.d.ts +7 -0
- package/dist/risk/validate.js +108 -0
- package/dist/schema-version.d.ts +9 -6
- package/dist/schema-version.js +9 -6
- package/dist/theme/themes.js +3 -2
- package/dist/validation/index.d.ts +4 -0
- package/dist/validation/index.js +2 -0
- package/dist/validation/types.d.ts +31 -0
- package/dist/validation/types.js +21 -0
- package/dist/validation/validate.d.ts +7 -0
- package/dist/validation/validate.js +106 -0
- package/dist/verification/index.d.ts +4 -0
- package/dist/verification/index.js +2 -0
- package/dist/verification/types.d.ts +31 -0
- package/dist/verification/types.js +12 -0
- package/dist/verification/validate.d.ts +7 -0
- package/dist/verification/validate.js +104 -0
- package/dist/versioned-attribute/index.d.ts +4 -0
- package/dist/versioned-attribute/index.js +2 -0
- package/dist/versioned-attribute/resolve.d.ts +15 -0
- package/dist/versioned-attribute/resolve.js +37 -0
- package/dist/versioned-attribute/types.d.ts +12 -0
- package/dist/versioned-attribute/types.js +6 -0
- package/dist/versioned-attribute/validate.d.ts +17 -0
- package/dist/versioned-attribute/validate.js +87 -0
- package/dist/webview/render-applications.d.ts +15 -1
- package/dist/webview/render-applications.js +7 -2
- package/dist/webview/render-blocks.js +6 -4
- package/dist/webview/render-capability-map.d.ts +16 -1
- package/dist/webview/render-capability-map.js +4 -1
- package/dist/webview/render-process.d.ts +1 -1
- package/dist/webview/render-process.js +5 -5
- package/package.json +9 -6
- package/src/activity-card/types.ts +1 -1
- package/src/agreement.ts +71 -0
- package/src/applications/index.ts +3 -0
- package/src/applications/resolve-maturity.ts +66 -0
- package/src/applications/validate.ts +23 -0
- package/src/capability-map/index.ts +7 -0
- package/src/capability-map/resolve-maturity.ts +84 -0
- package/src/capability-map/validate.ts +1 -2
- package/src/compliance/classify.ts +54 -3
- package/src/compliance/confidence.ts +1 -1
- package/src/compliance/gap-report.ts +91 -4
- package/src/compliance/html.ts +18 -6
- package/src/compliance/impact.ts +1 -1
- package/src/compliance/index.ts +6 -0
- package/src/compliance/markdown.ts +18 -4
- package/src/compliance/reverse-index.ts +35 -4
- package/src/compliance/trace.ts +7 -1
- package/src/compliance/types.ts +75 -2
- package/src/compliance/views.ts +1 -1
- package/src/compliance-matrix/types.ts +2 -2
- package/src/confidence/score.ts +1 -1
- package/src/constraint/validate.ts +4 -0
- package/src/edge-path.ts +1 -1
- package/src/factor/validate.ts +2 -2
- package/src/fgca/chain-table.ts +1 -1
- package/src/fgca/parse-canonical.ts +2 -2
- package/src/fgca/preview-layout.ts +1 -1
- package/src/goals/layout.ts +1 -1
- package/src/goals/parse-canonical.ts +1 -1
- package/src/goals/types.ts +1 -1
- package/src/goals/validate.ts +2 -2
- package/src/link-suspicion.ts +181 -0
- package/src/metric/index.ts +4 -0
- package/src/metric/types.ts +38 -0
- package/src/metric/validate.ts +102 -0
- package/src/need/index.ts +3 -0
- package/src/need/types.ts +31 -0
- package/src/need/validate.ts +71 -0
- package/src/repo-validate/check-versioned-attributes.ts +112 -0
- package/src/repo-validate/validate-repo.ts +3 -0
- package/src/requirement/index.ts +2 -2
- package/src/requirement/types.ts +25 -0
- package/src/requirement/validate.ts +25 -1
- package/src/risk/index.ts +4 -0
- package/src/risk/types.ts +35 -0
- package/src/risk/validate.ts +121 -0
- package/src/schema-version.ts +9 -6
- package/src/theme/themes.ts +3 -2
- package/src/validation/index.ts +4 -0
- package/src/validation/types.ts +57 -0
- package/src/validation/validate.ts +113 -0
- package/src/verification/index.ts +4 -0
- package/src/verification/types.ts +48 -0
- package/src/verification/validate.ts +111 -0
- package/src/versioned-attribute/index.ts +4 -0
- package/src/versioned-attribute/resolve.ts +45 -0
- package/src/versioned-attribute/types.ts +19 -0
- package/src/versioned-attribute/validate.ts +109 -0
- package/src/webview/render-applications.ts +25 -2
- package/src/webview/render-blocks.ts +8 -4
- package/src/webview/render-capability-map.ts +23 -1
- package/src/webview/render-process.ts +5 -5
- package/src/webview/styles.css +6 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** One version entry in an attribute's history. `value: null` is a gap
|
|
2
|
+
* marker — the attribute is unset from `valid_from` until the next entry. */
|
|
3
|
+
export interface AttributeVersionEntry {
|
|
4
|
+
valid_from: string;
|
|
5
|
+
value: unknown;
|
|
6
|
+
}
|
|
7
|
+
/** A parsed sidecar document, keyed by attribute name. */
|
|
8
|
+
export interface VersionedAttributeSidecar {
|
|
9
|
+
/** Canonical id of the primitive this sidecar versions. */
|
|
10
|
+
target: string;
|
|
11
|
+
attribute_versions: Record<string, AttributeVersionEntry[]>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Versioned-attribute sidecar (CONTRACT.md §9) — shared shape across every
|
|
2
|
+
// notation that declares a `time_varying` field. A sidecar is a co-located
|
|
3
|
+
// `<primitive_id>.history.yaml` document: no `id`, no admission record, no
|
|
4
|
+
// lifecycle of its own — its temporal window is governed by the target
|
|
5
|
+
// primitive's own `valid_from`/`valid_to` (CONTRACT.md §9.1).
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AttributeVersionEntry, VersionedAttributeSidecar } from './types.js';
|
|
2
|
+
export interface AttributeArrayFinding {
|
|
3
|
+
code: 'VERSIONED-002' | 'VERSIONED-003' | 'VERSIONED-005';
|
|
4
|
+
severity: 'error' | 'warning';
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
/** Parse a raw parsed-YAML mapping into a typed sidecar, or `null` if it
|
|
8
|
+
* isn't shaped like one (missing `target` string or `attribute_versions`
|
|
9
|
+
* map) — not every doc under `canon/elements/**` is a sidecar. */
|
|
10
|
+
export declare function parseSidecar(data: Record<string, unknown> | null | undefined): VersionedAttributeSidecar | null;
|
|
11
|
+
/** VERSIONED-002/003/005 for one attribute's version array.
|
|
12
|
+
* `targetValidFrom`/`targetValidTo` bound VERSIONED-005 — pass `undefined`
|
|
13
|
+
* for either to skip that half of the check (e.g. the target element could
|
|
14
|
+
* not be found, which is already reported separately as VERSIONED-001). */
|
|
15
|
+
export declare function validateAttributeArray(attributeName: string, entries: AttributeVersionEntry[], targetValidFrom?: string, targetValidTo?: string | null): AttributeArrayFinding[];
|
|
16
|
+
/** Run VERSIONED-002/003/005 over every attribute in a parsed sidecar. */
|
|
17
|
+
export declare function validateSidecar(sidecar: VersionedAttributeSidecar, targetValidFrom?: string, targetValidTo?: string | null): AttributeArrayFinding[];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Versioned-attribute sidecar validation — CONTRACT.md §9.3, `VERSIONED-00x`.
|
|
2
|
+
//
|
|
3
|
+
// Split by what each check needs to know:
|
|
4
|
+
// - `parseSidecar` + `validateAttributeArray` here cover VERSIONED-002/003/005,
|
|
5
|
+
// which only need the sidecar document itself (plus, for 005, the target
|
|
6
|
+
// primitive's own lifecycle window).
|
|
7
|
+
// - `VERSIONED-001` (target resolves to an admitted primitive) and
|
|
8
|
+
// `VERSIONED-004` (a time_varying field present inline on the primitive)
|
|
9
|
+
// both need cross-document knowledge — which ids exist, which notation a
|
|
10
|
+
// primitive is — that a single sidecar document doesn't carry. Those are
|
|
11
|
+
// repo-scope concerns; see `repo-validate/check-versioned-attributes.ts`.
|
|
12
|
+
function isAttributeVersionEntry(v) {
|
|
13
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v) && typeof v['valid_from'] === 'string';
|
|
14
|
+
}
|
|
15
|
+
/** Parse a raw parsed-YAML mapping into a typed sidecar, or `null` if it
|
|
16
|
+
* isn't shaped like one (missing `target` string or `attribute_versions`
|
|
17
|
+
* map) — not every doc under `canon/elements/**` is a sidecar. */
|
|
18
|
+
export function parseSidecar(data) {
|
|
19
|
+
if (!data)
|
|
20
|
+
return null;
|
|
21
|
+
const target = data['target'];
|
|
22
|
+
const attributeVersions = data['attribute_versions'];
|
|
23
|
+
if (typeof target !== 'string' || target.trim() === '')
|
|
24
|
+
return null;
|
|
25
|
+
if (typeof attributeVersions !== 'object' || attributeVersions === null || Array.isArray(attributeVersions))
|
|
26
|
+
return null;
|
|
27
|
+
const out = {};
|
|
28
|
+
for (const [name, value] of Object.entries(attributeVersions)) {
|
|
29
|
+
if (!Array.isArray(value))
|
|
30
|
+
continue;
|
|
31
|
+
out[name] = value.filter(isAttributeVersionEntry);
|
|
32
|
+
}
|
|
33
|
+
return { target, attribute_versions: out };
|
|
34
|
+
}
|
|
35
|
+
/** VERSIONED-002/003/005 for one attribute's version array.
|
|
36
|
+
* `targetValidFrom`/`targetValidTo` bound VERSIONED-005 — pass `undefined`
|
|
37
|
+
* for either to skip that half of the check (e.g. the target element could
|
|
38
|
+
* not be found, which is already reported separately as VERSIONED-001). */
|
|
39
|
+
export function validateAttributeArray(attributeName, entries, targetValidFrom, targetValidTo) {
|
|
40
|
+
const findings = [];
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
let sorted = true;
|
|
43
|
+
let prev;
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
if (seen.has(entry.valid_from)) {
|
|
46
|
+
findings.push({
|
|
47
|
+
code: 'VERSIONED-002',
|
|
48
|
+
severity: 'error',
|
|
49
|
+
message: `attribute '${attributeName}': duplicate valid_from '${entry.valid_from}'.`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
seen.add(entry.valid_from);
|
|
53
|
+
if (prev !== undefined && entry.valid_from < prev)
|
|
54
|
+
sorted = false;
|
|
55
|
+
prev = entry.valid_from;
|
|
56
|
+
if (targetValidFrom !== undefined && entry.valid_from < targetValidFrom) {
|
|
57
|
+
findings.push({
|
|
58
|
+
code: 'VERSIONED-005',
|
|
59
|
+
severity: 'error',
|
|
60
|
+
message: `attribute '${attributeName}': entry valid_from '${entry.valid_from}' is before the target's valid_from '${targetValidFrom}'.`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (targetValidTo !== undefined && targetValidTo !== null && entry.valid_from > targetValidTo) {
|
|
64
|
+
findings.push({
|
|
65
|
+
code: 'VERSIONED-005',
|
|
66
|
+
severity: 'error',
|
|
67
|
+
message: `attribute '${attributeName}': entry valid_from '${entry.valid_from}' is after the target's valid_to '${targetValidTo}'.`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (!sorted) {
|
|
72
|
+
findings.push({
|
|
73
|
+
code: 'VERSIONED-003',
|
|
74
|
+
severity: 'warning',
|
|
75
|
+
message: `attribute '${attributeName}': entries are not sorted by valid_from ascending.`,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return findings;
|
|
79
|
+
}
|
|
80
|
+
/** Run VERSIONED-002/003/005 over every attribute in a parsed sidecar. */
|
|
81
|
+
export function validateSidecar(sidecar, targetValidFrom, targetValidTo) {
|
|
82
|
+
const findings = [];
|
|
83
|
+
for (const [name, entries] of Object.entries(sidecar.attribute_versions)) {
|
|
84
|
+
findings.push(...validateAttributeArray(name, entries, targetValidFrom, targetValidTo));
|
|
85
|
+
}
|
|
86
|
+
return findings;
|
|
87
|
+
}
|
|
@@ -8,4 +8,18 @@
|
|
|
8
8
|
* `#transitrix-root` exactly the way it drops the goals SVG (Step 3 contract).
|
|
9
9
|
*/
|
|
10
10
|
import type { ApplicationsCatalogueHeader } from '../applications/types.js';
|
|
11
|
-
|
|
11
|
+
import type { ResolvedApplicationAttributes } from '../applications/resolve-maturity.js';
|
|
12
|
+
/**
|
|
13
|
+
* Resolved values for owner_role/vendor/maturity — sidecar-bound
|
|
14
|
+
* (CONTRACT.md §9.4) and never inline on an admitted catalogue entry
|
|
15
|
+
* (VERSIONED-004). The host resolves these upstream (see
|
|
16
|
+
* `applications/resolve-maturity.ts`) and passes the result in; this
|
|
17
|
+
* function stays a pure renderer of whatever it's given — no I/O, no
|
|
18
|
+
* write-back. `asOf` is the date the resolution ran at, shown so the view
|
|
19
|
+
* never states an undated value.
|
|
20
|
+
*/
|
|
21
|
+
export interface ApplicationsMaturityResolution {
|
|
22
|
+
asOf: string;
|
|
23
|
+
byAppId: Map<string, ResolvedApplicationAttributes>;
|
|
24
|
+
}
|
|
25
|
+
export declare function renderApplicationsHtml(catalogue: ApplicationsCatalogueHeader, resolution?: ApplicationsMaturityResolution): string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withResolvedAttributes } from '../applications/resolve-maturity.js';
|
|
1
2
|
import { escHtml } from './render-util.js';
|
|
2
3
|
const TYPE_LABEL = {
|
|
3
4
|
application: 'Application',
|
|
@@ -68,8 +69,11 @@ function renderRow(a) {
|
|
|
68
69
|
<td class="col-owner">${a.owner_role ? escHtml(a.owner_role) : '<span class="cell-empty">—</span>'}</td>
|
|
69
70
|
</tr>`;
|
|
70
71
|
}
|
|
71
|
-
export function renderApplicationsHtml(catalogue) {
|
|
72
|
-
const
|
|
72
|
+
export function renderApplicationsHtml(catalogue, resolution) {
|
|
73
|
+
const applications = resolution
|
|
74
|
+
? catalogue.applications.map((a) => withResolvedAttributes(a, resolution.byAppId))
|
|
75
|
+
: catalogue.applications;
|
|
76
|
+
const rows = applications.map(renderRow).join('\n');
|
|
73
77
|
const emptyRow = catalogue.applications.length === 0
|
|
74
78
|
? `<tr><td colspan="7" class="empty-catalogue">No applications defined.</td></tr>`
|
|
75
79
|
: '';
|
|
@@ -81,6 +85,7 @@ export function renderApplicationsHtml(catalogue) {
|
|
|
81
85
|
${catalogue.version ? `<span class="catalogue-version">v${escHtml(catalogue.version)}</span>` : ''}
|
|
82
86
|
</div>
|
|
83
87
|
${catalogue.description ? `<div class="catalogue-subtitle">${escHtml(catalogue.description)}</div>` : ''}
|
|
88
|
+
${resolution ? `<div class="catalogue-resolved-note">Owner Role, Vendor, Maturity resolved as of ${escHtml(resolution.asOf)}</div>` : ''}
|
|
84
89
|
</header>`;
|
|
85
90
|
return `<section class="tx-catalogue tx-applications">
|
|
86
91
|
${header}
|
|
@@ -115,8 +115,6 @@ export function renderBlocksSvg(doc, options = {}) {
|
|
|
115
115
|
*/
|
|
116
116
|
const GRID_CELL_PAD_X = 8;
|
|
117
117
|
const GRID_LINE_HEIGHT = 15;
|
|
118
|
-
const GRID_EMBED_CSS = `
|
|
119
|
-
.blocks-grid-border { fill: none; stroke: var(--ts-border, #cbd5e1); }`;
|
|
120
118
|
function centeredGridCellTextSvg(lines, cls, cx, cellTop, cellHeight) {
|
|
121
119
|
const ls = lines.length > 0 ? lines : [''];
|
|
122
120
|
const firstY = cellTop + cellHeight / 2 - ((ls.length - 1) / 2) * GRID_LINE_HEIGHT;
|
|
@@ -174,7 +172,11 @@ export function renderGridBody(layout, ox, oy) {
|
|
|
174
172
|
const colLineX = ox + col.x + col.width;
|
|
175
173
|
parts.push(`<line class="diagram-edge" x1="${colLineX}" y1="${gridY1}" x2="${colLineX}" y2="${gridY2}"/>`);
|
|
176
174
|
}
|
|
177
|
-
|
|
175
|
+
// Explicit fill="none" alongside the class: this rect covers the full grid
|
|
176
|
+
// bounds and is the last (topmost) element emitted, so if `.blocks-grid-border`
|
|
177
|
+
// is ever missing from a host's CSS (as it was in the VS Code webview path —
|
|
178
|
+
// see the grid-preview regression), it must not fall back to SVG's default opaque black fill.
|
|
179
|
+
parts.push(`<rect class="blocks-grid-border" fill="none" x="${ox}" y="${oy}" width="${tw}" height="${th}"/>`);
|
|
178
180
|
return parts.join('\n');
|
|
179
181
|
}
|
|
180
182
|
/** The single grid (matrix subset) SVG emitter shared by every host. */
|
|
@@ -185,7 +187,7 @@ export function renderGridLayoutSvg(layout, options = {}) {
|
|
|
185
187
|
const ox = PAD;
|
|
186
188
|
const oy = PAD + topInset;
|
|
187
189
|
const body = renderGridBody(layout, ox, oy);
|
|
188
|
-
const styleLine = embedCssTheme ? `\n<style>${generateSvgEmbedCss(embedCssTheme)}
|
|
190
|
+
const styleLine = embedCssTheme ? `\n<style>${generateSvgEmbedCss(embedCssTheme)}</style>` : '';
|
|
189
191
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">${styleLine}
|
|
190
192
|
${title}
|
|
191
193
|
${body}
|
|
@@ -4,4 +4,19 @@
|
|
|
4
4
|
* HTML fragment for the JCEF host (Step 4 of ADR 0001).
|
|
5
5
|
*/
|
|
6
6
|
import type { CapabilityMapHeader } from '../capability-map/types.js';
|
|
7
|
-
|
|
7
|
+
import type { ResolvedCapabilityAttributes } from '../capability-map/resolve-maturity.js';
|
|
8
|
+
/**
|
|
9
|
+
* Resolved values for current_maturity/target_maturity/owner_role/
|
|
10
|
+
* target_date — sidecar-bound (CONTRACT.md §9.4) on the standalone
|
|
11
|
+
* CAPABILITY-* primitive a nested entry references (the 2026-08-05
|
|
12
|
+
* packages decision). The host resolves
|
|
13
|
+
* these upstream (see `capability-map/resolve-maturity.ts`) and passes the
|
|
14
|
+
* result in; this function stays a pure renderer of whatever it's given —
|
|
15
|
+
* no I/O, no write-back. `asOf` is the date the resolution ran at, shown so
|
|
16
|
+
* the view never states an undated value.
|
|
17
|
+
*/
|
|
18
|
+
export interface CapabilityMapMaturityResolution {
|
|
19
|
+
asOf: string;
|
|
20
|
+
byId: Map<string, ResolvedCapabilityAttributes>;
|
|
21
|
+
}
|
|
22
|
+
export declare function renderCapabilityMapHtml(rawMap: CapabilityMapHeader, resolution?: CapabilityMapMaturityResolution): string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withResolvedCapabilityMap } from '../capability-map/resolve-maturity.js';
|
|
1
2
|
import { escHtml } from './render-util.js';
|
|
2
3
|
const MATURITY_LABEL = {
|
|
3
4
|
1: 'Initial',
|
|
@@ -52,7 +53,8 @@ function renderCapabilityCard(node, depth) {
|
|
|
52
53
|
${childBlock}
|
|
53
54
|
</div>`;
|
|
54
55
|
}
|
|
55
|
-
export function renderCapabilityMapHtml(
|
|
56
|
+
export function renderCapabilityMapHtml(rawMap, resolution) {
|
|
57
|
+
const map = resolution ? withResolvedCapabilityMap(rawMap, resolution.byId) : rawMap;
|
|
56
58
|
const header = `<header class="catalogue-header">
|
|
57
59
|
<div class="catalogue-title">${escHtml(map.name)}</div>
|
|
58
60
|
<div class="catalogue-meta">
|
|
@@ -60,6 +62,7 @@ export function renderCapabilityMapHtml(map) {
|
|
|
60
62
|
<span class="catalogue-updated">Assessed ${escHtml(map.assessment_date)}</span>
|
|
61
63
|
</div>
|
|
62
64
|
${map.description ? `<div class="catalogue-subtitle">${escHtml(map.description)}</div>` : ''}
|
|
65
|
+
${resolution ? `<div class="catalogue-resolved-note">Current Maturity, Target Maturity, Owner Role, Target Date resolved as of ${escHtml(resolution.asOf)}</div>` : ''}
|
|
63
66
|
</header>`;
|
|
64
67
|
if (map.capabilities.length === 0) {
|
|
65
68
|
return `<section class="tx-catalogue tx-capability-map">
|
|
@@ -70,7 +70,7 @@ export interface RenderProcessOptions {
|
|
|
70
70
|
/** Extra vertical space (px) reserved above the pool for a title block. */
|
|
71
71
|
topInset?: number;
|
|
72
72
|
}
|
|
73
|
-
export declare const BPMN_PROCESS_CSS = "\n.bpmn-pool { fill: var(--ts-bg-surface,#f8fafc); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }\n.bpmn-pool-name { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }\n.bpmn-lane { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }\n.bpmn-lane-header { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }\n.bpmn-pool-label { fill: var(--ts-text-primary,#0d2b35); font-size: 11px; font-weight: 700; }\n.bpmn-lane-label { fill: var(--ts-text-primary,#0d2b35); font-size: 11px; font-weight: 600; }\n.bpmn-task { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }\n.bpmn-task-name { fill: var(--ts-text-primary,#0d2b35); font-size: 11px; text-anchor: middle; }\n.bpmn-event { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); }\n.bpmn-event-start { stroke-width: 1.5; }\n.bpmn-event-end { stroke-width: 4; }\n.bpmn-event-label { fill: var(--ts-text-secondary,#516970); font-size: 10px; text-anchor: middle; }\n.bpmn-gateway { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }\n.bpmn-gateway-marker { stroke: var(--ts-node-stroke,#004d67); stroke-width: 2.5; stroke-linecap: round; fill: none; }\n.bpmn-gateway-label { fill: var(--ts-text-secondary,#516970); font-size: 10px; text-anchor: middle; }\n.bpmn-seq-flow { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; }\n.bpmn-default-mark { stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; }\n.bpmn-assoc { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1; stroke-dasharray: 4 2; }\n.bpmn-data-obj { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1; }\n.bpmn-data-obj-label { fill: var(--ts-text-secondary,#516970); font-size: 10px; text-anchor: middle; }\n.bpmn-event-intermediate { fill: none; stroke: var(--ts-node-stroke,#004d67); stroke-width: 1; }\n.bpmn-event-icon { fill: none; stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }\n";
|
|
73
|
+
export declare const BPMN_PROCESS_CSS = "\n.bpmn-pool { fill: var(--ts-bg-surface,#f8fafc); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }\n.bpmn-pool-name { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }\n.bpmn-lane { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }\n.bpmn-lane-header { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }\n.bpmn-pool-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 11px; font-weight: 700; }\n.bpmn-lane-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 11px; font-weight: 600; }\n.bpmn-task { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }\n.bpmn-task-name { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 11px; text-anchor: middle; }\n.bpmn-event { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); }\n.bpmn-event-start { stroke-width: 1.5; }\n.bpmn-event-end { stroke-width: 4; }\n.bpmn-event-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 10px; text-anchor: middle; }\n.bpmn-gateway { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }\n.bpmn-gateway-marker { stroke: var(--ts-node-stroke,#004d67); stroke-width: 2.5; stroke-linecap: round; fill: none; }\n.bpmn-gateway-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 10px; text-anchor: middle; }\n.bpmn-seq-flow { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; }\n.bpmn-default-mark { stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; }\n.bpmn-assoc { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1; stroke-dasharray: 4 2; }\n.bpmn-data-obj { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1; }\n.bpmn-data-obj-label { fill: var(--ts-text-secondary,#516970); font-size: 10px; text-anchor: middle; }\n.bpmn-event-intermediate { fill: none; stroke: var(--ts-node-stroke,#004d67); stroke-width: 1; }\n.bpmn-event-icon { fill: none; stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }\n";
|
|
74
74
|
/**
|
|
75
75
|
* Canonical body emitter, shared by every host (VS Code, JCEF, static export).
|
|
76
76
|
*
|
|
@@ -39,17 +39,17 @@ export const BPMN_PROCESS_CSS = `
|
|
|
39
39
|
.bpmn-pool-name { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }
|
|
40
40
|
.bpmn-lane { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }
|
|
41
41
|
.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-size: 11px; font-weight: 700; }
|
|
43
|
-
.bpmn-lane-label { fill: var(--ts-text-primary,#0d2b35); font-size: 11px; font-weight: 600; }
|
|
42
|
+
.bpmn-pool-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 11px; font-weight: 700; }
|
|
43
|
+
.bpmn-lane-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 11px; font-weight: 600; }
|
|
44
44
|
.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-size: 11px; text-anchor: middle; }
|
|
45
|
+
.bpmn-task-name { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 11px; text-anchor: middle; }
|
|
46
46
|
.bpmn-event { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); }
|
|
47
47
|
.bpmn-event-start { stroke-width: 1.5; }
|
|
48
48
|
.bpmn-event-end { stroke-width: 4; }
|
|
49
|
-
.bpmn-event-label { fill: var(--ts-text-secondary,#516970); font-size: 10px; text-anchor: middle; }
|
|
49
|
+
.bpmn-event-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 10px; text-anchor: middle; }
|
|
50
50
|
.bpmn-gateway { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }
|
|
51
51
|
.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-size: 10px; text-anchor: middle; }
|
|
52
|
+
.bpmn-gateway-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 10px; text-anchor: middle; }
|
|
53
53
|
.bpmn-seq-flow { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; }
|
|
54
54
|
.bpmn-default-mark { stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; }
|
|
55
55
|
.bpmn-assoc { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1; stroke-dasharray: 4 2; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@transitrix/diagrams",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared rendering, validation, and pure mutations for Transitrix custom diagram formats.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
},
|
|
16
16
|
"main": "dist/index.js",
|
|
17
17
|
"types": "dist/index.d.ts",
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=20"
|
|
20
|
+
},
|
|
18
21
|
"exports": {
|
|
19
22
|
".": {
|
|
20
23
|
"types": "./dist/index.d.ts",
|
|
@@ -51,13 +54,13 @@
|
|
|
51
54
|
"devDependencies": {
|
|
52
55
|
"@testing-library/dom": "^10.4.1",
|
|
53
56
|
"@testing-library/react": "^16.3.2",
|
|
54
|
-
"@types/react": "^
|
|
55
|
-
"@types/react-dom": "^19.2.
|
|
57
|
+
"@types/react": "^19.2.17",
|
|
58
|
+
"@types/react-dom": "^19.2.4",
|
|
56
59
|
"jsdom": "^29.1.1",
|
|
57
|
-
"react": "^19.2.
|
|
58
|
-
"react-dom": "^19.2.
|
|
60
|
+
"react": "^19.2.8",
|
|
61
|
+
"react-dom": "^19.2.8",
|
|
59
62
|
"reactflow": "^11.11.4",
|
|
60
63
|
"typescript": "^5.0.0",
|
|
61
|
-
"vitest": "^
|
|
64
|
+
"vitest": "^4.1.10"
|
|
62
65
|
}
|
|
63
66
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Activity Card notation — single-project narrative view.
|
|
2
2
|
//
|
|
3
3
|
// Spec: transitrix/methodology `notations/views/18-activity-card.md` (v0.1).
|
|
4
|
-
//
|
|
4
|
+
// Studio track #134.
|
|
5
5
|
//
|
|
6
6
|
// The Activity Card is the first MULTI-DOCUMENT Studio notation. The card
|
|
7
7
|
// YAML itself holds only `id`, `project` (an Activity ID), `description`,
|
package/src/agreement.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Agreement axis — methodology CONTRACT.md §6.3 / §6.3.1 (AGREE-001..003).
|
|
2
|
+
//
|
|
3
|
+
// A closed three-value axis (`draft` | `agreed` | `disputed`) carried by
|
|
4
|
+
// REQUIREMENT, CONSTRAINT, and NEED — independent of admission (§6.1),
|
|
5
|
+
// recording whether the accountable party has committed to the statement.
|
|
6
|
+
// Absent ⇒ `agreed` (back-compat; a human-authored element that predates
|
|
7
|
+
// this axis needs no change).
|
|
8
|
+
//
|
|
9
|
+
// Ported from methodology's reference implementation
|
|
10
|
+
// (`scripts/check-agreement.mjs`'s `checkAgreement`/`looksLikeTool`) so a
|
|
11
|
+
// repo carrying the field validates identically here — same closed
|
|
12
|
+
// vocabulary, same tool-id heuristic, same "AGREE-003 wins over AGREE-002
|
|
13
|
+
// when both would fire" precedence.
|
|
14
|
+
|
|
15
|
+
import type { ValidationError } from './validation-types.js';
|
|
16
|
+
|
|
17
|
+
export const AGREEMENT_VALUES = ['draft', 'agreed', 'disputed'] as const;
|
|
18
|
+
export type AgreementValue = (typeof AGREEMENT_VALUES)[number];
|
|
19
|
+
|
|
20
|
+
/** Same convention as `ADMIT-007`'s footgun-catcher: an npm-scoped name, or
|
|
21
|
+
* a hyphenated `*-cli` / `*-reviewer[-…]` / `*-bot` / `*-scanner` id, reads
|
|
22
|
+
* as a tool; anything else reads as a human handle. Not a security
|
|
23
|
+
* boundary — catches the common mistake of a tool defaulting or
|
|
24
|
+
* copy-pasting `agreed`. */
|
|
25
|
+
const TOOL_ID_RE = /^@|-cli$|-reviewer(?:-\w+)?$|-bot$|-scanner$/i;
|
|
26
|
+
|
|
27
|
+
export function looksLikeTool(id: unknown): boolean {
|
|
28
|
+
return TOOL_ID_RE.test(String(id ?? ''));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* AGREE-001..003 over an element's already-extracted `agreement` /
|
|
33
|
+
* `agreed_by` fields. Returns an empty list when `agreement` is absent
|
|
34
|
+
* (back-compat — absent ⇒ `agreed`, nothing to check) or when the fields
|
|
35
|
+
* are well-formed.
|
|
36
|
+
*
|
|
37
|
+
* AGREE-002 and AGREE-003 can both describe an `agreement: agreed` record
|
|
38
|
+
* with no `agreed_by` — this reports AGREE-003 only, the same choice
|
|
39
|
+
* `check-agreement.mjs` makes (CONTRACT.md §6.3.1 permits either or both).
|
|
40
|
+
*/
|
|
41
|
+
export function checkAgreement(record: Record<string, unknown>): ValidationError[] {
|
|
42
|
+
const agreement = record.agreement;
|
|
43
|
+
if (agreement === undefined) return [];
|
|
44
|
+
|
|
45
|
+
if (typeof agreement !== 'string' || !(AGREEMENT_VALUES as readonly string[]).includes(agreement)) {
|
|
46
|
+
return [{
|
|
47
|
+
code: 'AGREE-001',
|
|
48
|
+
message: `agreement "${String(agreement)}" must be one of ${AGREEMENT_VALUES.join(', ')}.`,
|
|
49
|
+
path: 'agreement',
|
|
50
|
+
}];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const agreedBy = record.agreed_by;
|
|
54
|
+
if (typeof agreedBy !== 'string' || agreedBy.trim() === '') {
|
|
55
|
+
return [{
|
|
56
|
+
code: 'AGREE-003',
|
|
57
|
+
message: 'agreed_by is required whenever agreement is present.',
|
|
58
|
+
path: 'agreed_by',
|
|
59
|
+
}];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (agreement === 'agreed' && looksLikeTool(agreedBy)) {
|
|
63
|
+
return [{
|
|
64
|
+
code: 'AGREE-002',
|
|
65
|
+
message: `agreement: agreed but agreed_by "${agreedBy}" identifies a tool — a tool must never write "agreed".`,
|
|
66
|
+
path: 'agreed_by',
|
|
67
|
+
}];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
@@ -9,3 +9,6 @@ export type {
|
|
|
9
9
|
} from './types.js';
|
|
10
10
|
|
|
11
11
|
export { validateApplicationsCatalogue } from './validate.js';
|
|
12
|
+
|
|
13
|
+
export type { ResolvedApplicationAttributes } from './resolve-maturity.js';
|
|
14
|
+
export { resolveApplicationAttributes, withResolvedAttributes } from './resolve-maturity.js';
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Render-time resolution of the applications-catalogue's sidecar-bound
|
|
2
|
+
// time_varying fields (owner_role, vendor, maturity — notations/views/
|
|
3
|
+
// 10-applications.md §5a, CONTRACT.md §9.4). VERSIONED-004 rejects these
|
|
4
|
+
// inline on an admitted catalogue entry, so a compliant document never
|
|
5
|
+
// carries them — a renderer that only reads the document straight shows
|
|
6
|
+
// blank cells for every field the maturity decision moved out.
|
|
7
|
+
//
|
|
8
|
+
// Pure: no I/O, no repo walk. The host (VS Code extension, IntelliJ/JCEF,
|
|
9
|
+
// CLI) already owns loading canon/elements/** (canon-loader.ts's shared FS
|
|
10
|
+
// layer) — this module only knows how to pick sidecars out of that pool and
|
|
11
|
+
// resolve them at a date.
|
|
12
|
+
//
|
|
13
|
+
// Per the 2026-08-05 hub decision (proposal #1022): this is a read model,
|
|
14
|
+
// never storage. Callers render an augmented, ephemeral copy of the
|
|
15
|
+
// application row and must never serialise it back — the sidecar stays the
|
|
16
|
+
// only place these fields are written.
|
|
17
|
+
|
|
18
|
+
import { parseSidecar, resolveAttributes } from '../versioned-attribute/index.js';
|
|
19
|
+
import type { Application } from './types.js';
|
|
20
|
+
|
|
21
|
+
export interface ResolvedApplicationAttributes {
|
|
22
|
+
owner_role?: string;
|
|
23
|
+
vendor?: string;
|
|
24
|
+
maturity?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Pick every sidecar out of a raw canon/elements/** doc pool and resolve
|
|
28
|
+
* owner_role/vendor/maturity at `atDate`, keyed by the sidecar's target id
|
|
29
|
+
* (== an application's `app_id`, per the notation's element-file pairing). */
|
|
30
|
+
export function resolveApplicationAttributes(
|
|
31
|
+
rawElementDocs: unknown[],
|
|
32
|
+
atDate: string,
|
|
33
|
+
): Map<string, ResolvedApplicationAttributes> {
|
|
34
|
+
const byAppId = new Map<string, ResolvedApplicationAttributes>();
|
|
35
|
+
for (const doc of rawElementDocs) {
|
|
36
|
+
if (!doc || typeof doc !== 'object' || Array.isArray(doc)) continue;
|
|
37
|
+
const sidecar = parseSidecar(doc as Record<string, unknown>);
|
|
38
|
+
if (!sidecar) continue;
|
|
39
|
+
const resolved = resolveAttributes(sidecar, atDate);
|
|
40
|
+
const entry: ResolvedApplicationAttributes = {};
|
|
41
|
+
if (typeof resolved['owner_role'] === 'string') entry.owner_role = resolved['owner_role'];
|
|
42
|
+
if (typeof resolved['vendor'] === 'string') entry.vendor = resolved['vendor'];
|
|
43
|
+
if (typeof resolved['maturity'] === 'number') entry.maturity = resolved['maturity'];
|
|
44
|
+
if (Object.keys(entry).length > 0) byAppId.set(sidecar.target, entry);
|
|
45
|
+
}
|
|
46
|
+
return byAppId;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Merge resolved sidecar values into a rendering copy of an application row.
|
|
50
|
+
* An inline value would win if present, but VERSIONED-004 already forbids
|
|
51
|
+
* that on an admitted document — this is a display fallback, not a
|
|
52
|
+
* migration tool. The returned object is a plain copy; nothing here writes
|
|
53
|
+
* it anywhere. */
|
|
54
|
+
export function withResolvedAttributes(
|
|
55
|
+
app: Application,
|
|
56
|
+
resolved: Map<string, ResolvedApplicationAttributes>,
|
|
57
|
+
): Application {
|
|
58
|
+
const r = resolved.get(app.app_id);
|
|
59
|
+
if (!r) return app;
|
|
60
|
+
return {
|
|
61
|
+
...app,
|
|
62
|
+
owner_role: app.owner_role ?? r.owner_role,
|
|
63
|
+
vendor: app.vendor ?? r.vendor,
|
|
64
|
+
maturity: app.maturity ?? r.maturity,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -12,6 +12,17 @@ const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
|
12
12
|
|
|
13
13
|
const IFACE_CONDITIONAL = ['protocol', 'payload_class', 'sensitivity', 'directionality'] as const;
|
|
14
14
|
|
|
15
|
+
// VERSIONED-004 (CONTRACT.md §9.3) — `owner_role`, `vendor`, `maturity` are
|
|
16
|
+
// declared `time_varying` by notations/views/10-applications.md §5/§5a and
|
|
17
|
+
// belong in the sidecar `<app_id>.history.yaml`, not inline on the catalogue
|
|
18
|
+
// entry. Unlike capability's VERSIONED-004 (packages/diagrams/src/
|
|
19
|
+
// repo-validate/check-versioned-attributes.ts), which walks standalone
|
|
20
|
+
// element files under canon/elements/**, an applications-catalogue entry has
|
|
21
|
+
// no file of its own to check inline-vs-sidecar on — the entry lives in this
|
|
22
|
+
// view document — so the check runs here, at the per-file notation
|
|
23
|
+
// validator, rather than in the repo-scope element sweep.
|
|
24
|
+
const TIME_VARYING_FIELDS = ['owner_role', 'vendor', 'maturity'] as const;
|
|
25
|
+
|
|
15
26
|
export function validateApplicationsCatalogue(input: unknown): ValidationResult {
|
|
16
27
|
const errors: ValidationError[] = [];
|
|
17
28
|
const warnings: ValidationWarning[] = [];
|
|
@@ -114,6 +125,18 @@ export function validateApplicationsCatalogue(input: unknown): ValidationResult
|
|
|
114
125
|
}
|
|
115
126
|
}
|
|
116
127
|
|
|
128
|
+
// VERSIONED-004: time_varying fields must live in the sidecar, not inline.
|
|
129
|
+
for (const field of TIME_VARYING_FIELDS) {
|
|
130
|
+
if (a[field] !== undefined) {
|
|
131
|
+
errors.push({
|
|
132
|
+
code: 'VERSIONED-004',
|
|
133
|
+
message:
|
|
134
|
+
`${idx}: '${field}' is time_varying (CONTRACT.md §9) and belongs in the sidecar ` +
|
|
135
|
+
`'${typeof a['app_id'] === 'string' ? a['app_id'] : idx}.history.yaml', not inline on the catalogue entry.`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
117
140
|
// APP-009: integrations[].direction enum
|
|
118
141
|
if (Array.isArray(a['integrations'])) {
|
|
119
142
|
const integrations = a['integrations'] as unknown[];
|
|
@@ -7,6 +7,13 @@ export type {
|
|
|
7
7
|
|
|
8
8
|
export { validateCapabilityMap } from './validate.js';
|
|
9
9
|
|
|
10
|
+
export type { ResolvedCapabilityAttributes } from './resolve-maturity.js';
|
|
11
|
+
export {
|
|
12
|
+
resolveCapabilityAttributes,
|
|
13
|
+
withResolvedAttributes,
|
|
14
|
+
withResolvedCapabilityMap,
|
|
15
|
+
} from './resolve-maturity.js';
|
|
16
|
+
|
|
10
17
|
export type {
|
|
11
18
|
CapabilityTreeNode,
|
|
12
19
|
CapabilityTreeEdge,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Render-time resolution of a nested capability-map entry's sidecar-bound
|
|
2
|
+
// time_varying fields (current_maturity, target_maturity, owner_role,
|
|
3
|
+
// target_date — 05-capability-map.md §13/§14, CONTRACT.md §9.4).
|
|
4
|
+
// check-versioned-attributes.ts's VERSIONED-004 already rejects these inline
|
|
5
|
+
// on the standalone CAPABILITY-*.yaml primitive; a nested capability-map
|
|
6
|
+
// entry references that same primitive by id (the 2026-08-05 packages
|
|
7
|
+
// decision — no separate per-nested-entry sidecar convention), so this
|
|
8
|
+
// resolves the same way applications/resolve-maturity.ts does for the
|
|
9
|
+
// applications catalogue.
|
|
10
|
+
//
|
|
11
|
+
// Pure: no I/O, no repo walk. The host (VS Code extension, IntelliJ/JCEF,
|
|
12
|
+
// CLI) already owns loading canon/elements/** — this module only knows how
|
|
13
|
+
// to pick sidecars out of that pool and resolve them at a date.
|
|
14
|
+
//
|
|
15
|
+
// Per the 2026-08-05 packages decision: this is a read model, never
|
|
16
|
+
// storage. Callers render an augmented, ephemeral copy of the capability
|
|
17
|
+
// tree and must never serialise it back — the sidecar stays the only place
|
|
18
|
+
// these fields are written.
|
|
19
|
+
|
|
20
|
+
import { parseSidecar, resolveAttributes } from '../versioned-attribute/index.js';
|
|
21
|
+
import type { CapabilityMapHeader, CapabilityNode } from './types.js';
|
|
22
|
+
|
|
23
|
+
export interface ResolvedCapabilityAttributes {
|
|
24
|
+
current_maturity?: number;
|
|
25
|
+
target_maturity?: number;
|
|
26
|
+
owner_role?: string;
|
|
27
|
+
target_date?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Pick every sidecar out of a raw canon/elements/** doc pool and resolve
|
|
31
|
+
* current_maturity/target_maturity/owner_role/target_date at `atDate`,
|
|
32
|
+
* keyed by the sidecar's target id (== a capability entry's `id`, per the
|
|
33
|
+
* CAPABILITY-* primitive that entry references). */
|
|
34
|
+
export function resolveCapabilityAttributes(
|
|
35
|
+
rawElementDocs: unknown[],
|
|
36
|
+
atDate: string,
|
|
37
|
+
): Map<string, ResolvedCapabilityAttributes> {
|
|
38
|
+
const byId = new Map<string, ResolvedCapabilityAttributes>();
|
|
39
|
+
for (const doc of rawElementDocs) {
|
|
40
|
+
if (!doc || typeof doc !== 'object' || Array.isArray(doc)) continue;
|
|
41
|
+
const sidecar = parseSidecar(doc as Record<string, unknown>);
|
|
42
|
+
if (!sidecar) continue;
|
|
43
|
+
const resolved = resolveAttributes(sidecar, atDate);
|
|
44
|
+
const entry: ResolvedCapabilityAttributes = {};
|
|
45
|
+
if (typeof resolved['current_maturity'] === 'number') entry.current_maturity = resolved['current_maturity'];
|
|
46
|
+
if (typeof resolved['target_maturity'] === 'number') entry.target_maturity = resolved['target_maturity'];
|
|
47
|
+
if (typeof resolved['owner_role'] === 'string') entry.owner_role = resolved['owner_role'];
|
|
48
|
+
if (typeof resolved['target_date'] === 'string') entry.target_date = resolved['target_date'];
|
|
49
|
+
if (Object.keys(entry).length > 0) byId.set(sidecar.target, entry);
|
|
50
|
+
}
|
|
51
|
+
return byId;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Merge resolved sidecar values into a rendering copy of a capability node
|
|
55
|
+
* and its descendants. An inline value always wins if present — this is a
|
|
56
|
+
* display fallback for fields the document doesn't carry inline, not a
|
|
57
|
+
* migration tool. Returns a new node tree; nothing here writes it
|
|
58
|
+
* anywhere. */
|
|
59
|
+
export function withResolvedAttributes(
|
|
60
|
+
node: CapabilityNode,
|
|
61
|
+
resolved: Map<string, ResolvedCapabilityAttributes>,
|
|
62
|
+
): CapabilityNode {
|
|
63
|
+
const r = resolved.get(node.id);
|
|
64
|
+
const merged: CapabilityNode = r
|
|
65
|
+
? {
|
|
66
|
+
...node,
|
|
67
|
+
current_maturity: node.current_maturity ?? r.current_maturity,
|
|
68
|
+
target_maturity: node.target_maturity ?? r.target_maturity,
|
|
69
|
+
owner_role: node.owner_role ?? r.owner_role,
|
|
70
|
+
target_date: node.target_date ?? r.target_date,
|
|
71
|
+
}
|
|
72
|
+
: node;
|
|
73
|
+
if (!node.children || node.children.length === 0) return merged;
|
|
74
|
+
return { ...merged, children: node.children.map((c) => withResolvedAttributes(c, resolved)) };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Same merge, applied across a capability map's whole top-level tree. */
|
|
78
|
+
export function withResolvedCapabilityMap(
|
|
79
|
+
map: CapabilityMapHeader,
|
|
80
|
+
resolved: Map<string, ResolvedCapabilityAttributes>,
|
|
81
|
+
): CapabilityMapHeader {
|
|
82
|
+
if (resolved.size === 0) return map;
|
|
83
|
+
return { ...map, capabilities: map.capabilities.map((c) => withResolvedAttributes(c, resolved)) };
|
|
84
|
+
}
|
|
@@ -8,8 +8,7 @@ const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
|
8
8
|
// Canonical form per `IDS_AND_REFERENCES.md` §2: `CAPABILITY-V…` /
|
|
9
9
|
// `CAPABILITY-H…` with the dotted address (`V1`, `V1.2.1`, `H1`). The bare
|
|
10
10
|
// legacy form (`V1`, `H1`) is still accepted so existing maps without the
|
|
11
|
-
// canonical prefix do not break
|
|
12
|
-
// vkgeorgia/strategy#36.
|
|
11
|
+
// canonical prefix do not break; migration is tracked separately.
|
|
13
12
|
const CAP_ID_RE = /^(CAPABILITY-)?(V|H)\d+(\.\d+)*$/;
|
|
14
13
|
|
|
15
14
|
export function validateCapabilityMap(input: unknown): ValidationResult {
|