@sparkelf/dsh-dataops-managed 0.3.20 → 0.3.21

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.
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Whether the stored DataOps credential is still accepted, observed rather than guessed.
3
+ *
4
+ * Every DataOps call this plugin makes already knows the answer: a 401 is the server saying the
5
+ * caller's session has ended. Those answers used to be swallowed at each call site — each one
6
+ * returned `undefined` and moved on — which turned "your session expired" into "this feature
7
+ * happens to be empty". That is the failure this module removes: the calls still degrade
8
+ * gracefully, but the reason is recorded once, in a place the interface can read.
9
+ *
10
+ * The state is deliberately tiny and stuck in this process. It is not a token cache and it does
11
+ * not decide authorization — DataOps remains the only authority. It exists because a person
12
+ * looking at the workspace cannot see a 401 that was handled three layers down, and "sign in
13
+ * again" is only actionable if it is shown while it is still true.
14
+ *
15
+ * @module auth-state
16
+ */
17
+ /** What the last DataOps answer said about the stored credential. */
18
+ export type AuthState = 'unknown' | 'valid' | 'expired';
19
+ /**
20
+ * Record that a DataOps call was accepted.
21
+ *
22
+ * Called on any non-401 answer, including ones that carry a refusal for another reason: a 403
23
+ * says the session is fine and the permission is not, so it must not raise the sign-in prompt.
24
+ */
25
+ export declare const noteAccepted: () => void;
26
+ /** Record that DataOps refused the credential, which means the session is over. */
27
+ export declare const noteExpired: () => void;
28
+ /**
29
+ * Read the current observation.
30
+ *
31
+ * @returns `unknown` before any call has completed, then what the last one said.
32
+ */
33
+ export declare const currentAuthState: () => AuthState;
34
+ /**
35
+ * Test-only reset, so one case cannot leak its observation into the next.
36
+ */
37
+ export declare const resetAuthState: () => void;
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ReviewNotification } from './review-types.ts';
3
+ /** Props accepted by a bell. */
4
+ export interface NotificationBellProps {
5
+ /** Accessible label; the two plaza headings differ so screen readers can tell them apart. */
6
+ label: string;
7
+ /** Called after a row is read, so the plaza can jump to the matching tab or card. */
8
+ onOpen?: (item: ReviewNotification) => void;
9
+ }
10
+ /**
11
+ * A bell with an unread badge and the shared inbox.
12
+ *
13
+ * The bell is a button rather than a menu so keyboard focus and Escape follow the dialog
14
+ * conventions the rest of the page already uses. Rows mark themselves read when opened;
15
+ * "全部已读" is the one bulk action.
16
+ */
17
+ export declare function NotificationBell({ label, onOpen }: NotificationBellProps): ReactNode;
18
+ /** The automation page's seat: the same inbox under the workflow plaza's heading. */
19
+ export declare function WorkflowPlazaBell(): ReactNode;
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * The sidebar glyph for the permission centre.
4
+ *
5
+ * The entry is hidden for a caller the Host reports as an ordinary user. This is a navigation
6
+ * affordance, not the authorization decision: every permission-centre endpoint re-checks the
7
+ * caller, and the page itself refuses to render without a management role.
8
+ */
9
+ export declare function PermissionCenterIcon(): ReactNode;
@@ -0,0 +1,15 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { en } from './locales.ts';
3
+ /** Props accepted by the page. */
4
+ export interface PermissionCenterSectionProps {
5
+ t?: (key: keyof typeof en) => string;
6
+ }
7
+ /**
8
+ * The permission centre.
9
+ *
10
+ * Every write goes through `saveRbacSection`; the backend enforces the same role/scope rules
11
+ * the disabled controls represent. The page keeps target-aware state explicitly (actor role,
12
+ * actor department) so an ADMIN cannot edit a SUPER_ADMIN row and a DEPT_ADMIN cannot reach
13
+ * another department, even if a list payload is wider than expected.
14
+ */
15
+ export declare function PermissionCenterSection({ t }: PermissionCenterSectionProps): ReactNode;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The "sign in again" prompt shown when this workspace's DataOps session has ended.
3
+ *
4
+ * The failure it reports used to be invisible. Every DataOps call handles a 401 by degrading to
5
+ * an empty result, which is right for one panel but wrong for the operator: settings, shared
6
+ * models and the skill plaza all render as "nothing here" while the real cause is that nobody is
7
+ * signed in any more. This prompt names that state once, with the one action that clears it.
8
+ *
9
+ * It reports and nothing more. It does not retry, expire, or re-authenticate — the portal owns
10
+ * the login form and is the only thing that can issue a session. It also cannot navigate the
11
+ * operator there: the portal is a different origin, so this frame cannot read or drive its
12
+ * window, and the gateway does not forward the browser's origin to the workspace. Guessing a
13
+ * URL from inside here would open the wrong page on some deployments, so the dialog tells the
14
+ * operator which window to switch to instead of pretending it can take them.
15
+ *
16
+ * @module ReloginPrompt
17
+ */
18
+ /** The copy this prompt needs, bound by its host surface. */
19
+ export interface ReloginPromptProps {
20
+ /** Resolve one of the prompt's own strings. */
21
+ readonly t: (key: 'reloginTitle' | 'reloginBody' | 'reloginAction' | 'reloginDismiss') => string;
22
+ }
23
+ /**
24
+ * Watch for an ended DataOps session and say so.
25
+ *
26
+ * @param props.t - localized copy resolver.
27
+ * @returns the dialog while the session is known to have ended, otherwise `null`.
28
+ */
29
+ export declare function ReloginPrompt(props: ReloginPromptProps): import("react").JSX.Element | null;
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { en } from './locales.ts';
3
+ /**
4
+ * The three plaza-review switches.
5
+ *
6
+ * The card is independent of the review queue so the settings page owns the switches while the
7
+ * plaza owns the rows. Defaults are on; a 503 keeps the switches usable and says the API is
8
+ * waiting rather than pretending the save happened.
9
+ */
10
+ export declare function ReviewSettingsCard({ t }: {
11
+ t: (key: keyof typeof en) => string;
12
+ }): ReactNode;
@@ -19,11 +19,9 @@ export type SkillPlazaSectionProps = Partial<InjectFace<SkillPlazaSectionInjecte
19
19
  /**
20
20
  * Browse, share, and install skills other users published.
21
21
  *
22
- * The section appears only where this plugin is installed: the skill center renders the slot,
23
- * and an empty slot renders nothing. Every action goes through this plugin's Host route, so a
24
- * workspace without DataOps never reaches these calls.
25
- * @param props - Skill center injection values.
26
- * @returns The plaza section, or nothing before injection.
22
+ * Review is a permissioned overlay on the same plaza: the tab strip and the audit dialog only
23
+ * appear when the Host says the caller may review, and the Host additionally narrows the pending
24
+ * list to the caller's department subtree.
27
25
  */
