create-principles-disciple 1.145.35 → 1.145.37
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/_release/product-identity.json +2 -2
- package/console/dist/server/index.js +4 -0
- package/console/dist/server/models/ApprovalsConsoleModel.d.ts +22 -2
- package/console/dist/server/models/ApprovalsConsoleModel.js +56 -16
- package/console/dist/server/models/ApprovalsGroupedConsoleModel.d.ts +8 -0
- package/console/dist/server/models/ApprovalsGroupedConsoleModel.js +3 -2
- package/console/dist/server/models/approval-review-material.d.ts +15 -0
- package/console/dist/server/models/approval-review-material.js +32 -0
- package/console/dist/server/routes/approvals.js +32 -7
- package/console/dist/server/utils/response.js +8 -0
- package/console/dist/ui/api.d.ts +8 -2
- package/console/dist/ui/api.js +8 -4
- package/console/dist/ui/i18n/en.json +98 -7
- package/console/dist/ui/i18n/zh-CN.json +98 -7
- package/console/dist/ui/pages/focus/ApprovalSubjectReading.d.ts +24 -0
- package/console/dist/ui/pages/focus/ApprovalSubjectReading.js +133 -0
- package/console/dist/ui/pages/focus/FocusPage.d.ts +5 -26
- package/console/dist/ui/pages/focus/FocusPage.js +166 -459
- package/console/dist/ui/pages/focus/OwnerWorkbench.d.ts +19 -0
- package/console/dist/ui/pages/focus/OwnerWorkbench.js +75 -0
- package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.d.ts +4 -1
- package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.js +7 -3
- package/console/dist/ui/pages/focus/focus-validation.js +31 -0
- package/console/dist/ui/pages/principles/PrincipleDetailPage.js +110 -86
- package/console/dist/ui/pages/principles/PrinciplesPage.js +39 -30
- package/console/dist/ui/utils/approval-review.d.ts +30 -0
- package/console/dist/ui/utils/approval-review.js +50 -0
- package/console/dist/ui/utils/reading-layout.d.ts +1 -0
- package/console/dist/ui/utils/reading-layout.js +1 -1
- package/console/dist/ui/utils/validators.d.ts +4 -0
- package/console/dist/ui/utils/validators.js +10 -2
- package/console/dist/web/assets/app.css +119 -11
- package/console/dist/web/assets/app.js +40 -30
- package/core/dist/runtime-v2/activation/activation-types.d.ts +12 -2
- package/core/dist/runtime-v2/activation/activation-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/activation-types.js.map +1 -1
- package/core/dist/runtime-v2/activation/approval-queue.d.ts +3 -2
- package/core/dist/runtime-v2/activation/approval-queue.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/approval-queue.js +6 -4
- package/core/dist/runtime-v2/activation/approval-queue.js.map +1 -1
- package/core/dist/runtime-v2/activation/approval-review-guard.d.ts +26 -0
- package/core/dist/runtime-v2/activation/approval-review-guard.d.ts.map +1 -0
- package/core/dist/runtime-v2/activation/approval-review-guard.js +25 -0
- package/core/dist/runtime-v2/activation/approval-review-guard.js.map +1 -0
- package/core/dist/runtime-v2/activation/memory-approval-store.d.ts +6 -2
- package/core/dist/runtime-v2/activation/memory-approval-store.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/memory-approval-store.js +26 -2
- package/core/dist/runtime-v2/activation/memory-approval-store.js.map +1 -1
- package/core/dist/runtime-v2/activation/sqlite-approval-store.d.ts +6 -2
- package/core/dist/runtime-v2/activation/sqlite-approval-store.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/sqlite-approval-store.js +59 -2
- package/core/dist/runtime-v2/activation/sqlite-approval-store.js.map +1 -1
- package/core/dist/runtime-v2/diagnostician-association-shadow.d.ts +24 -0
- package/core/dist/runtime-v2/diagnostician-association-shadow.d.ts.map +1 -0
- package/core/dist/runtime-v2/diagnostician-association-shadow.js +84 -0
- package/core/dist/runtime-v2/diagnostician-association-shadow.js.map +1 -0
- package/core/dist/runtime-v2/diagnostician-association-validation.d.ts +18 -0
- package/core/dist/runtime-v2/diagnostician-association-validation.d.ts.map +1 -0
- package/core/dist/runtime-v2/diagnostician-association-validation.js +47 -0
- package/core/dist/runtime-v2/diagnostician-association-validation.js.map +1 -0
- package/core/dist/runtime-v2/diagnostician-output.d.ts +123 -0
- package/core/dist/runtime-v2/diagnostician-output.d.ts.map +1 -1
- package/core/dist/runtime-v2/diagnostician-output.js +41 -0
- package/core/dist/runtime-v2/diagnostician-output.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +2 -0
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +1 -0
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/internalization/diag-router-runner.d.ts +12 -1
- package/core/dist/runtime-v2/internalization/diag-router-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/diag-router-runner.js +50 -2
- package/core/dist/runtime-v2/internalization/diag-router-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/router-context-evidence.d.ts +45 -0
- package/core/dist/runtime-v2/internalization/router-context-evidence.d.ts.map +1 -0
- package/core/dist/runtime-v2/internalization/router-context-evidence.js +64 -0
- package/core/dist/runtime-v2/internalization/router-context-evidence.js.map +1 -0
- package/core/package.json +1 -1
- package/package.json +1 -1
- package/pd-cli/package.json +1 -1
- package/plugin/dist/bundle.js +395 -395
- package/release-manager/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export type OwnerWorkItemKind = 'prompt' | 'rule' | 'promotion' | 'revision' | 'validation' | 'binding' | 'archive' | 'approval' | 'unknown';
|
|
3
|
+
export interface OwnerWorkItem {
|
|
4
|
+
key: string;
|
|
5
|
+
title: string;
|
|
6
|
+
kind: OwnerWorkItemKind;
|
|
7
|
+
summary: string;
|
|
8
|
+
reference?: string;
|
|
9
|
+
content: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
/** Presentation only: list grouping never changes an action's identity or authority. */
|
|
12
|
+
export declare function OwnerWorkbench({ items, completed, uncertain }: {
|
|
13
|
+
items: OwnerWorkItem[];
|
|
14
|
+
completed: {
|
|
15
|
+
key: string;
|
|
16
|
+
message: string;
|
|
17
|
+
} | null;
|
|
18
|
+
uncertain: boolean;
|
|
19
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { useSearchParams } from 'react-router-dom';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { Archive, CircleHelp, ClipboardCheck, Code2, FilePenLine, FlaskConical, Link2, MessageSquareText, ShieldCheck } from 'lucide-react';
|
|
6
|
+
// Type describes the decision, not an inferred urgency or permission.
|
|
7
|
+
const KIND_PRESENTATION = {
|
|
8
|
+
prompt: { icon: MessageSquareText, label: 'promptReview', style: 'border-gov/30 bg-gov/10 text-gov' },
|
|
9
|
+
rule: { icon: Code2, label: 'ruleReview', style: 'border-ink-3/30 bg-ink-3/10 text-ink-2' },
|
|
10
|
+
promotion: { icon: ShieldCheck, label: 'rulePromotion', style: 'border-amber/30 bg-amber/10 text-ink-2' },
|
|
11
|
+
revision: { icon: FilePenLine, label: 'revisionDecision', style: 'border-amber/30 text-ink-2' },
|
|
12
|
+
validation: { icon: FlaskConical, label: 'validationReview', style: 'border-gov/30 text-gov' },
|
|
13
|
+
binding: { icon: Link2, label: 'bindingReview', style: 'border-gov/30 text-gov' },
|
|
14
|
+
archive: { icon: Archive, label: 'archiveReview', style: 'border-line bg-surface text-ink-3' },
|
|
15
|
+
approval: { icon: ClipboardCheck, label: 'approvalMaterialReview', style: 'border-line text-ink-2' },
|
|
16
|
+
unknown: { icon: CircleHelp, label: 'unknownReview', style: 'border-line text-ink-3' },
|
|
17
|
+
};
|
|
18
|
+
function WorkItemType({ kind }) {
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
const { icon: Icon, label, style } = KIND_PRESENTATION[kind];
|
|
21
|
+
return _jsxs("span", { "data-testid": "owner-work-type", "data-kind": kind, className: `inline-flex max-w-full items-center gap-1.5 rounded-[4px] border px-2 py-1 text-[11px] font-medium leading-relaxed ${style}`, children: [_jsx(Icon, { "aria-hidden": "true", className: "h-3.5 w-3.5 shrink-0", strokeWidth: 1.75 }), t(`pages.focus.workbench.${label}`)] });
|
|
22
|
+
}
|
|
23
|
+
/** Presentation only: list grouping never changes an action's identity or authority. */
|
|
24
|
+
export function OwnerWorkbench({ items, completed, uncertain }) {
|
|
25
|
+
const { t } = useTranslation();
|
|
26
|
+
const [params, setParams] = useSearchParams();
|
|
27
|
+
const selectedKey = params.get('review') ?? '';
|
|
28
|
+
const selected = items.find(item => item.key === selectedKey);
|
|
29
|
+
const heading = useRef(null);
|
|
30
|
+
const list = useRef(null);
|
|
31
|
+
const lastSelected = useRef('');
|
|
32
|
+
const listScroll = useRef(0);
|
|
33
|
+
// A deep link can arrive before the items do. `focusKey` only becomes the
|
|
34
|
+
// selection once the item exists, so the effect re-runs when it appears
|
|
35
|
+
// without stealing focus on every data refresh.
|
|
36
|
+
const focusKey = selected ? selectedKey : '';
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (focusKey) {
|
|
39
|
+
heading.current?.focus({ preventScroll: true });
|
|
40
|
+
const reading = heading.current?.closest('[data-testid="owner-work-reading"]');
|
|
41
|
+
if (reading && (reading.getBoundingClientRect().top < 0 || reading.getBoundingClientRect().top > window.innerHeight - 100))
|
|
42
|
+
reading.scrollIntoView({ block: 'start' });
|
|
43
|
+
const current = list.current?.querySelector('[aria-current="true"]');
|
|
44
|
+
if (current && list.current && list.current.scrollHeight > list.current.clientHeight) {
|
|
45
|
+
list.current.scrollTop = Math.max(0, current.offsetTop - list.current.offsetTop - 24);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else if (selectedKey) {
|
|
49
|
+
heading.current?.focus({ preventScroll: true });
|
|
50
|
+
}
|
|
51
|
+
else if (lastSelected.current && list.current) {
|
|
52
|
+
list.current.scrollTop = listScroll.current;
|
|
53
|
+
const previous = Array.from(list.current.querySelectorAll('[data-item-key]')).find(button => button.dataset.itemKey === lastSelected.current);
|
|
54
|
+
previous?.focus({ preventScroll: true });
|
|
55
|
+
if (previous && (previous.getBoundingClientRect().top < 0 || previous.getBoundingClientRect().bottom > window.innerHeight))
|
|
56
|
+
previous.scrollIntoView({ block: 'nearest' });
|
|
57
|
+
}
|
|
58
|
+
}, [focusKey, selectedKey]);
|
|
59
|
+
function choose(key) {
|
|
60
|
+
if (!key && selectedKey) {
|
|
61
|
+
lastSelected.current = selectedKey;
|
|
62
|
+
listScroll.current = list.current?.scrollTop ?? 0;
|
|
63
|
+
}
|
|
64
|
+
const next = new URLSearchParams(params);
|
|
65
|
+
if (key)
|
|
66
|
+
next.set('review', key);
|
|
67
|
+
else
|
|
68
|
+
next.delete('review');
|
|
69
|
+
setParams(next, { preventScrollReset: true });
|
|
70
|
+
}
|
|
71
|
+
const completedHere = completed?.key === selectedKey ? completed : null;
|
|
72
|
+
const selectedIndex = items.findIndex(item => item.key === selectedKey);
|
|
73
|
+
const nextItem = selectedIndex >= 0 ? items[selectedIndex + 1] : undefined;
|
|
74
|
+
return _jsxs("section", { className: "owner-workbench", "data-testid": "owner-workbench", children: [_jsxs("nav", { ref: list, tabIndex: -1, className: `owner-work-list ${selected ? 'owner-work-list-selected' : ''}`, "aria-label": t('pages.focus.workbench.list'), children: [_jsxs("div", { className: "mb-3 flex items-baseline justify-between gap-3", children: [_jsx("h2", { className: "text-[15px] font-medium text-ink", children: t('pages.focus.workbench.list') }), _jsx("span", { className: "text-[12px] text-ink-4", children: t('pages.focus.workbench.count', { count: items.length }) })] }), items.length === 0 && _jsx("p", { className: "text-sm leading-relaxed text-ink-3", children: t(uncertain ? 'pages.focus.workbench.uncertain' : 'pages.focus.workbench.empty') }), _jsx("ol", { className: "space-y-2", children: items.map(item => _jsx("li", { children: _jsxs("button", { type: "button", onClick: () => choose(item.key), "aria-current": selected?.key === item.key ? 'true' : undefined, "data-testid": "owner-work-item", "data-item-key": item.key, className: `w-full rounded-[6px] border px-4 py-4 text-left transition-colors focus-visible:outline-2 focus-visible:outline-gov ${selected?.key === item.key ? 'border-gov bg-gov/5' : 'border-line bg-panel hover:border-gov/50'}`, children: [_jsx("span", { className: "mb-3 block", children: _jsx(WorkItemType, { kind: item.kind }) }), _jsx("span", { className: "block text-[14px] font-medium leading-relaxed text-ink break-words", children: item.title }), _jsx("span", { className: "mt-2 block text-[12.5px] leading-relaxed text-ink-3 line-clamp-2", children: item.summary }), item.reference && items.some(other => other.key !== item.key && other.title === item.title && other.summary === item.summary) && _jsx("span", { className: "mt-2 block break-all font-mono text-[11px] text-ink-4", children: item.reference })] }) }, item.key)) })] }), _jsx("div", { className: "owner-work-reading min-w-0", "data-testid": "owner-work-reading", children: selected ? _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "mb-5 text-[13px] text-gov underline underline-offset-4", onClick: () => choose(''), children: t('pages.focus.workbench.back') }), _jsx("div", { className: "mb-3", children: _jsx(WorkItemType, { kind: selected.kind }) }), _jsx("h2", { ref: heading, tabIndex: -1, className: "mb-6 text-[22px] font-semibold leading-snug text-ink break-words", children: selected.title }), _jsx("div", { children: selected.content }, selected.key), _jsxs("footer", { className: "mt-7 border-t border-line pt-5", children: [_jsx("p", { className: "mb-3 text-[12.5px] text-ink-3", children: t('pages.focus.workbench.readingEnd') }), nextItem && _jsx("button", { type: "button", className: "text-[13px] text-gov underline underline-offset-4", onClick: () => choose(nextItem.key), children: t('pages.focus.workbench.next') })] })] }) : _jsxs("div", { className: "py-10 text-ink-3", children: [_jsx("h2", { ref: heading, tabIndex: -1, className: "mb-3 text-[22px] font-medium text-ink", children: t(completedHere ? 'pages.focus.workbench.completed' : selectedKey ? 'pages.focus.workbench.notCurrent' : 'pages.focus.workbench.startTitle') }), _jsx("p", { role: completedHere ? 'status' : undefined, className: "max-w-[440px] text-[14px] leading-relaxed", children: completedHere?.message ?? t(selectedKey ? 'pages.focus.workbench.notCurrentBody' : 'pages.focus.workbench.startBody') }), items[0] && _jsx("button", { type: "button", className: "mt-6 rounded-[4px] border border-gov bg-gov px-4 py-2.5 text-sm text-paper", onClick: () => choose(items[0].key), children: t(completedHere ? 'pages.focus.workbench.next' : 'pages.focus.workbench.start') })] }) })] });
|
|
75
|
+
}
|
|
@@ -28,4 +28,7 @@ export declare function canOfferSourceCorrection(item: Pick<RuleBindingQueueItem
|
|
|
28
28
|
* `unknown` previously fell through to raw English in the zh-CN group header). */
|
|
29
29
|
export declare const PAIN_SOURCE_KEY: Record<string, string>;
|
|
30
30
|
/** Inline in Governance Focus; assignment, rule approval and live promotion remain separate. */
|
|
31
|
-
export declare function RuleCandidateBindingPanel(
|
|
31
|
+
export declare function RuleCandidateBindingPanel({ candidateId, onChanged }?: {
|
|
32
|
+
candidateId?: string;
|
|
33
|
+
onChanged?: () => void;
|
|
34
|
+
}): import("react").JSX.Element | null;
|
|
@@ -139,7 +139,7 @@ function stageKindLabel(t, stageKind) {
|
|
|
139
139
|
return key ? t(key) : stageKind;
|
|
140
140
|
}
|
|
141
141
|
/** Inline in Governance Focus; assignment, rule approval and live promotion remain separate. */
|
|
142
|
-
export function RuleCandidateBindingPanel() {
|
|
142
|
+
export function RuleCandidateBindingPanel({ candidateId, onChanged } = {}) {
|
|
143
143
|
const { t } = useTranslation();
|
|
144
144
|
const [queue, setQueue] = useState(null);
|
|
145
145
|
const [review, setReview] = useState(null);
|
|
@@ -176,11 +176,15 @@ export function RuleCandidateBindingPanel() {
|
|
|
176
176
|
setError([result.error, result.nextAction].filter(Boolean).join(' ') || t('pages.focus.ruleBinding.unavailable'));
|
|
177
177
|
setBusy(false);
|
|
178
178
|
}
|
|
179
|
+
useEffect(() => { if (candidateId)
|
|
180
|
+
void load(candidateId); }, [candidateId]);
|
|
179
181
|
function closeReview() { setOpenCandidateId(''); setReview(null); setSearch(''); setReason(''); }
|
|
180
182
|
async function refreshQueue() {
|
|
181
183
|
const result = await fetchRuleBindingQueue();
|
|
182
|
-
if (result.success)
|
|
184
|
+
if (result.success) {
|
|
183
185
|
setQueue(result.data);
|
|
186
|
+
onChanged?.();
|
|
187
|
+
}
|
|
184
188
|
}
|
|
185
189
|
async function decide(action) {
|
|
186
190
|
if (!review)
|
|
@@ -262,7 +266,7 @@ export function RuleCandidateBindingPanel() {
|
|
|
262
266
|
}
|
|
263
267
|
if ((queue === null || queue.items.length === 0) && !error)
|
|
264
268
|
return null;
|
|
265
|
-
const groups = groupRuleBindingQueue(queue?.items ?? []);
|
|
269
|
+
const groups = groupRuleBindingQueue((queue?.items ?? []).filter(item => candidateId === undefined || item.candidateId === candidateId));
|
|
266
270
|
const visibleGroups = showAllGroups ? groups : groups.slice(0, COLLAPSED_GROUP_LIMIT);
|
|
267
271
|
const bound = review?.currentBinding?.action === 'bind';
|
|
268
272
|
return _jsxs("section", { className: "mb-7 rounded border border-line p-4", "data-testid": "rule-binding-panel", "aria-label": t('pages.focus.ruleBinding.title'), children: [_jsx("h2", { className: "text-sm font-medium", children: t('pages.focus.ruleBinding.title') }), _jsx("p", { className: "mt-2 text-sm text-ink-3", children: t('pages.focus.ruleBinding.separate') }), queue && _jsx("p", { className: "mt-2 text-[13px] text-ink-3", role: "status", "data-testid": "rule-binding-counts", children: t('pages.focus.ruleBinding.countsLine', { candidates: queue.summary.candidates, painGroups: queue.summary.painGroups, bound: queue.summary.bound, unlinked: queue.summary.unlinked, blocked: queue.summary.blocked }) }), queue?.truncated && _jsx("p", { className: "mt-1 text-[12.5px] text-amber", children: t('pages.focus.ruleBinding.truncatedNote', { count: queue.items.length }) }), error && _jsx("p", { role: "alert", className: "my-2 text-amber", children: error }), message && _jsx("p", { role: "status", className: "my-2", children: message }), visibleGroups.map(group => _jsxs("div", { className: "mt-4", "data-testid": "rule-binding-pain-group", children: [_jsxs("div", { className: "border-l-2 border-line pl-3", children: [_jsxs("div", { className: "text-[13px] text-ink-2 leading-relaxed", children: [_jsx("span", { className: "font-medium text-ink", children: t('pages.focus.ruleBinding.groupPrefix') }), ' ', group.pain.text
|
|
@@ -36,12 +36,19 @@ function validateApprovalGroup(raw) {
|
|
|
36
36
|
typeof r.status !== "string") {
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
|
+
let recordRevisionReviewUnavailable;
|
|
40
|
+
if (Object.hasOwn(r, "revisionReviewUnavailable")) {
|
|
41
|
+
if (typeof r.revisionReviewUnavailable !== "boolean")
|
|
42
|
+
return null;
|
|
43
|
+
recordRevisionReviewUnavailable = r.revisionReviewUnavailable;
|
|
44
|
+
}
|
|
39
45
|
validRecords.push({
|
|
40
46
|
id: r.id,
|
|
41
47
|
artifactId: r.artifactId,
|
|
42
48
|
channel: r.channel,
|
|
43
49
|
createdAt: r.createdAt,
|
|
44
50
|
status: r.status,
|
|
51
|
+
...(recordRevisionReviewUnavailable !== undefined ? { revisionReviewUnavailable: recordRevisionReviewUnavailable } : {}),
|
|
45
52
|
});
|
|
46
53
|
}
|
|
47
54
|
// Wave 7: candidateDescription is optional — present when backend could
|
|
@@ -85,6 +92,28 @@ function validateApprovalGroup(raw) {
|
|
|
85
92
|
return null;
|
|
86
93
|
artifactUnavailable = unavailable;
|
|
87
94
|
}
|
|
95
|
+
let revisionReviewUnavailable;
|
|
96
|
+
if (Object.hasOwn(raw, 'revisionReviewUnavailable')) {
|
|
97
|
+
const { revisionReviewUnavailable: unavailable } = raw;
|
|
98
|
+
if (typeof unavailable !== 'boolean')
|
|
99
|
+
return null;
|
|
100
|
+
revisionReviewUnavailable = unavailable;
|
|
101
|
+
}
|
|
102
|
+
let revisionReview;
|
|
103
|
+
if (Object.hasOwn(raw, 'revisionReview')) {
|
|
104
|
+
const review = raw.revisionReview;
|
|
105
|
+
if (!isRecord(review) || typeof review.oldStatement !== 'string' || typeof review.newStatement !== 'string' || !isRecord(review.intentFields))
|
|
106
|
+
return null;
|
|
107
|
+
const intentFields = {};
|
|
108
|
+
for (const [key, value] of Object.entries(review.intentFields)) {
|
|
109
|
+
if (typeof value !== 'string')
|
|
110
|
+
return null;
|
|
111
|
+
intentFields[key] = value;
|
|
112
|
+
}
|
|
113
|
+
revisionReview = { oldStatement: review.oldStatement, newStatement: review.newStatement, intentFields,
|
|
114
|
+
...(typeof review.artifactId === 'string' ? { artifactId: review.artifactId } : {}),
|
|
115
|
+
...(typeof review.artifactDigest === 'string' && /^sha256:[a-f0-9]{64}$/.test(review.artifactDigest) ? { artifactDigest: review.artifactDigest } : {}) };
|
|
116
|
+
}
|
|
88
117
|
return {
|
|
89
118
|
principleId,
|
|
90
119
|
principleTitle,
|
|
@@ -93,6 +122,8 @@ function validateApprovalGroup(raw) {
|
|
|
93
122
|
fitsPromptBudget,
|
|
94
123
|
promptBudgetTierFit,
|
|
95
124
|
artifactUnavailable,
|
|
125
|
+
revisionReviewUnavailable,
|
|
126
|
+
revisionReview,
|
|
96
127
|
records: validRecords,
|
|
97
128
|
};
|
|
98
129
|
}
|