@chrisdudek/yg 5.2.6 → 5.4.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/bin.js +5152 -3063
- package/dist/structure.d.ts +9 -0
- package/dist/structure.js +10 -5
- package/dist/templates/portal/js/bootstrap.js +206 -0
- package/dist/templates/portal/js/consumer.js +128 -0
- package/dist/templates/portal/js/dispatch.js +181 -0
- package/dist/templates/portal/js/export.js +151 -0
- package/dist/templates/portal/js/glossary.js +72 -0
- package/dist/templates/portal/js/namespace.js +60 -0
- package/dist/templates/portal/js/palette-overlay.js +176 -0
- package/dist/templates/portal/js/palette.js +133 -0
- package/dist/templates/portal/js/router.js +169 -0
- package/dist/templates/portal/js/shell.js +264 -0
- package/dist/templates/portal/js/state-model.js +142 -0
- package/dist/templates/portal/js/tree.js +190 -0
- package/dist/templates/portal/js/views/coverage-view.js +247 -0
- package/dist/templates/portal/js/views/flows-view.js +204 -0
- package/dist/templates/portal/js/views/overview-view.js +167 -0
- package/dist/templates/portal/js/views/panel-aspect.js +141 -0
- package/dist/templates/portal/js/views/panel-view.js +314 -0
- package/dist/templates/portal/js/views/relations-matrix.js +185 -0
- package/dist/templates/portal/js/views/relations-view.js +185 -0
- package/dist/templates/portal/js/views/rulebook-view.js +174 -0
- package/dist/templates/portal/js/views/start-view.js +287 -0
- package/dist/templates/portal/js/views/suppressions-view.js +187 -0
- package/dist/templates/portal/js/views/tree-view.js +68 -0
- package/dist/templates/portal/js/views/types-view.js +154 -0
- package/dist/templates/portal/vendor/d3-hierarchy.js +350 -0
- package/package.json +5 -2
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* V1 Overview — the pro-adoption landing.
|
|
3
|
+
*
|
|
4
|
+
* The first surface on open (empty hash). Never a celebratory checkmark, never the raw graph.
|
|
5
|
+
* It pairs a plain-language verdict (derived live from the counts — honest about what is and
|
|
6
|
+
* is not green) with a "Start here" door to the on-ramp, the residue turned into links (the
|
|
7
|
+
* unguarded surface made clickable, so absence of red is never a pass), and a collapsed
|
|
8
|
+
* "precise picture" preview that opens the full Coverage & Audit ledger (V2). Every state
|
|
9
|
+
* treatment is read from the one shared honest-state model; nothing here invents a green.
|
|
10
|
+
*
|
|
11
|
+
* Browser globals only — reads the already-resolved PortalData; no network, no Node.
|
|
12
|
+
*/
|
|
13
|
+
(function () {
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
var Yg = (window.YgPortal = window.YgPortal || {});
|
|
17
|
+
var dom = Yg.dom;
|
|
18
|
+
Yg.views = Yg.views || {};
|
|
19
|
+
|
|
20
|
+
/** A plain-language verdict derived honestly from the live counts. */
|
|
21
|
+
function verdict(c) {
|
|
22
|
+
if (c.errors > 0) {
|
|
23
|
+
return {
|
|
24
|
+
state: c.refused > 0 ? 'refused' : 'unverified',
|
|
25
|
+
head: c.refused > 0 ? 'Some code broke a rule.' : 'Some code is waiting to be checked.',
|
|
26
|
+
sub:
|
|
27
|
+
'A reviewer found ' +
|
|
28
|
+
c.refused +
|
|
29
|
+
' refusal(s) and ' +
|
|
30
|
+
c.unverified +
|
|
31
|
+
' thing(s) not yet confirmed against the current code. These block until resolved.',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (c.warnings > 0) {
|
|
35
|
+
return {
|
|
36
|
+
state: 'warning',
|
|
37
|
+
head: 'No failures — a few advisories worth a look.',
|
|
38
|
+
sub: c.warnings + ' advisory signal(s) flagged. They do not block; they are worth a look.',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
state: 'verified',
|
|
43
|
+
head: 'Every checked thing passed against the current code.',
|
|
44
|
+
sub:
|
|
45
|
+
c.verified +
|
|
46
|
+
' of ' +
|
|
47
|
+
c.pairsTotal +
|
|
48
|
+
' expected checks are verified. Green means a reviewer actually checked it — but the absence of red is not a pass, so the unguarded surface below is still worth a look.',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A residue link chip that routes somewhere honest (never a dead end of numbers). */
|
|
53
|
+
function residueLink(state, count, text, onClick) {
|
|
54
|
+
var chip = dom.el('button', 'reslink');
|
|
55
|
+
chip.type = 'button';
|
|
56
|
+
chip.appendChild(Yg.states.badge(state));
|
|
57
|
+
chip.appendChild(dom.el('b', null, String(count)));
|
|
58
|
+
chip.appendChild(dom.el('span', null, text));
|
|
59
|
+
chip.appendChild(dom.el('span', 'reslink-arrow', '→'));
|
|
60
|
+
chip.addEventListener('click', onClick);
|
|
61
|
+
return chip;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The file-aware freshness strip — the heartbeat (§0b.1). When any node's source has changed
|
|
66
|
+
* since the last reviewer pass, the landing says so FIRST: a touched file reads "we don't
|
|
67
|
+
* know", and the whole-repo cached green can never render as "you're fine" over it. Each
|
|
68
|
+
* touched node is a chip routing to its attestation panel. Absent any touched node the strip
|
|
69
|
+
* is not shown (no fabricated "all fresh" claim).
|
|
70
|
+
*/
|
|
71
|
+
function freshnessStrip(stage, data, nav) {
|
|
72
|
+
var touched = (data.nodes || []).filter(function (n) {
|
|
73
|
+
return n.fresh === true;
|
|
74
|
+
});
|
|
75
|
+
if (!touched.length) return;
|
|
76
|
+
var strip = dom.el('div', 'ov-fresh ' + Yg.states.cssClass('unverified'));
|
|
77
|
+
var head = dom.el('div', 'ov-fresh-head');
|
|
78
|
+
head.appendChild(Yg.states.badge('unverified'));
|
|
79
|
+
head.appendChild(dom.el('b', null, touched.length + (touched.length === 1 ? ' file changed' : ' files changed') + ' since the last reviewer pass'));
|
|
80
|
+
strip.appendChild(head);
|
|
81
|
+
strip.appendChild(dom.el('p', 'ov-fresh-sub', 'These read unverified — not a pass, just “we don’t know” over the new bytes. Whole-repo cached green never overrides a file you just touched. Refresh re-runs the free checks; Approve re-confirms.'));
|
|
82
|
+
var chips = dom.el('div', 'ov-fresh-chips');
|
|
83
|
+
for (var i = 0; i < touched.length && i < 24; i += 1) {
|
|
84
|
+
var n = touched[i];
|
|
85
|
+
var chip = dom.el('button', 'ov-fresh-chip');
|
|
86
|
+
chip.type = 'button';
|
|
87
|
+
chip.setAttribute('aria-label', 'Open ' + n.path + ' (source changed, unverified)');
|
|
88
|
+
chip.appendChild(Yg.states.badge('unverified'));
|
|
89
|
+
chip.appendChild(dom.el('span', 'mono', n.path));
|
|
90
|
+
chip.addEventListener('click', (function (p) {
|
|
91
|
+
return function () {
|
|
92
|
+
nav({ view: 'tree', node: p });
|
|
93
|
+
};
|
|
94
|
+
})(n.path));
|
|
95
|
+
chips.appendChild(chip);
|
|
96
|
+
}
|
|
97
|
+
strip.appendChild(chips);
|
|
98
|
+
stage.appendChild(strip);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
Yg.views.overview = function (stage, route, data, ctx) {
|
|
102
|
+
var c = data.meta.counts;
|
|
103
|
+
var nav = ctx && ctx.navigate ? ctx.navigate : function () {};
|
|
104
|
+
|
|
105
|
+
// The file-aware loop heartbeat: surface any touched-but-unverified file at the very top.
|
|
106
|
+
freshnessStrip(stage, data, nav);
|
|
107
|
+
|
|
108
|
+
// Plain-language verdict + the Start-here door.
|
|
109
|
+
var hero = dom.el('div', 'ov-hero');
|
|
110
|
+
var v = verdict(c);
|
|
111
|
+
var verdictBox = dom.el('div', 'ov-verdict ' + Yg.states.cssClass(v.state));
|
|
112
|
+
var vhead = dom.el('div', 'ov-verdict-head');
|
|
113
|
+
vhead.appendChild(Yg.states.badge(v.state));
|
|
114
|
+
vhead.appendChild(dom.el('b', null, v.head));
|
|
115
|
+
verdictBox.appendChild(vhead);
|
|
116
|
+
verdictBox.appendChild(dom.el('p', 'ov-verdict-sub', v.sub));
|
|
117
|
+
hero.appendChild(verdictBox);
|
|
118
|
+
|
|
119
|
+
var door = dom.el('div', 'ov-door');
|
|
120
|
+
door.appendChild(dom.el('h4', null, 'New here?'));
|
|
121
|
+
door.appendChild(dom.el('p', null, 'A two-minute walk through what this system is and how to read the colours.'));
|
|
122
|
+
var doorBtn = dom.el('button', 'ov-door-link');
|
|
123
|
+
doorBtn.type = 'button';
|
|
124
|
+
doorBtn.appendChild(dom.el('span', null, 'Start here →'));
|
|
125
|
+
doorBtn.addEventListener('click', function () {
|
|
126
|
+
nav({ view: 'start' });
|
|
127
|
+
});
|
|
128
|
+
door.appendChild(doorBtn);
|
|
129
|
+
hero.appendChild(door);
|
|
130
|
+
stage.appendChild(hero);
|
|
131
|
+
|
|
132
|
+
// The residue — the honest unguarded surface, made clickable.
|
|
133
|
+
var resLabel = dom.el('div', 'ov-sectlabel', 'The residue worth a look');
|
|
134
|
+
stage.appendChild(resLabel);
|
|
135
|
+
var residue = dom.el('div', 'ov-residue');
|
|
136
|
+
residue.appendChild(
|
|
137
|
+
residueLink('no-rule', c.noRule, 'nodes have no effective rule', function () {
|
|
138
|
+
nav({ view: 'coverage', filter: 'no-rule' });
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
residue.appendChild(
|
|
142
|
+
residueLink('no-rule', c.uncoveredFiles, 'source files unmapped (unguarded)', function () {
|
|
143
|
+
nav({ view: 'coverage', filter: 'uncovered' });
|
|
144
|
+
}),
|
|
145
|
+
);
|
|
146
|
+
residue.appendChild(
|
|
147
|
+
residueLink('suppressed', c.suppressed, 'active waivers', function () {
|
|
148
|
+
nav({ view: 'suppressions' });
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
stage.appendChild(residue);
|
|
152
|
+
|
|
153
|
+
// The precise-picture preview → opens the full ledger (V2).
|
|
154
|
+
var precise = dom.el('button', 'ov-precise');
|
|
155
|
+
precise.type = 'button';
|
|
156
|
+
precise.appendChild(dom.el('span', 'ov-precise-frac', c.verified + ' / ' + c.pairsTotal));
|
|
157
|
+
precise.appendChild(dom.el('span', 'ov-precise-lbl', 'expected checks verified — open the precise picture'));
|
|
158
|
+
precise.appendChild(dom.el('span', 'reslink-arrow', '→'));
|
|
159
|
+
precise.addEventListener('click', function () {
|
|
160
|
+
nav({ view: 'coverage' });
|
|
161
|
+
});
|
|
162
|
+
stage.appendChild(precise);
|
|
163
|
+
|
|
164
|
+
var foot = dom.el('p', 'ov-foot', 'Absence of red is not a pass — green means a reviewer actually checked it and approved against current inputs.');
|
|
165
|
+
stage.appendChild(foot);
|
|
166
|
+
};
|
|
167
|
+
})();
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Aspect inspector — the aspect-side mirror of the node attestation panel.
|
|
3
|
+
*
|
|
4
|
+
* Renders ONE rule into the shared right-hand panel: Yg.views.panel delegates here for an aspect
|
|
5
|
+
* route. Shows the rule's identity (kind / status / scope / when), the actual rule prose
|
|
6
|
+
* (content.md) for an LLM rule or an honest note for a deterministic / aggregate one, its implies
|
|
7
|
+
* chain, and every component it lands on with that component's honest verdict (each cell routing
|
|
8
|
+
* to the component's own panel). Split out of panel-view.js so each file stays a focused unit.
|
|
9
|
+
*
|
|
10
|
+
* Browser globals only — reads the already-resolved PortalData; no network, no Node.
|
|
11
|
+
*/
|
|
12
|
+
(function () {
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
var Yg = (window.YgPortal = window.YgPortal || {});
|
|
16
|
+
var dom = Yg.dom;
|
|
17
|
+
|
|
18
|
+
/** A titled panel section with an optional count label (mirrors panel-view's own helper). */
|
|
19
|
+
function section(title, countLabel) {
|
|
20
|
+
var s = dom.el('section', 'pan-sect');
|
|
21
|
+
var h = dom.el('h5', 'pan-h', title);
|
|
22
|
+
if (countLabel != null) h.appendChild(dom.el('span', 'pan-count', ' · ' + countLabel));
|
|
23
|
+
s.appendChild(h);
|
|
24
|
+
return s;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* One honest per-node cell for an aspect: the node's verdict state for THIS rule + the path,
|
|
29
|
+
* routing to that node's own attestation panel. Reuses the rulebook cell classes.
|
|
30
|
+
*/
|
|
31
|
+
function aspectNodeCell(node, eff, na, nav) {
|
|
32
|
+
var state = na ? 'not-applicable' : !eff ? 'no-rule' : eff.pairState === 'n/a' ? 'not-applicable' : eff.pairState;
|
|
33
|
+
var cell = dom.el('button', 'rb-cell ' + Yg.states.cssClass(state));
|
|
34
|
+
cell.type = 'button';
|
|
35
|
+
cell.appendChild(Yg.states.badge(state));
|
|
36
|
+
cell.appendChild(dom.el('span', 'mono', node.path));
|
|
37
|
+
cell.appendChild(dom.el('span', 'rb-cell-st', Yg.states.label(state)));
|
|
38
|
+
cell.addEventListener('click', function () {
|
|
39
|
+
nav({ view: 'tree', node: node.path });
|
|
40
|
+
});
|
|
41
|
+
return cell;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Render a RULE into the shared panel — its identity (kind / status / scope / when), the actual
|
|
46
|
+
* rule prose (content.md) for an LLM rule or an honest note for a deterministic / aggregate one,
|
|
47
|
+
* its implies chain, and every node it lands on with that node's honest verdict.
|
|
48
|
+
*/
|
|
49
|
+
function renderAspectPanel(panel, route, data, nav) {
|
|
50
|
+
var close = dom.el('button', 'pan-close', '×');
|
|
51
|
+
close.type = 'button';
|
|
52
|
+
close.setAttribute('aria-label', 'Close panel');
|
|
53
|
+
close.addEventListener('click', function () {
|
|
54
|
+
nav({ view: route.view || 'rulebook' });
|
|
55
|
+
});
|
|
56
|
+
panel.appendChild(close);
|
|
57
|
+
|
|
58
|
+
var aspect = (data.aspects || []).filter(function (a) {
|
|
59
|
+
return a.id === route.aspect;
|
|
60
|
+
})[0];
|
|
61
|
+
if (!aspect) {
|
|
62
|
+
panel.appendChild(dom.el('h3', 'panel-title mono', route.aspect));
|
|
63
|
+
panel.appendChild(dom.el('p', 'panel-sub', 'No rule named "' + route.aspect + '" — it may have been removed.'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Identity.
|
|
68
|
+
var head = dom.el('div', 'pan-head pan-aspect');
|
|
69
|
+
head.appendChild(dom.el('div', 'pan-title mono', aspect.id));
|
|
70
|
+
var meta = dom.el('div', 'pan-meta');
|
|
71
|
+
meta.appendChild(
|
|
72
|
+
dom.el('span', 'rb-badge rb-badge-' + aspect.kind, aspect.kind === 'llm' ? 'LLM' : aspect.kind === 'aggregate' ? 'aggregating' : 'deterministic'),
|
|
73
|
+
);
|
|
74
|
+
meta.appendChild(dom.el('span', 'rb-st rb-st-' + aspect.status, aspect.status));
|
|
75
|
+
if (aspect.kind !== 'aggregate') meta.appendChild(dom.el('span', 'mono', 'per:' + aspect.scope + (aspect.hasWhen ? ' · when' : '')));
|
|
76
|
+
head.appendChild(meta);
|
|
77
|
+
if (aspect.name && aspect.name !== aspect.id) head.appendChild(dom.el('p', 'pan-desc', aspect.name));
|
|
78
|
+
panel.appendChild(head);
|
|
79
|
+
|
|
80
|
+
// The rule itself — the exact text the reviewer enforces: the LLM prose (content.md), or the
|
|
81
|
+
// deterministic check's own source (check.mjs) for a local check, or an honest note for a bundle.
|
|
82
|
+
var ruleSect = section('The rule');
|
|
83
|
+
if (aspect.ruleProse) {
|
|
84
|
+
ruleSect.appendChild(dom.el('pre', 'pan-rule', aspect.ruleProse));
|
|
85
|
+
} else if (aspect.checkSource) {
|
|
86
|
+
ruleSect.appendChild(dom.el('div', 'pan-rule-h', 'A deterministic local check — the exact source that enforces it:'));
|
|
87
|
+
ruleSect.appendChild(dom.el('pre', 'pan-rule pan-rule-code', aspect.checkSource));
|
|
88
|
+
} else if (aspect.kind === 'aggregate') {
|
|
89
|
+
ruleSect.appendChild(dom.el('p', 'pan-norule', 'A bundle — it groups the rules below and judges nothing of its own.'));
|
|
90
|
+
} else {
|
|
91
|
+
ruleSect.appendChild(dom.el('p', 'pan-norule', 'A deterministic local check enforces this rule mechanically. Its state is the union of the cells below.'));
|
|
92
|
+
}
|
|
93
|
+
panel.appendChild(ruleSect);
|
|
94
|
+
|
|
95
|
+
// Implies chain (clickable to each included rule).
|
|
96
|
+
if (aspect.implies && aspect.implies.length) {
|
|
97
|
+
var impSect = section('Includes', String(aspect.implies.length));
|
|
98
|
+
var ul = dom.el('ul', 'pan-rels');
|
|
99
|
+
for (var k = 0; k < aspect.implies.length; k += 1) {
|
|
100
|
+
var li = dom.el('li', 'pan-rel');
|
|
101
|
+
var link = dom.el('button', 'pan-rellink mono');
|
|
102
|
+
link.type = 'button';
|
|
103
|
+
link.textContent = aspect.implies[k];
|
|
104
|
+
link.addEventListener('click', (function (id) {
|
|
105
|
+
return function () {
|
|
106
|
+
nav({ view: 'rulebook', aspect: id });
|
|
107
|
+
};
|
|
108
|
+
})(aspect.implies[k]));
|
|
109
|
+
li.appendChild(link);
|
|
110
|
+
ul.appendChild(li);
|
|
111
|
+
}
|
|
112
|
+
impSect.appendChild(ul);
|
|
113
|
+
panel.appendChild(impSect);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Every node it lands on, each with its honest verdict for THIS rule.
|
|
117
|
+
var cellSect = section('Every component it lands on');
|
|
118
|
+
var cells = dom.el('div', 'rb-cells');
|
|
119
|
+
var found = 0;
|
|
120
|
+
var nodes = data.nodes || [];
|
|
121
|
+
for (var i = 0; i < nodes.length; i += 1) {
|
|
122
|
+
var node = nodes[i];
|
|
123
|
+
var eff = (node.effectiveAspects || []).filter(function (e) {
|
|
124
|
+
return e.aspectId === aspect.id;
|
|
125
|
+
})[0];
|
|
126
|
+
var na = !eff
|
|
127
|
+
? (node.notApplicable || []).filter(function (e) {
|
|
128
|
+
return e.aspectId === aspect.id;
|
|
129
|
+
})[0]
|
|
130
|
+
: null;
|
|
131
|
+
if (!eff && !na) continue;
|
|
132
|
+
found += 1;
|
|
133
|
+
cells.appendChild(aspectNodeCell(node, eff, na, nav));
|
|
134
|
+
}
|
|
135
|
+
if (!found) cells.appendChild(dom.el('p', 'rb-empty', 'This rule lands on no component right now — it verifies nothing. Not a pass.'));
|
|
136
|
+
cellSect.appendChild(cells);
|
|
137
|
+
panel.appendChild(cellSect);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Yg.panelAspect = renderAspectPanel;
|
|
141
|
+
})();
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SHELL-panel — the Node Attestation panel.
|
|
3
|
+
*
|
|
4
|
+
* Turns an opaque checkmark into a citable attestation (§3.2, §3a SHELL-panel). Co-present
|
|
5
|
+
* with any view on node selection, it shows: identity (type + description + mapped globs +
|
|
6
|
+
* live source-file count); the effective-aspects table (per row: aspect, reviewer kind +
|
|
7
|
+
* tier/consensus, cost, status, the channel-provenance, the honest verdict state, and the
|
|
8
|
+
* folded input set behind a VERIFIED green / the reason behind a REFUSED); relations BOTH
|
|
9
|
+
* directions; the when-filtered-OUT (not-applicable) set as its own list; the per-node log
|
|
10
|
+
* WHY timeline; active suppressions with risk flags; and a copy-attestation-digest action.
|
|
11
|
+
* A no-rule node links to the Type Model so "nothing here" is never a terminal shrug. Every
|
|
12
|
+
* state treatment is read from the one shared honest-state model.
|
|
13
|
+
*
|
|
14
|
+
* Browser globals only — reads the already-resolved PortalData; no network, no Node.
|
|
15
|
+
*/
|
|
16
|
+
(function () {
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
var Yg = (window.YgPortal = window.YgPortal || {});
|
|
20
|
+
var dom = Yg.dom;
|
|
21
|
+
Yg.views = Yg.views || {};
|
|
22
|
+
|
|
23
|
+
var CHANNELS = ['', 'own', 'ancestor', 'own-type', 'ancestor-type', 'flow', 'port', 'implied'];
|
|
24
|
+
|
|
25
|
+
function section(title, countLabel) {
|
|
26
|
+
var s = dom.el('section', 'pan-sect');
|
|
27
|
+
var h = dom.el('h5', 'pan-h', title);
|
|
28
|
+
if (countLabel != null) h.appendChild(dom.el('span', 'pan-count', ' · ' + countLabel));
|
|
29
|
+
s.appendChild(h);
|
|
30
|
+
return s;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A plain-language sentence for an aspect's reviewer kind / tier / consensus / cost. */
|
|
34
|
+
function kindPlain(a) {
|
|
35
|
+
if (a.kind === 'llm') {
|
|
36
|
+
var s = 'An AI reviewer';
|
|
37
|
+
if (a.tier) s += ' on the “' + a.tier + '” tier';
|
|
38
|
+
if (a.consensus) s += ', voting ' + a.consensus + '×,';
|
|
39
|
+
s += ' judges this rule';
|
|
40
|
+
s += a.cost === 'billed' ? ' — it may cost a paid API call.' : ' — free (a local reviewer).';
|
|
41
|
+
return s;
|
|
42
|
+
}
|
|
43
|
+
if (a.kind === 'aggregate') return 'A bundle that groups other rules — it judges nothing of its own.';
|
|
44
|
+
return 'A free local script checks this rule mechanically — no AI, no cost.';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function aspectRow(a, nav) {
|
|
48
|
+
var row = dom.el('div', 'pan-asprow');
|
|
49
|
+
var name = dom.el('button', 'pan-aspname mono');
|
|
50
|
+
name.type = 'button';
|
|
51
|
+
name.textContent = a.aspectId;
|
|
52
|
+
var aspectDef = Yg.glossary && Yg.glossary.lookup ? Yg.glossary.lookup('aspect') : null;
|
|
53
|
+
if (aspectDef) name.setAttribute('title', a.aspectId + ' — ' + aspectDef);
|
|
54
|
+
name.addEventListener('click', function () {
|
|
55
|
+
nav({ view: 'rulebook', aspect: a.aspectId });
|
|
56
|
+
});
|
|
57
|
+
row.appendChild(name);
|
|
58
|
+
|
|
59
|
+
var kindLabel =
|
|
60
|
+
a.kind === 'llm'
|
|
61
|
+
? 'LLM' + (a.tier ? ' · ' + a.tier + ' tier' : '') + (a.consensus ? ' · ' + a.consensus + ' opinion' : '') + ' · ' + (a.cost === 'billed' ? 'billed' : 'free')
|
|
62
|
+
: a.kind === 'aggregate'
|
|
63
|
+
? 'aggregating · judges nothing'
|
|
64
|
+
: 'deterministic · free';
|
|
65
|
+
// Plain-language tooltip so the dense kind / tier / cost vocabulary is legible to a non-expert.
|
|
66
|
+
var badge = dom.el('span', 'pan-badge pan-badge-' + a.kind, kindLabel);
|
|
67
|
+
badge.setAttribute('title', kindPlain(a));
|
|
68
|
+
badge.setAttribute('aria-label', kindLabel + ' — ' + kindPlain(a));
|
|
69
|
+
row.appendChild(badge);
|
|
70
|
+
// The provenance / attach channel, with a plain definition (own / ancestor / type / flow / …).
|
|
71
|
+
var chanId = CHANNELS[a.channel] || a.origin || '';
|
|
72
|
+
var chan = dom.el('span', 'pan-chan', chanId);
|
|
73
|
+
var chanDef = chanId && Yg.glossary && Yg.glossary.lookup ? Yg.glossary.lookup(chanId) : null;
|
|
74
|
+
if (chanDef) {
|
|
75
|
+
chan.setAttribute('title', chanDef);
|
|
76
|
+
chan.setAttribute('aria-label', chanId + ': ' + chanDef);
|
|
77
|
+
chan.setAttribute('tabindex', '0');
|
|
78
|
+
}
|
|
79
|
+
row.appendChild(chan);
|
|
80
|
+
var st = a.pairState === 'n/a' ? 'not-applicable' : a.pairState;
|
|
81
|
+
row.appendChild(Yg.states.badge(st));
|
|
82
|
+
|
|
83
|
+
// Drill-through: folded inputs (verified) or the reason (refused).
|
|
84
|
+
if (a.pairState === 'verified' && a.foldedInputs && a.foldedInputs.length) {
|
|
85
|
+
var drill = dom.el('div', 'pan-drill');
|
|
86
|
+
drill.appendChild(dom.el('div', 'pan-drill-h', 'This green attests these exact bytes:'));
|
|
87
|
+
for (var i = 0; i < a.foldedInputs.length; i += 1) {
|
|
88
|
+
var c = dom.el('div', 'pan-check');
|
|
89
|
+
c.appendChild(dom.el('span', 'pan-check-c', '✓'));
|
|
90
|
+
c.appendChild(dom.el('span', 'mono', a.foldedInputs[i]));
|
|
91
|
+
drill.appendChild(c);
|
|
92
|
+
}
|
|
93
|
+
// The tier/secrets-overlay caveat is about the LLM reviewer's tier ONLY.
|
|
94
|
+
// A deterministic check has no tier/reviewer, so the note is irrelevant
|
|
95
|
+
// (and misleading) there — gate it to LLM aspects.
|
|
96
|
+
if (a.kind === 'llm') {
|
|
97
|
+
drill.appendChild(dom.el('div', 'pan-caveat', 'Tier config may be locally overridden via a secrets overlay; only the tier name is hashed. Shown values come from committed config only.'));
|
|
98
|
+
}
|
|
99
|
+
row.appendChild(drill);
|
|
100
|
+
} else if ((a.pairState === 'refused' || a.pairState === 'warning') && a.reason) {
|
|
101
|
+
// A refusal — blocking (enforced, 'refused') OR non-blocking (advisory, 'warning') — must
|
|
102
|
+
// cite the reviewer's reason here, the canonical per-node attestation surface. An advisory
|
|
103
|
+
// refusal additionally states it does not block, so a warning reads honestly on its own.
|
|
104
|
+
row.appendChild(dom.el('blockquote', 'pan-reason', a.reason));
|
|
105
|
+
if (a.pairState === 'warning') {
|
|
106
|
+
row.appendChild(dom.el('div', 'pan-caveat', 'Advisory rule — non-blocking signal, not a pass and not a clean check.'));
|
|
107
|
+
}
|
|
108
|
+
} else if (a.pairState === 'unverified') {
|
|
109
|
+
row.appendChild(dom.el('div', 'pan-caveat', 'Inputs changed or were never checked — not a stale pass. Run the reviewer to confirm.'));
|
|
110
|
+
}
|
|
111
|
+
return row;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function relList(title, rels, dir, nav) {
|
|
115
|
+
if (!rels || !rels.length) return null;
|
|
116
|
+
var s = section(title, String(rels.length));
|
|
117
|
+
var ul = dom.el('ul', 'pan-rels');
|
|
118
|
+
for (var i = 0; i < rels.length; i += 1) {
|
|
119
|
+
var r = rels[i];
|
|
120
|
+
var target = dir === 'out' ? r.target : r.source;
|
|
121
|
+
var li = dom.el('li', 'pan-rel');
|
|
122
|
+
li.appendChild(dom.el('span', 'pan-reltype', r.type));
|
|
123
|
+
var link = dom.el('button', 'pan-rellink mono');
|
|
124
|
+
link.type = 'button';
|
|
125
|
+
link.textContent = target;
|
|
126
|
+
link.addEventListener('click', function (t) {
|
|
127
|
+
return function () {
|
|
128
|
+
nav({ view: 'tree', node: t });
|
|
129
|
+
};
|
|
130
|
+
}(target));
|
|
131
|
+
li.appendChild(link);
|
|
132
|
+
ul.appendChild(li);
|
|
133
|
+
}
|
|
134
|
+
s.appendChild(ul);
|
|
135
|
+
return s;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A short, stable attestation digest a reviewer can paste (no datetimes — not recorded).
|
|
140
|
+
* Pinned to the exact committed state it attests: the COMMITTED-lock hash and the git commit
|
|
141
|
+
* ref (from PortalData.meta via the facade). With those two pins a third party can reproduce
|
|
142
|
+
* the verdict set the digest claims; absent a commit ref it states "no commit ref" rather
|
|
143
|
+
* than fabricating one. The per-aspect rows carry each effective aspect's honest pair state,
|
|
144
|
+
* so the digest can never read more-green than the live panel.
|
|
145
|
+
*/
|
|
146
|
+
function attestationDigest(node, meta) {
|
|
147
|
+
var m = meta || {};
|
|
148
|
+
var lines = [
|
|
149
|
+
'attestation: ' + node.path + ' (' + node.type + ')',
|
|
150
|
+
'state: ' + node.state + (node.fresh ? ' (source changed since last reviewer pass — not a stale pass)' : ''),
|
|
151
|
+
'commit: ' + (m.commitRef || 'no commit ref'),
|
|
152
|
+
'lock: ' + (m.lockHash || 'no committed lock'),
|
|
153
|
+
];
|
|
154
|
+
var eff = node.effectiveAspects || [];
|
|
155
|
+
for (var i = 0; i < eff.length; i += 1) {
|
|
156
|
+
lines.push(' ' + eff[i].aspectId + ' [' + eff[i].kind + '] ' + eff[i].pairState);
|
|
157
|
+
}
|
|
158
|
+
return lines.join('\n');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Render the shared inspector panel. `route.node` opens a NODE's attestation (below);
|
|
163
|
+
* `route.aspect` opens a RULE's detail (delegated to the panel-aspect module — the aspect-side
|
|
164
|
+
* mirror). `ctx.navigate` routes the panel's links. When the route names neither, the panel
|
|
165
|
+
* closes. Returns nothing.
|
|
166
|
+
*/
|
|
167
|
+
Yg.views.panel = function (panel, route, data, ctx) {
|
|
168
|
+
var nav = ctx && ctx.navigate ? ctx.navigate : function () {};
|
|
169
|
+
dom.clear(panel);
|
|
170
|
+
if (route && route.aspect && !route.node && Yg.panelAspect) {
|
|
171
|
+
panel.classList.add('open');
|
|
172
|
+
Yg.panelAspect(panel, route, data, nav);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (!route || !route.node) {
|
|
176
|
+
panel.classList.remove('open');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
panel.classList.add('open');
|
|
180
|
+
|
|
181
|
+
var node = (data.nodes || []).filter(function (n) {
|
|
182
|
+
return n.path === route.node;
|
|
183
|
+
})[0];
|
|
184
|
+
if (!node) {
|
|
185
|
+
panel.appendChild(dom.el('h3', 'panel-title', route.node));
|
|
186
|
+
panel.appendChild(dom.el('p', 'panel-sub', 'Unknown node.'));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Identity.
|
|
191
|
+
var meta = (data && data.meta) || {};
|
|
192
|
+
var head = dom.el('div', 'pan-head');
|
|
193
|
+
// A close affordance — essential when the panel is a right-overlay sheet on a narrow window,
|
|
194
|
+
// harmless as an extra dismiss on a wide one. Drops the node from the route (panel closes),
|
|
195
|
+
// staying on the current view.
|
|
196
|
+
var close = dom.el('button', 'pan-close', '×');
|
|
197
|
+
close.type = 'button';
|
|
198
|
+
close.setAttribute('aria-label', 'Close panel');
|
|
199
|
+
close.addEventListener('click', function () {
|
|
200
|
+
nav({ view: route.view || 'overview' });
|
|
201
|
+
});
|
|
202
|
+
head.appendChild(close);
|
|
203
|
+
var title = dom.el('div', 'pan-title');
|
|
204
|
+
title.appendChild(Yg.states.badge(node.state));
|
|
205
|
+
title.appendChild(dom.el('b', null, node.name || node.path));
|
|
206
|
+
head.appendChild(title);
|
|
207
|
+
head.appendChild(dom.el('div', 'pan-path mono', node.path));
|
|
208
|
+
head.appendChild(dom.el('div', 'pan-meta', node.type + ' · ' + node.sourceFileCount + ' source files'));
|
|
209
|
+
if (node.description) head.appendChild(dom.el('p', 'pan-desc', node.description));
|
|
210
|
+
|
|
211
|
+
// The file-aware loop: a node whose source changed since the last reviewer pass reads
|
|
212
|
+
// "unverified" here — a banner makes the touched-not-a-pass status explicit on the panel.
|
|
213
|
+
if (node.fresh) {
|
|
214
|
+
var freshBox = dom.el('div', 'pan-fresh ' + Yg.states.cssClass('unverified'));
|
|
215
|
+
freshBox.appendChild(Yg.states.badge('unverified'));
|
|
216
|
+
freshBox.appendChild(dom.el('span', null, 'Source changed since the last reviewer pass — this reads unverified, not a pass. Refresh re-runs the free checks; Approve re-confirms.'));
|
|
217
|
+
head.appendChild(freshBox);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Provenance pins — the committed-lock hash + the git commit ref the digest attests to.
|
|
221
|
+
var prov = dom.el('div', 'pan-prov mono');
|
|
222
|
+
prov.appendChild(dom.el('span', 'pan-prov-k', 'commit ' + (meta.commitRef ? meta.commitRef.slice(0, 12) : 'none')));
|
|
223
|
+
prov.appendChild(dom.el('span', 'pan-prov-k', 'lock ' + (meta.lockHash ? meta.lockHash.slice(0, 12) : 'none')));
|
|
224
|
+
head.appendChild(prov);
|
|
225
|
+
|
|
226
|
+
var copy = dom.el('button', 'pan-copy');
|
|
227
|
+
copy.type = 'button';
|
|
228
|
+
copy.textContent = 'copy attestation digest';
|
|
229
|
+
copy.setAttribute('aria-label', 'Copy this node’s attestation digest (pins the commit ref and lock hash) to the clipboard');
|
|
230
|
+
copy.addEventListener('click', function () {
|
|
231
|
+
var text = attestationDigest(node, meta);
|
|
232
|
+
try {
|
|
233
|
+
if (navigator && navigator.clipboard) navigator.clipboard.writeText(text);
|
|
234
|
+
} catch (_e) {
|
|
235
|
+
/* clipboard may be unavailable (file://) — degrade silently */
|
|
236
|
+
}
|
|
237
|
+
copy.textContent = 'copied';
|
|
238
|
+
});
|
|
239
|
+
head.appendChild(copy);
|
|
240
|
+
panel.appendChild(head);
|
|
241
|
+
|
|
242
|
+
// Effective aspects.
|
|
243
|
+
var eff = node.effectiveAspects || [];
|
|
244
|
+
if (eff.length) {
|
|
245
|
+
var aspSect = section('Effective aspects', String(eff.length));
|
|
246
|
+
for (var i = 0; i < eff.length; i += 1) aspSect.appendChild(aspectRow(eff[i], nav));
|
|
247
|
+
panel.appendChild(aspSect);
|
|
248
|
+
} else {
|
|
249
|
+
// A no-rule node — link to the Type Model so "nothing here" is never terminal.
|
|
250
|
+
var noRule = section('No effective rule here');
|
|
251
|
+
var p = dom.el('p', 'pan-norule', 'Nothing is checking this node — unguarded, not approved. ');
|
|
252
|
+
var link = dom.el('button', 'pan-norule-link');
|
|
253
|
+
link.type = 'button';
|
|
254
|
+
link.textContent = "see what its type could enforce →";
|
|
255
|
+
link.addEventListener('click', function () {
|
|
256
|
+
nav({ view: 'types' });
|
|
257
|
+
});
|
|
258
|
+
p.appendChild(link);
|
|
259
|
+
noRule.appendChild(p);
|
|
260
|
+
panel.appendChild(noRule);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Relations both directions.
|
|
264
|
+
var depOn = relList('Depends on', node.relationsOut, 'out', nav);
|
|
265
|
+
if (depOn) panel.appendChild(depOn);
|
|
266
|
+
var depBy = relList('Depended on by', node.relationsIn, 'in', nav);
|
|
267
|
+
if (depBy) panel.appendChild(depBy);
|
|
268
|
+
|
|
269
|
+
// When-filtered-out (not-applicable) — its own honest list.
|
|
270
|
+
if (node.notApplicable && node.notApplicable.length) {
|
|
271
|
+
var na = section('Not applicable here (when-filtered)', String(node.notApplicable.length));
|
|
272
|
+
var naUl = dom.el('ul', 'pan-na');
|
|
273
|
+
for (var j = 0; j < node.notApplicable.length; j += 1) {
|
|
274
|
+
var item = node.notApplicable[j];
|
|
275
|
+
var li = dom.el('li', 'pan-na-item');
|
|
276
|
+
li.appendChild(Yg.states.badge('not-applicable'));
|
|
277
|
+
li.appendChild(dom.el('span', 'mono', item.aspectId));
|
|
278
|
+
li.appendChild(dom.el('span', 'pan-na-why', item.why));
|
|
279
|
+
naUl.appendChild(li);
|
|
280
|
+
}
|
|
281
|
+
na.appendChild(naUl);
|
|
282
|
+
panel.appendChild(na);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Suppressions with risk flags.
|
|
286
|
+
if (node.suppressions && node.suppressions.length) {
|
|
287
|
+
var supSect = section('Suppressions on this node', String(node.suppressions.length));
|
|
288
|
+
for (var k = 0; k < node.suppressions.length; k += 1) {
|
|
289
|
+
var sup = node.suppressions[k];
|
|
290
|
+
var srow = dom.el('div', 'pan-supp');
|
|
291
|
+
srow.appendChild(Yg.states.badge('suppressed'));
|
|
292
|
+
srow.appendChild(dom.el('span', 'mono', sup.file + ':' + sup.line));
|
|
293
|
+
srow.appendChild(dom.el('span', 'pan-supp-asp mono', sup.aspectId));
|
|
294
|
+
if (sup.risk) srow.appendChild(dom.el('span', 'pan-risk', sup.risk));
|
|
295
|
+
srow.appendChild(dom.el('div', 'pan-supp-reason', '"' + sup.reason + '" — waiver, not a pass.'));
|
|
296
|
+
supSect.appendChild(srow);
|
|
297
|
+
}
|
|
298
|
+
panel.appendChild(supSect);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Log WHY timeline (the only datetimed evidence).
|
|
302
|
+
if (node.log && node.log.length) {
|
|
303
|
+
var logSect = section('Why (log)', String(node.log.length));
|
|
304
|
+
for (var l = 0; l < node.log.length; l += 1) {
|
|
305
|
+
var entry = node.log[l];
|
|
306
|
+
var le = dom.el('div', 'pan-log');
|
|
307
|
+
le.appendChild(dom.el('div', 'pan-log-when mono', entry.when));
|
|
308
|
+
le.appendChild(dom.el('div', 'pan-log-body', entry.body));
|
|
309
|
+
logSect.appendChild(le);
|
|
310
|
+
}
|
|
311
|
+
panel.appendChild(logSect);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
})();
|