28
26
  export declare function SkillPlazaSection(props: SkillPlazaSectionProps): ReactNode;
29
27
  /** Re-exported so the registration site reads the route from one place. */
@@ -6,7 +6,13 @@ interface MarkProps {
6
6
  export declare function WanxiangMark({ size, className, animated }: MarkProps & {
7
7
  animated?: boolean;
8
8
  }): import("react").JSX.Element;
9
- /** Approved rounded-Heiti artwork, stored as paths with no runtime font dependency. */
9
+ /**
10
+ * Platform wordmark rendered as text.
11
+ *
12
+ * It used to be glyph artwork for the previous platform name, which cannot express the new
13
+ * name without redrawing every path. Text keeps the visible header, the accessible name, and
14
+ * the browser title on one string, and inherits the host's CJK font stack through `font-family`.
15
+ */
10
16
  export declare function WanxiangWordmark({ height, accessible, className }: {
11
17
  height: number;
12
18
  accessible?: boolean;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Workspace image upgrade banner and confirmation dialog.
3
+ *
4
+ * The banner is deliberately quiet: it tells the user a newer workspace image exists and
5
+ * leaves the current session usable. Only the explicit upgrade action opens the dialog
6
+ * that can interrupt running sessions.
7
+ *
8
+ * @module WorkspaceUpgradePrompt
9
+ */
10
+ import type { en } from './locales.ts';
11
+ /** The copy resolver bound by the client plugin. */
12
+ export interface WorkspaceUpgradePromptProps {
13
+ /** Resolve one managed DataOps message key. */
14
+ readonly t: (key: keyof typeof en) => string;
15
+ }
16
+ /**
17
+ * Render the banner and its upgrade dialog.
18
+ * @param props.t - localized copy resolver.
19
+ * @returns the banner while an image update is available, otherwise null.
20
+ */
21
+ export declare function WorkspaceUpgradePrompt(props: WorkspaceUpgradePromptProps): import("react").JSX.Element | null;
@@ -0,0 +1,17 @@
1
+ /** Pure helpers shared by the permission-centre combobox and resource details. */
2
+ /** One selectable option. */
3
+ export interface ComboboxOption {
4
+ readonly value: string;
5
+ readonly label: string;
6
+ readonly hint?: string;
7
+ }
8
+ /**
9
+ * Filter options by display label or code hint, case-insensitively.
10
+ * @param options - all options.
11
+ * @param query - typed search text.
12
+ */
13
+ export declare function filterComboboxOptions(options: readonly ComboboxOption[], query: string): ComboboxOption[];
14
+ /** Keep an active index inside a possibly empty result list. */
15
+ export declare function clampActiveIndex(index: number, length: number): number;
16
+ /** Owner cell face: a real display name, or a dash; never the raw id. */
17
+ export declare function ownerDisplay(ownerName: string | undefined | null): string;
@@ -1,6 +1,15 @@
1
1
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
2
2
  import { type ManagedDataOpsKey } from './locales.ts';
3
3
  declare module '@deepseek-ai/dsh-client-ui-slots' {
4
+ interface SlotMap {
5
+ /** Header actions declared by the knowledge-base workflow plaza. */
6
+ 'workflow-plaza.actions': {
7
+ kind: 'single';
8
+ scope: 'root';
9
+ owner: Record<string, never>;
10
+ hookContext: unknown;
11
+ };
12
+ }
4
13
  interface LocaleNamespaceMap {
5
14
  'settings.dataops-managed': ManagedDataOpsKey;
6
15
  }
@@ -8,7 +17,13 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
8
17
  /** Required client services for the managed DataOps Settings section. */
9
18
  export declare const inject: string[];
10
19
  /**
11
- * Register localized managed DataOps Settings navigation and content.
12
- * @param ctx - DSH client context with locale and settings slots.
20
+ * Register the managed DataOps surfaces and the sign-in prompt.
21
+ *
22
+ * The prompt rides a sidebar seat because the client needs *some* always-mounted surface for a
23
+ * dialog that is about the whole workspace rather than one panel. The seat is a container: the
24
+ * dialog itself portals to `document.body`, so this contributes no visible element to the
25
+ * sidebar at rest and cannot disturb the regions that already live there.
26
+ *
27
+ * @param ctx - DSH client context with locale, slots and the sidebar foot.
13
28
  */
14
29
  export declare function apply(ctx: ClientContext): void;
@@ -38,6 +38,109 @@ export declare const en: {
38
38
  readonly plazaLoadFailed: "The skill plaza could not be loaded. Try again.";
39
39
  readonly plazaActionFailed: "The action could not be completed. Try again.";
40
40
  readonly plazaRetry: "Try again";
41
+ readonly plazaRefresh: "Refresh";
42
+ readonly plazaTabApproved: "Approved";
43
+ readonly plazaTabPending: "Pending";
44
+ readonly plazaTabAll: "All";
45
+ readonly plazaSharedTag: "Shared";
46
+ readonly plazaReview: "Review";
47
+ readonly plazaPendingEmpty: "No skills are waiting for review.";
48
+ readonly plazaAuditTitle: "Review skill";
49
+ readonly plazaAuditSource: "Source";
50
+ readonly plazaAuditVisibility: "Visibility";
51
+ readonly plazaRiskTitle: "AI risk report";
52
+ readonly plazaRiskScan: "Run AI scan";
53
+ readonly plazaRiskScanning: "Scanning…";
54
+ readonly plazaRiskNone: "No AI risk report yet.";
55
+ readonly plazaRiskScore: "Risk score";
56
+ readonly plazaRiskFindings: "Findings";
57
+ readonly plazaRiskFailed: "The AI scan failed; review manually.";
58
+ readonly plazaApprove: "Approve";
59
+ readonly plazaReject: "Reject";
60
+ readonly plazaReviewNote: "Review note (required to reject)";
61
+ readonly plazaReviewNotePlaceholder: "Explain the reason or what to change…";
62
+ readonly plazaReviewDone: "Review saved.";
63
+ readonly plazaReviewFailed: "The review could not be saved. Try again.";
64
+ readonly plazaDemo: "Demo data";
65
+ readonly plazaGlobal: "Global";
66
+ readonly plazaPrivate: "Private";
67
+ readonly plazaDeptSuffix: " department";
68
+ readonly reviewSettingsTitle: "Review settings";
69
+ readonly reviewSettingsSkill: "Skill plaza review";
70
+ readonly reviewSettingsWorkflow: "Workflow plaza review";
71
+ readonly reviewSettingsKb: "Knowledge base review";
72
+ readonly reviewSettingsHint: "When on, submitted content stays private until an administrator approves it.";
73
+ readonly reviewSettingsSave: "Save";
74
+ readonly reviewSettingsSaved: "Review settings saved.";
75
+ readonly reviewSettingsFailed: "Could not save. Try again.";
76
+ readonly reviewSettingsWaiting: "Review settings API is waiting for the backend (task-39).";
77
+ readonly permNav: "Permissions";
78
+ readonly permTitle: "Permission centre";
79
+ readonly permTabRoles: "Role permissions";
80
+ readonly permTabMembers: "Member roles";
81
+ readonly permTabDepartments: "Departments";
82
+ readonly permTabResources: "Resource scope";
83
+ readonly permRoleMatrix: "Role permission matrix";
84
+ readonly permPermissionModule: "Module";
85
+ readonly permPermissionPoint: "Permission";
86
+ readonly permReadOnly: "Read only";
87
+ readonly permSave: "Save";
88
+ readonly permSaved: "Saved.";
89
+ readonly permSaveFailed: "Could not save. Try again.";
90
+ readonly permSuperAdminOnly: "Only a super administrator can change this.";
91
+ readonly permMember: "Member";
92
+ readonly permDepartment: "Department";
93
+ readonly permRole: "Role";
94
+ readonly permDeptAdmin: "Department administrator";
95
+ readonly permScope: "Visibility";
96
+ readonly permGlobal: "Global";
97
+ readonly permDepartmentScope: "Department";
98
+ readonly permPrivate: "Private";
99
+ readonly permNoAccess: "You do not have access to the permission centre.";
100
+ readonly permWaiting: "The permission-centre API is waiting for the backend (task-37).";
101
+ readonly permResourceSkill: "Skill";
102
+ readonly permResourceWorkflow: "Workflow";
103
+ readonly permResourceKb: "Knowledge base";
104
+ readonly permType: "Type";
105
+ readonly permName: "Name";
106
+ readonly permOwner: "Owner";
107
+ readonly permOperations: "Actions";
108
+ readonly permViewDetails: "View details";
109
+ readonly permDetailTitle: "Resource details";
110
+ readonly permDetailType: "Type";
111
+ readonly permDetailName: "Name";
112
+ readonly permDetailOwner: "Owner";
113
+ readonly permDetailScope: "Visibility";
114
+ readonly permDetailDepartment: "Department";
115
+ readonly permDetailStatus: "Review status";
116
+ readonly permDetailUpdated: "Updated";
117
+ readonly permNone: "None";
118
+ readonly permSearch: "Search…";
119
+ readonly permNoMatches: "No matches";
120
+ readonly permClose: "Close";
121
+ readonly permSelect: "Select…";
122
+ readonly permStatusPending: "Pending review";
123
+ readonly permStatusApproved: "Approved";
124
+ readonly permStatusRejected: "Rejected";
125
+ readonly permDemo: "Demo data";
126
+ readonly reloginTitle: "Sign in to DataOps again";
127
+ readonly reloginBody: "This workspace can no longer reach DataOps: the sign-in for this session has ended, so settings, shared models and the skill plaza cannot load. Sign in again in the DataOps window, then reload this page.";
128
+ readonly reloginAction: "Reload";
129
+ readonly reloginDismiss: "Later";
130
+ readonly upgradeAvailableTitle: "A new workspace version is available";
131
+ readonly upgradeAvailableBody: "Restart the workspace to apply it.";
132
+ readonly upgradeAction: "Upgrade";
133
+ readonly upgradeLater: "Later";
134
+ readonly upgradeDialogTitle: "Upgrade workspace";
135
+ readonly upgradeDialogBody: "The workspace will restart and briefly disconnect. Your files and sessions are kept.";
136
+ readonly upgradeNoSessions: "No sessions are running. The workspace will restart now.";
137
+ readonly upgradeSessionsWarning: "{count} running session(s) will be interrupted. After the upgrade, each one is resumed and sent “继续”.";
138
+ readonly upgradeCancel: "Cancel";
139
+ readonly upgradeConfirm: "Stop tasks and upgrade";
140
+ readonly upgradeBusy: "Upgrading workspace…";
141
+ readonly upgradeStarted: "Upgrade started. Reload this page when the workspace reconnects.";
142
+ readonly upgradeFailed: "The workspace upgrade could not be started. Try again.";
143
+ readonly upgradeRetry: "Retry";
41
144
  };
42
145
  /** Stable key set shared by every managed DataOps locale. */
43
146
  export type ManagedDataOpsKey = keyof typeof en;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Browser calls for review, notifications, the permission centre, and review settings.
3
+ *
4
+ * Every request goes through this plugin's same-origin bridge, which holds the DataOps JWT.
5
+ * A 503/404 means the Host or DataOps has not shipped that endpoint yet; callers render their
6
+ * "waiting for the backend" state instead of treating an unfinished feature as a failure.
7
+ */
8
+ import type { ReviewContext, ReviewNotification, RiskReport, ReviewStatus, ResourceVisibility } from './review-types.ts';
9
+ /** Caller identity and review capability. */
10
+ export declare const REVIEW_CONTEXT_PATH = "/integrations/dataops/review-context";
11
+ /** Notification list and read receipts. */
12
+ export declare const NOTIFICATIONS_PATH = "/integrations/dataops/review-notifications";
13
+ /** AI scan and the human decision. */
14
+ export declare const REVIEW_ACTION_PATH = "/integrations/dataops/review";
15
+ /** Role matrix, members, departments, and resource scopes. */
16
+ export declare const RBAC_PATH = "/integrations/dataops/rbac";
17
+ /** The three plaza-review switches in the DataOps settings panel. */
18
+ export declare const REVIEW_SETTINGS_PATH = "/integrations/dataops/review-settings";
19
+ /** Department CRUD bridge (task-67). */
20
+ export declare const DEPARTMENTS_PATH = "/integrations/dataops/departments";
21
+ /** One permission point in the role matrix. */
22
+ export interface PermissionPoint {
23
+ key: string;
24
+ name: string;
25
+ group: string;
26
+ /** Backend scope metadata for the DSH capability catalogue (task-70). */
27
+ scope?: 'department' | 'global' | string;
28
+ /** Chinese presentation note (task-76). */
29
+ description?: string;
30
+ }
31
+ /** One role row in the matrix. */
32
+ export interface RoleMatrixRow {
33
+ code: string;
34
+ name: string;
35
+ systemKey?: string;
36
+ dataScope: 'ALL' | 'DEPARTMENT' | 'SELF' | 'CUSTOM_DEPARTMENTS' | 'DEPARTMENT_TREE';
37
+ permissions: readonly string[];
38
+ }
39
+ /** One member and their role assignment. */
40
+ export interface MemberRow {
41
+ userId: string;
42
+ username: string;
43
+ displayName: string;
44
+ roleCodes: readonly string[];
45
+ departmentCode?: string;
46
+ departmentPath?: readonly {
47
+ code: string;
48
+ name: string;
49
+ }[];
50
+ dataScope?: string;
51
+ }
52
+ /** One department in the tree. */
53
+ export interface DepartmentRow {
54
+ code: string;
55
+ name: string;
56
+ parentCode?: string;
57
+ path?: readonly {
58
+ code: string;
59
+ name: string;
60
+ }[];
61
+ memberCount?: number;
62
+ deptAdmins?: readonly {
63
+ userId: string;
64
+ username: string;
65
+ displayName: string;
66
+ }[];
67
+ description?: string;
68
+ owner?: string;
69
+ enabled?: boolean;
70
+ updatedAt?: string;
71
+ }
72
+ /** One skill/workflow resource and its visibility scope. */
73
+ export interface ResourceScopeRow {
74
+ kind: 'skill' | 'workflow';
75
+ id: string | number;
76
+ name: string;
77
+ ownerId?: string;
78
+ ownerName?: string;
79
+ departmentCode?: string | null;
80
+ visibility: ResourceVisibility;
81
+ reviewStatus?: ReviewStatus;
82
+ updatedAt?: string;
83
+ }
84
+ /** Everything the permission centre renders in one read. */
85
+ export interface RbacOverview {
86
+ matrix: {
87
+ roles: readonly RoleMatrixRow[];
88
+ permissions: readonly PermissionPoint[];
89
+ };
90
+ members: readonly MemberRow[];
91
+ departments: readonly DepartmentRow[];
92
+ resources: readonly ResourceScopeRow[];
93
+ }
94
+ /** One plaza review switch. */
95
+ export interface ReviewSettings {
96
+ skillPlaza: boolean;
97
+ workflowPlaza: boolean;
98
+ knowledgeBase: boolean;
99
+ }
100
+ /** The product defaults, used until DataOps returns its own value. */
101
+ export declare const DEFAULT_REVIEW_SETTINGS: ReviewSettings;
102
+ /**
103
+ * Whether the page was opened with the demo fixture.
104
+ *
105
+ * The fixture exists so the review dialog and the permission matrix can be reviewed before the
106
+ * backend contract is deployed; every fixture row is labelled and the mode is never the default.
107
+ */
108
+ export declare function reviewDemoMode(): boolean;
109
+ /** Read the caller's role and review capability. */
110
+ export declare function fetchReviewContext(): Promise<ReviewContext | null>;
111
+ /** Read the current user's notifications. */
112
+ export declare function fetchNotifications(): Promise<{
113
+ items: readonly ReviewNotification[];
114
+ unread: number;
115
+ } | null>;
116
+ /** Mark some notifications, or every one, as read. */
117
+ export declare function markNotificationsRead(ids: 'all' | readonly string[]): Promise<boolean>;
118
+ /** Run the AI review for one or more resources. */
119
+ export declare function scanReview(kind: 'skill' | 'workflow', ids: readonly (string | number)[]): Promise<RiskReport | null>;
120
+ /** Record the administrator's decision. The backend enforces the authorization flag too. */
121
+ export declare function decideReview(kind: 'skill' | 'workflow', ids: readonly (string | number)[], decision: 'approve' | 'reject', note: string): Promise<boolean>;
122
+ /** Read the permission centre. */
123
+ export declare function fetchRbacOverview(): Promise<RbacOverview | null>;
124
+ /** What a permission-centre write returned, including the backend's readable refusal. */
125
+ export interface RbacSaveResult {
126
+ ok: boolean;
127
+ reason?: string;
128
+ }
129
+ /** Persist one permission-centre section. */
130
+ export declare function saveRbacSection(section: 'roles' | 'members' | 'departments' | 'resources', value: unknown): Promise<RbacSaveResult>;
131
+ /** Read the three plaza-review switches. */
132
+ export declare function fetchReviewSettings(): Promise<ReviewSettings | null>;
133
+ /** Persist the three plaza-review switches. */
134
+ export declare function saveReviewSettings(settings: ReviewSettings): Promise<boolean>;
135
+ /** Create one department through the same-origin bridge. */
136
+ export declare function createDepartment(input: {
137
+ code: string;
138
+ name: string;
139
+ parentCode?: string | null;
140
+ description?: string;
141
+ owner?: string;
142
+ enabled?: boolean;
143
+ }): Promise<RbacSaveResult>;
144
+ /** Update one department; the Host fills `updatedAt` from the current row when omitted. */
145
+ export declare function updateDepartment(input: {
146
+ code: string;
147
+ name?: string;
148
+ parentCode?: string | null;
149
+ description?: string;
150
+ owner?: string;
151
+ enabled?: boolean;
152
+ updatedAt?: string;
153
+ }): Promise<RbacSaveResult>;
154
+ /** Delete one department after the confirmation dialog. */
155
+ export declare function deleteDepartment(code: string): Promise<RbacSaveResult>;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Labeled fixtures for reviewing the new surfaces before the backend contract is deployed.
3
+ *
4
+ * They are only reachable through `?dataopsReviewDemo=1`; production never merges them into a
5
+ * listing. Keeping them in one module means the plaza, the audit dialog and the permission
6
+ * centre tell the same story instead of each inventing a different sample.
7
+ */
8
+ import type { PlazaSkill } from './skill-plaza-client.ts';
9
+ import type { RbacOverview } from './review-client.ts';
10
+ /** A pending skill with an AI report, an approved global skill, and a rejected private one. */
11
+ export declare function demoPlazaSkills(): PlazaSkill[];
12
+ /** Four roles, the permission catalog, three members, two departments, and three resources. */
13
+ export declare function demoRbacOverview(): RbacOverview;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * One process-wide notification store shared by every bell.
3
+ *
4
+ * The skill plaza and the workflow plaza are separate React trees, but they show the same
5
+ * inbox; keeping the read state in one store is what stops a badge in one place from staying
6
+ * unread after the other opened the row.
7
+ */
8
+ import type { ReviewNotification } from './review-types.ts';
9
+ interface NotificationState {
10
+ status: 'idle' | 'loading' | 'ready' | 'unavailable';
11
+ items: readonly ReviewNotification[];
12
+ unread: number;
13
+ }
14
+ /** Current snapshot for `useSyncExternalStore`. */
15
+ export declare function notificationSnapshot(): NotificationState;
16
+ /** Subscribe to store changes. */
17
+ export declare function subscribeNotifications(listener: () => void): () => void;
18
+ export declare function loadNotifications(): Promise<void>;
19
+ /** Mark one row or the whole inbox read, optimistically. */
20
+ export declare function markRead(ids: 'all' | readonly string[]): Promise<void>;
21
+ /** Load the inbox when a bell first mounts. */
22
+ export declare function useNotifications(): NotificationState;
23
+ export {};
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Shared vocabulary for the review, permission and notification surfaces.
3
+ *
4
+ * The backend contract is landing in parallel; every field that a legacy row cannot carry
5
+ * yet is optional here and normalized to the product default (approved/global) rather than
6
+ * guessed per component. Keeping the rules in one place is what lets the skill plaza, the
7
+ * permission centre and the notification bell agree on what "pending" means.
8
+ */
9
+ /** Review state of a shared skill or workflow. */
10
+ export type ReviewStatus = 'pending' | 'approved' | 'rejected';
11
+ /** Who may see a shared resource outside its owner. */
12
+ export type ResourceVisibility = 'global' | 'department' | 'private';
13
+ /** The skill-plaza filter tabs, in display order. */
14
+ export type ReviewTab = 'approved' | 'pending' | 'all';
15
+ /** The four DataOps roles the permission centre knows. */
16
+ export type PortalRole = 'SUPER_ADMIN' | 'ADMIN' | 'DEPT_ADMIN' | 'APP_USER';
17
+ /** One node of a department ancestry path. */
18
+ export interface DepartmentPathEntry {
19
+ code: string;
20
+ name: string;
21
+ }
22
+ /** A department as a card tag needs it: code, label, and the ancestry for the tooltip. */
23
+ export interface DepartmentSummary {
24
+ code: string;
25
+ name: string;
26
+ /** Root-first path, in either the legacy string form or the backend object form. */
27
+ path?: readonly (string | DepartmentPathEntry)[];
28
+ }
29
+ /** One AI risk finding. */
30
+ export interface RiskFinding {
31
+ category: string;
32
+ severity: 'low' | 'medium' | 'high';
33
+ evidence?: string;
34
+ recommendation?: string;
35
+ }
36
+ /** The AI report attached to a pending resource. */
37
+ export interface RiskReport {
38
+ score?: number;
39
+ /** Overall severity, which is wider than a single finding's severity. */
40
+ severity?: 'low' | 'medium' | 'high' | 'critical';
41
+ summary?: string;
42
+ findings: readonly RiskFinding[];
43
+ scannedAt?: string;
44
+ }
45
+ /** The caller's review permissions, as the Host reports them. */
46
+ export interface ReviewContext {
47
+ userId?: string;
48
+ role: PortalRole;
49
+ departmentCode?: string;
50
+ departmentPath?: readonly string[];
51
+ canReview: boolean;
52
+ canManagePermissions: boolean;
53
+ }
54
+ /** One notification row. */
55
+ export interface ReviewNotification {
56
+ id: string;
57
+ type: string;
58
+ title: string;
59
+ body?: string;
60
+ resourceKind?: string;
61
+ resourceId?: string | number;
62
+ read: boolean;
63
+ createdAt: string;
64
+ }
65
+ /** A reviewable row as the cards read it. */
66
+ export interface ReviewableSummary {
67
+ reviewStatus?: ReviewStatus;
68
+ visibility?: ResourceVisibility;
69
+ departmentCode?: string | null;
70
+ departmentName?: string;
71
+ departmentPath?: readonly (string | DepartmentPathEntry)[];
72
+ riskReport?: RiskReport | null;
73
+ reviewNote?: string | null;
74
+ submittedAt?: string;
75
+ }
76
+ /** Treat a missing status as approved: rows written before the review state existed were live. */
77
+ export declare function normalizeReviewStatus(value: unknown): ReviewStatus;
78
+ /** Treat a missing visibility as global: a shared row with no scope was visible to everyone. */
79
+ export declare function normalizeVisibility(value: unknown): ResourceVisibility;
80
+ /** Whether the caller may see and act on the review queue. */
81
+ export declare function canReview(context: ReviewContext | null | undefined): boolean;
82
+ /** Whether the caller may open the permission centre's write controls. */
83
+ export declare function canManagePermissions(context: ReviewContext | null | undefined): boolean;
84
+ /**
85
+ * The tabs this caller may see.
86
+ *
87
+ * A department administrator sees the queue too, but the Host narrows it to their subtree;
88
+ * an ordinary user gets only the approved list, and no tab strip at all when there is one tab.
89
+ */
90
+ export declare function reviewTabsFor(context: ReviewContext | null | undefined): readonly ReviewTab[];
91
+ /** Filter rows by the selected review tab. */
92
+ export declare function filterByReview<T extends ReviewableSummary>(rows: readonly T[], tab: ReviewTab): readonly T[];
93
+ /** Counts for the tab labels. */
94
+ export declare function reviewCounts(rows: readonly ReviewableSummary[]): Record<ReviewTab, number>;
95
+ /** The status badge shown on a local skill card. */
96
+ export declare function reviewStatusLabel(status: ReviewStatus): string;
97
+ /**
98
+ * The visibility tag in a card's top-right corner.
99
+ *
100
+ * A department tag names the department and keeps the full path in the tooltip; a global tag
101
+ * is one word because it is the default and should not compete with the skill name.
102
+ */
103
+ /** Read a department path from either the legacy string form or the backend object form. */
104
+ export declare function departmentPathNames(path: ReviewableSummary['departmentPath']): string[];
105
+ export declare function visibilityTag(row: ReviewableSummary): {
106
+ label: string;
107
+ detail: string;
108
+ };
109
+ /** Unread count for a bell badge. */
110
+ export declare function unreadCount(items: readonly ReviewNotification[]): number;
111
+ /** Newest first. Read state never reorders rows, so marking one read cannot move it to another page. */
112
+ export declare function sortNotifications(items: readonly ReviewNotification[]): readonly ReviewNotification[];
113
+ /** The tab a notification should open, when its resource has one. */
114
+ export declare function notificationTab(item: ReviewNotification): ReviewTab | undefined;