@sparkelf/dsh-knowledge-base 0.1.29 → 0.1.31

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.
@@ -1,265 +0,0 @@
1
- /**
2
- * The pure half of the permissions overview: how one grant table reads two ways.
3
- *
4
- * Kept apart from the rendering module for a real reason, not for tidiness: a
5
- * component module imports the primitives package, which imports CSS, and a Node
6
- * test runner cannot load a `.css` file. The grouping rules are the part worth
7
- * testing without a DOM, so they live somewhere a plain import can reach.
8
- *
9
- * @module @sparkelf/dsh-knowledge-base/client/permissionRows
10
- */
11
- import type { GrantView, KnowledgeGrantSubjectType } from '../api.ts';
12
- /** One person's holdings, as the person view renders them. */
13
- export interface PersonHoldings {
14
- /** Principal id the grants name. */
15
- principalId: string;
16
- /** Principal kind, so the type column can name it. */
17
- principalType: KnowledgeGrantSubjectType;
18
- /** Display name when a directory entry resolved one, else the id. */
19
- label: string;
20
- /** Distinct paths this person may touch. */
21
- paths: string[];
22
- /** Which actions the person holds, collapsed so a full house reads `all`. */
23
- actions: string[];
24
- /**
25
- * Roles the person reaches through a `role` grant they belong to.
26
- *
27
- * Empty when the directory does not know the membership: a role grant names the
28
- * ROLE, not the people in it, so this is only populated when a directory entry
29
- * declared the membership.
30
- */
31
- roles: string[];
32
- /** Most recent grant instant across this person's rows, ISO-8601. */
33
- latestAt: string;
34
- /** The grants this row covers, so a revoke can name exactly what it removes. */
35
- grants: GrantView[];
36
- }
37
- /** One resource and everyone who may reach it. */
38
- export interface ResourceHoldings {
39
- /** Path, relative to the mount point. */
40
- relPath: string;
41
- /** Whether it is a file or a directory. */
42
- kind: 'file' | 'directory';
43
- /** Everyone holding any action on it, grouped by principal kind. */
44
- principals: Array<{
45
- principalType: KnowledgeGrantSubjectType;
46
- principalId: string;
47
- label: string;
48
- actions: string[];
49
- latestAt: string;
50
- grants: GrantView[];
51
- }>;
52
- }
53
- /**
54
- * The result of reading a set of grants as permissions.
55
- *
56
- * `known: false` carries a reason rather than an empty list, because "we could not
57
- * work this out" and "this principal holds nothing" must not render the same way: the
58
- * first is a fault the reader should see, and collapsing it into an empty set is how a
59
- * denial gets invented. See {@link resolveActions}.
60
- */
61
- export interface ResolvedActions {
62
- /** Actions held, in display order. Empty when `known` is false. */
63
- actions: string[];
64
- /**
65
- * Whether the full set could be determined.
66
- *
67
- * False when part of the input could not be read -- an inheritance path that was
68
- * unavailable, or a grant whose action is a word this build does not know.
69
- */
70
- known: boolean;
71
- /** Why the set is incomplete, when it is. Shown to the reader beside the actions. */
72
- reason?: string;
73
- }
74
- /**
75
- * Union a principal's grants into the permission set it actually holds.
76
- *
77
- * UNION, never intersection and never "the first row": a principal reaches a resource
78
- * through several grants (a direct one and one inherited from each role), and what
79
- * they may do is everything any of those grants allows. Reading one row is what made
80
- * an administrator display as read-only.
81
- *
82
- * An unrecognised action word makes the whole answer unknown rather than being
83
- * skipped. Skipping it would under-report, and under-reporting a permission is the
84
- * direction that misleads an operator into thinking access is safe to ignore.
85
- *
86
- * @param actions - every action string from every grant reaching this principal.
87
- * @returns The unioned set, or an explicit unknown with the reason.
88
- */
89
- export declare function resolveActions(actions: readonly string[]): ResolvedActions;
90
- /**
91
- * Whether a resolved set confers the ability to change a document.
92
- *
93
- * Used by the page to check itself against what the reader can actually do. `manage`
94
- * implies write in this model: it is the full-control level, and a principal holding
95
- * it can move and delete.
96
- * @param resolved - the unioned permission set.
97
- * @returns true when writing is possible, or unknown.
98
- */
99
- export declare function canWriteWith(resolved: ResolvedActions): boolean | undefined;
100
- /**
101
- * Collapse a set of action strings into the shortest describing list.
102
- *
103
- * A thin view over {@link resolveActions} so there is ONE vocabulary and ONE union
104
- * rule in the browser: this used to be a second implementation filtering on
105
- * `create`/`update`/`delete`, which silently dropped every `write` and `manage` grant.
106
- *
107
- * Kept as an exported name because the older callers and their tests use it.
108
- * @param actions - raw action strings from grants.
109
- * @returns The unioned actions in display order; unknown words are dropped here,
110
- * which is why a surface that must not under-report calls {@link resolveActions}.
111
- */
112
- export declare function summarizeActions(actions: readonly string[]): string[];
113
- /**
114
- * Group grants by the principal they name.
115
- *
116
- * A principal appearing in several grants is one row: "what does 张三 have" is a
117
- * question about a person, and splitting them across rows because they hold two
118
- * paths would answer it wrong.
119
- *
120
- * @param grants - the grant table.
121
- * @param labels - display names by `principalType:principalId`, optional.
122
- * @returns one entry per principal, ordered by label then id.
123
- */
124
- export declare function groupHoldingsByPrincipal(grants: readonly GrantView[], labels?: Readonly<Record<string, string>>): PersonHoldings[];
125
- /**
126
- * Group grants by the resource they cover.
127
- *
128
- * @param grants - the grant table.
129
- * @param labels - display names by `principalType:principalId`, optional.
130
- * @returns one entry per path, ordered by path.
131
- */
132
- export declare function groupHoldingsByResource(grants: readonly GrantView[], labels?: Readonly<Record<string, string>>): ResourceHoldings[];
133
- /**
134
- * Format a grant instant the way the reference table does.
135
- *
136
- * DataOps' permission preview prints `YYYY/MM/DD HH:mm` and a dash for a missing
137
- * value, so a reader comparing the two surfaces sees the same date. The raw
138
- * ISO-8601 string the API returns is not that.
139
- * @param value - ISO-8601 instant, or an empty string when the backend sent none.
140
- * @returns The formatted instant, or a dash when there is nothing to show.
141
- */
142
- export declare function formatGrantInstant(value: string): string;
143
- /**
144
- * One DataOps account as the user directory reports it, reduced to what grouping needs.
145
- *
146
- * `roles` is what makes inheritance visible: a role grant names the ROLE, so without
147
- * the membership list the page can only show role grants as belonging to nobody. The
148
- * directory is the only source for it, and it is administrator-only -- see
149
- * {@link DirectoryState}.
150
- */
151
- export interface DirectoryUser {
152
- /** DataOps user id, which is what a `user` grant names. */
153
- userId: string;
154
- /** Login name, shown beside the display name so two "张伟" can be told apart. */
155
- username: string;
156
- /** Display name, when the account has one. */
157
- displayName: string;
158
- /** Role codes this account belongs to. */
159
- roleCodes: string[];
160
- }
161
- /**
162
- * Whether the user directory could be read.
163
- *
164
- * Three states, not two. "Nobody holds a role" and "we could not look" produce the same
165
- * empty membership map, and rendering them the same way is how a page ends up telling
166
- * an operator that nobody has inherited access when the truth is that the read failed.
167
- */
168
- export type DirectoryState = {
169
- kind: 'available';
170
- users: readonly DirectoryUser[];
171
- } | {
172
- kind: 'unavailable';
173
- reason: string;
174
- };
175
- /** One account's holdings, as the by-person view renders them. */
176
- export interface AccountHoldings {
177
- /** The account id. */
178
- userId: string;
179
- /** Login name, for disambiguating identical display names. */
180
- username?: string;
181
- /** Display name, or the id when the account is not in the directory. */
182
- label: string;
183
- /** Distinct paths this account reaches, directly or through a role. */
184
- paths: string[];
185
- /** The unioned permission set, never narrowed by a single grant. */
186
- resolved: ResolvedActions;
187
- /** Direct grants the account holds itself. */
188
- directGrants: GrantView[];
189
- /**
190
- * Role codes the account inherits through.
191
- *
192
- * Empty when the directory could not be read; {@link inheritanceKnown} says which.
193
- */
194
- viaRoles: string[];
195
- /** Whether the inheritance half of this row was actually determined. */
196
- inheritanceKnown: boolean;
197
- /** Why inheritance is unknown, when it is. */
198
- inheritanceReason?: string;
199
- /** Most recent grant instant across the rows this account covers. */
200
- latestAt: string;
201
- }
202
- /** One role's holdings, as the by-role view renders them. */
203
- export interface RoleHoldings {
204
- /** Role code, which is what a `role` grant names. */
205
- roleCode: string;
206
- /** Role display name when the directory resolved one, else the code. */
207
- label: string;
208
- /** Distinct paths the role covers. */
209
- paths: string[];
210
- /** The unioned permission set for this role. */
211
- resolved: ResolvedActions;
212
- /** Grants that name this role. */
213
- grants: GrantView[];
214
- /** How many directory accounts belong to this role, when the directory is known. */
215
- memberCount?: number;
216
- /** Most recent grant instant across this role's rows. */
217
- latestAt: string;
218
- }
219
- /**
220
- * Group grants by the DataOps ACCOUNT that holds them.
221
- *
222
- * The account is the row grain because "who can see this" is a question about people.
223
- * A role grant confers access on every member of that role, so its paths are attributed
224
- * to each member here -- otherwise a reader whose access is entirely role-derived would
225
- * appear on no row at all, and the page would say "nobody has access" to somebody who
226
- * can write.
227
- *
228
- * Three sources are merged, and the merge is a UNION:
229
- * 1. direct `user` grants;
230
- * 2. grants held by any role the account belongs to (inherited);
231
- * 3. nothing else -- the built-in administrator levels are already rows in the grant
232
- * table (`source: 'seed'`), so hard-coding them here would create a second authority
233
- * that could disagree with the backend.
234
- *
235
- * @param grants - the whole grant table.
236
- * @param directory - the user directory, or the reason it could not be read.
237
- * @param labels - display names by `principalType:principalId`, optional.
238
- * @returns One row per account that holds or inherits at least one grant, sorted by label.
239
- */
240
- export declare function groupHoldingsByAccount(grants: readonly GrantView[], directory: DirectoryState, labels?: Readonly<Record<string, string>>): AccountHoldings[];
241
- /**
242
- * Group grants by the ROLE they name.
243
- *
244
- * The same grants the old "by person" table showed, now labelled as what they are: a
245
- * role is not a person, and putting it under "by person" told the reader that the
246
- * administrators were the only people with access.
247
- *
248
- * A role whose grants are all unreadable still gets a row with `known: false`: that is
249
- * the safety net against rendering "no permission" for a read failure.
250
- *
251
- * @param grants - the whole grant table.
252
- * @param labels - display names by `principalType:principalId`, optional.
253
- * @param memberCounts - role code -> member count, when the directory is known.
254
- * @returns One row per role named by at least one grant, sorted by label.
255
- */
256
- export declare function groupHoldingsByRole(grants: readonly GrantView[], labels?: Readonly<Record<string, string>>, memberCounts?: ReadonlyMap<string, number>): RoleHoldings[];
257
- /**
258
- * Build the role -> member-count map from a directory.
259
- *
260
- * Only meaningful when the directory is known; returns undefined otherwise so the row
261
- * omits the count rather than claiming zero members.
262
- * @param directory - the user directory state.
263
- * @returns Counts by role code, or undefined when unavailable.
264
- */
265
- export declare function roleMemberCounts(directory: DirectoryState): Map<string, number> | undefined;
@@ -1,54 +0,0 @@
1
- /**
2
- * What each role in the grant table actually amounts to.
3
- *
4
- * A role is not stored anywhere: it exists because grants name it. That is a good
5
- * property to inherit — a role cannot outlive its resources or appear with none —
6
- * but it means "list the roles" is a question about the grants, answered by
7
- * reading them. This module is that reading, kept out of the component so it can
8
- * be tested without a DOM.
9
- *
10
- * @module @sparkelf/dsh-knowledge-base/client/roleSummary
11
- */
12
- import type { GrantView } from '../api.ts';
13
- /** One role as the overview shows it. */
14
- export interface RoleSummary {
15
- /** Role id, which is the `principalId` its grants carry. */
16
- id: string;
17
- /** Display name, falling back to the id when no locale entry matches. */
18
- label: string;
19
- /** Actions granted anywhere by this role, collapsed so a full house reads `all`. */
20
- actions: string[];
21
- /** Distinct paths it reaches. */
22
- paths: string[];
23
- /** Whether this is one of the two the product ships with. */
24
- builtIn: boolean;
25
- }
26
- /**
27
- * Collapse raw action strings the way the rest of the page does.
28
- *
29
- * This used to carry its own word list, inherited from the create/edit form
30
- * (`read`/`create`/`update`/`delete`). DataOps stores `read`/`write`/`manage`, so
31
- * `write` and `manage` matched nothing and were dropped: the role BADGES above the
32
- * table showed 「只读」 while the table below them — already fixed — showed
33
- * 读/写/管理. One page, two conclusions, and the reader has no way to tell which is
34
- * true.
35
- *
36
- * It now delegates to {@link resolveActions}, which is the single vocabulary and the
37
- * single union rule for this package. Do not reintroduce a local list here: a second
38
- * copy of "what the words mean" is exactly the defect this line removes.
39
- *
40
- * @param actions - raw action strings.
41
- * @returns The unioned actions in product order.
42
- */
43
- export declare function summarizeRoleActions(actions: readonly string[]): string[];
44
- /**
45
- * Read the roles out of a grant table.
46
- *
47
- * Only `role` grants contribute: a user grant is not a role, and inventing one
48
- * per user would flood the list with rows that mean nothing.
49
- *
50
- * @param grants - the grant table.
51
- * @param locale - `zh` or `en`, for the built-in role names.
52
- * @returns one entry per role named by a grant, built-ins first then alphabetical.
53
- */
54
- export declare function buildRoleSummaries(grants: readonly GrantView[], locale?: 'zh' | 'en'): RoleSummary[];