@knowttl/mg-axi 0.1.2 → 0.1.4

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.
@@ -3,7 +3,11 @@ import { listTotals } from "./list-totals.js";
3
3
  // READ-03: the Conditional Access read mapping behind
4
4
  // `mg-axi entra conditional-access policy list/show` and
5
5
  // `mg-axi entra conditional-access named-location list/show` as separate
6
- // az-style grammar. Operation construction stays beside its command; the
6
+ // az-style grammar, plus the remaining v1.0 named collections under
7
+ // `identity/conditionalAccess`: authentication-strength policies, each
8
+ // policy's combination configurations, authentication method modes,
9
+ // templates, authentication context class references and the deleted
10
+ // policy and named-location collections. Operation construction stays beside its command; the
7
11
  // shared session owns URLs, credentials, paging, retries and error
8
12
  // translation, and the SDK owns TOON rendering. This module only maps flags
9
13
  // to session calls and projects rows for compact output. No policy mutation
@@ -11,13 +15,45 @@ import { listTotals } from "./list-totals.js";
11
15
  //
12
16
  // Reviewed against the v1.0 conditionalaccessroot-list-policies,
13
17
  // conditionalaccesspolicy-get, conditionalaccessroot-list-namedlocations and
14
- // countrynamedlocation-get operation documentation on 2026-10-04. All four
15
- // read D/A Policy.Read.All; delegated callers additionally need a supported
16
- // directory role (Conditional Access Administrator, Global Reader, Global
17
- // Secure Access Administrator, Security Administrator or Security Reader).
18
+ // countrynamedlocation-get operation documentation on 2026-10-04, and the
19
+ // authenticationstrengthroot-list-policies,
20
+ // authenticationstrengthpolicy-get,
21
+ // authenticationstrengthpolicy-list-combinationconfigurations,
22
+ // authenticationcombinationconfiguration-get,
23
+ // authenticationstrengthroot-list-authenticationmethodmodes,
24
+ // authenticationmethodmodedetail-get, conditionalaccessroot-list-templates,
25
+ // conditionalaccesstemplate-get, conditionalaccessroot-list-
26
+ // authenticationcontextclassreferences and authenticationcontextclassreference-
27
+ // get operation documentation on 2026-10-06. The deleted policy and
28
+ // named-location collections carry no v1.0 operation-level documentation
29
+ // page; their review follows the beta policyDeletableItem list/get contract
30
+ // for conditional-access objects with the conditionalAccessPolicy,
31
+ // namedLocation and policyDeletableItem resource references.
32
+ // Policy and named-location reads need D/A Policy.Read.All; the
33
+ // authentication-strength family (policies, combination configurations and
34
+ // authentication method modes) reads D/A Policy.Read.AuthenticationMethod
35
+ // instead, and template reads D/A Policy.Read.All. Delegated callers
36
+ // additionally need a supported directory role (Conditional Access
37
+ // Administrator, Security Administrator or Security Reader for the
38
+ // strength family; those plus Global Reader and Global Secure Access
39
+ // Administrator for policies, named locations and templates). The
40
+ // authentication context class references read D/A
41
+ // AuthenticationContext.Read.All with the same broad delegated role set.
42
+ // Deleted policies and named locations read D/A Policy.Read.All with the
43
+ // same broad delegated role set; restore is a beta-only mutation and stays
44
+ // out of scope.
18
45
  // Conditional Access needs P1; risk-based Conditional Access needs P2. The
19
46
  // reviewed raw surface in src/api.ts carries exactly these routes, fields
20
47
  // and access choices; the named commands below reuse that contract.
48
+ //
49
+ // Deferred with reason: the parameterless
50
+ // `authenticationStrength/policies/{id}/usage()` binds no placeholder, so
51
+ // it never passes through the session's validated function-argument
52
+ // binding (FUNCTION_ARGUMENT_BINDINGS covers only named-parameter
53
+ // segments); widening the request-path guard for one function is out of
54
+ // scope. Authentication context class references land here: the former
55
+ // needs an AuthenticationContext.Read.All allowlist addition, now carried
56
+ // in the session's READ_SCOPES set.
21
57
  // Every policy property this slice may request or display, matching the
22
58
  // reviewed raw surface. Anything else fails before credentials.
23
59
  export const KNOWN_POLICY_FIELDS = [
@@ -44,6 +80,61 @@ export const KNOWN_LOCATION_FIELDS = [
44
80
  ];
45
81
  const POLICY_KNOWN = new Set(KNOWN_POLICY_FIELDS);
46
82
  const LOCATION_KNOWN = new Set(KNOWN_LOCATION_FIELDS);
83
+ // Every authentication-strength policy property this slice may request or
84
+ // display, matching the reviewed raw surface.
85
+ export const KNOWN_STRENGTH_FIELDS = [
86
+ "id",
87
+ "displayName",
88
+ "description",
89
+ "policyType",
90
+ "requirementsSatisfied",
91
+ "allowedCombinations",
92
+ "createdDateTime",
93
+ "modifiedDateTime",
94
+ ];
95
+ // Every combination-configuration property this slice may request or
96
+ // display: the base type carries only an identifier and the combinations
97
+ // it applies to; subtype detail rides on @odata.type instead.
98
+ export const KNOWN_COMBO_FIELDS = [
99
+ "id",
100
+ "appliesToCombinations",
101
+ ];
102
+ // Every authentication method mode property this slice may request or
103
+ // display, matching the reviewed raw surface.
104
+ export const KNOWN_MODE_FIELDS = [
105
+ "id",
106
+ "displayName",
107
+ "authenticationMethod",
108
+ ];
109
+ // Every Conditional Access template property this slice may request or
110
+ // display, matching the reviewed raw surface.
111
+ export const KNOWN_TEMPLATE_FIELDS = [
112
+ "id",
113
+ "name",
114
+ "description",
115
+ "scenarios",
116
+ "details",
117
+ ];
118
+ // Every authentication context class reference property this slice may
119
+ // request or display, matching the reviewed v1.0 resource shape.
120
+ export const KNOWN_AUTH_CONTEXT_FIELDS = [
121
+ "id",
122
+ "displayName",
123
+ "description",
124
+ "isAvailable",
125
+ ];
126
+ // Deleted policies and named locations return the underlying policy and
127
+ // location objects with the soft-delete timestamp the policyDeletableItem
128
+ // base type adds, so the reviewed sets extend the live ones.
129
+ export const KNOWN_DELETED_POLICY_FIELDS = [...KNOWN_POLICY_FIELDS, "deletedDateTime"];
130
+ export const KNOWN_DELETED_LOCATION_FIELDS = [...KNOWN_LOCATION_FIELDS, "deletedDateTime"];
131
+ const STRENGTH_KNOWN = new Set(KNOWN_STRENGTH_FIELDS);
132
+ const COMBO_KNOWN = new Set(KNOWN_COMBO_FIELDS);
133
+ const MODE_KNOWN = new Set(KNOWN_MODE_FIELDS);
134
+ const TEMPLATE_KNOWN = new Set(KNOWN_TEMPLATE_FIELDS);
135
+ const AUTH_CONTEXT_KNOWN = new Set(KNOWN_AUTH_CONTEXT_FIELDS);
136
+ const DELETED_POLICY_KNOWN = new Set(KNOWN_DELETED_POLICY_FIELDS);
137
+ const DELETED_LOCATION_KNOWN = new Set(KNOWN_DELETED_LOCATION_FIELDS);
47
138
  // @odata.type is preserved on named-location rows without being selectable:
48
139
  // it names the location kind (ipNamedLocation versus countryNamedLocation).
49
140
  const LOCATION_TYPE_PROPERTY = "@odata.type";
@@ -58,8 +149,44 @@ const DEFAULT_POLICY_SHOW_SELECT = [...KNOWN_POLICY_FIELDS];
58
149
  const DEFAULT_LOCATION_LIST_SELECT = ["id", "displayName"];
59
150
  // Show rows: the full reviewed location set.
60
151
  const DEFAULT_LOCATION_SHOW_SELECT = [...KNOWN_LOCATION_FIELDS];
61
- // Policy.Read.All covers both families in both modes.
152
+ // Compact strength rows: identifier, name and the built-in/custom kind.
153
+ const DEFAULT_STRENGTH_LIST_SELECT = ["id", "displayName", "policyType"];
154
+ // Fetch the full reviewed strength set for inspection.
155
+ const DEFAULT_STRENGTH_SHOW_SELECT = [...KNOWN_STRENGTH_FIELDS];
156
+ // Compact combination rows: the identifier; the kind rides as @odata.type.
157
+ const DEFAULT_COMBO_LIST_SELECT = ["id"];
158
+ // Show rows: the full reviewed base set.
159
+ const DEFAULT_COMBO_SHOW_SELECT = [...KNOWN_COMBO_FIELDS];
160
+ // Compact mode rows: identifier and name.
161
+ const DEFAULT_MODE_LIST_SELECT = ["id", "displayName"];
162
+ // Show rows: the full reviewed mode set.
163
+ const DEFAULT_MODE_SHOW_SELECT = [...KNOWN_MODE_FIELDS];
164
+ // Compact template rows: identifier and name.
165
+ const DEFAULT_TEMPLATE_LIST_SELECT = ["id", "name"];
166
+ // Show rows: the full reviewed template set.
167
+ const DEFAULT_TEMPLATE_SHOW_SELECT = [...KNOWN_TEMPLATE_FIELDS];
168
+ // Compact authentication-context rows: identifier and name.
169
+ const DEFAULT_AUTH_CONTEXT_LIST_SELECT = ["id", "displayName"];
170
+ // Show rows: the full reviewed authentication-context set.
171
+ const DEFAULT_AUTH_CONTEXT_SHOW_SELECT = [...KNOWN_AUTH_CONTEXT_FIELDS];
172
+ // Compact deleted-policy rows: identifier, name, enforcement state and the
173
+ // soft-delete timestamp, which is the point of the deleted collection.
174
+ const DEFAULT_DELETED_POLICY_LIST_SELECT = ["id", "displayName", "state", "deletedDateTime"];
175
+ // Show rows: the full reviewed deleted-policy set.
176
+ const DEFAULT_DELETED_POLICY_SHOW_SELECT = [...KNOWN_DELETED_POLICY_FIELDS];
177
+ // Compact deleted-location rows: identifier, name and deletion timestamp;
178
+ // the kind rides as @odata.type.
179
+ const DEFAULT_DELETED_LOCATION_LIST_SELECT = ["id", "displayName", "deletedDateTime"];
180
+ // Show rows: the full reviewed deleted-location set.
181
+ const DEFAULT_DELETED_LOCATION_SHOW_SELECT = [...KNOWN_DELETED_LOCATION_FIELDS];
182
+ // Policy.Read.All covers policies, named locations and templates in both modes.
62
183
  export const DEFAULT_DELEGATED_SCOPES = ["https://graph.microsoft.com/Policy.Read.All"];
184
+ // Policy.Read.AuthenticationMethod is the least-privileged read for the
185
+ // authentication-strength family in both modes.
186
+ export const DEFAULT_STRENGTH_SCOPES = ["https://graph.microsoft.com/Policy.Read.AuthenticationMethod"];
187
+ // AuthenticationContext.Read.All is the least-privileged read for the
188
+ // authentication context class references in both modes.
189
+ export const DEFAULT_AUTH_CONTEXT_SCOPES = ["https://graph.microsoft.com/AuthenticationContext.Read.All"];
63
190
  const TRUNCATE_AT = 500;
64
191
  function fieldList(raw, flag, known, knownList, noun, help) {
65
192
  const parts = String(raw)
@@ -81,7 +208,7 @@ function fieldList(raw, flag, known, knownList, noun, help) {
81
208
  }
82
209
  return fields;
83
210
  }
84
- function scopesFor(flags, profile, help) {
211
+ function scopesFor(flags, profile, help, defaults = DEFAULT_DELEGATED_SCOPES) {
85
212
  if (profile.mode === "application") {
86
213
  if (flags.scopes !== undefined) {
87
214
  throw new AxiError("Application profiles use the configured Graph .default audience; delegated scopes are unavailable", "VALIDATION_ERROR", [help, "mg-axi profile show --profile <name>"]);
@@ -89,7 +216,7 @@ function scopesFor(flags, profile, help) {
89
216
  return undefined;
90
217
  }
91
218
  if (flags.scopes === undefined)
92
- return [...DEFAULT_DELEGATED_SCOPES];
219
+ return [...defaults];
93
220
  const scopes = String(flags.scopes)
94
221
  .split(",")
95
222
  .map(scope => scope.trim())
@@ -180,6 +307,39 @@ const LOCATION_DENIAL_HINTS = [
180
307
  "Named-location reads need Policy.Read.All plus a supported directory role: Conditional Access Administrator, Global Reader, Global Secure Access Administrator, Security Administrator or Security Reader for delegated access, or admin-consented Policy.Read.All for application access",
181
308
  "Conditional Access and named locations need P1; risk-based Conditional Access needs P2",
182
309
  ];
310
+ // The authentication-strength family (strength policies, combination
311
+ // configurations, authentication method modes) reads the least-privileged
312
+ // Policy.Read.AuthenticationMethod instead of Policy.Read.All, with the
313
+ // narrower Conditional Access Administrator / Security Administrator /
314
+ // Security Reader role set.
315
+ const STRENGTH_DENIAL_HINTS = [
316
+ "Authentication-strength reads need Policy.Read.AuthenticationMethod plus a supported directory role: Conditional Access Administrator, Security Administrator or Security Reader for delegated access, or admin-consented Policy.Read.AuthenticationMethod for application access",
317
+ "Conditional Access needs P1",
318
+ ];
319
+ const TEMPLATE_DENIAL_HINTS = [
320
+ "Conditional Access template reads need Policy.Read.All plus a supported directory role: Conditional Access Administrator, Global Reader, Global Secure Access Administrator, Security Administrator or Security Reader for delegated access, or admin-consented Policy.Read.All for application access",
321
+ "Conditional Access needs P1",
322
+ ];
323
+ // Authentication-context reads carry the least-privileged
324
+ // AuthenticationContext.Read.All with the broad Conditional Access role
325
+ // set shared with policies, named locations and templates.
326
+ const AUTH_CONTEXT_DENIAL_HINTS = [
327
+ "Authentication-context reads need AuthenticationContext.Read.All plus a supported directory role: Conditional Access Administrator, Global Reader, Global Secure Access Administrator, Security Administrator or Security Reader for delegated access, or admin-consented AuthenticationContext.Read.All for application access",
328
+ "Conditional Access needs P1",
329
+ ];
330
+ // Deleted policy and named-location reads keep Policy.Read.All with the
331
+ // broad Conditional Access role set; restore is a beta-only mutation, so
332
+ // denial guidance never offers it as a next step.
333
+ const DELETED_POLICY_DENIAL_HINTS = [
334
+ "Deleted-policy reads need Policy.Read.All plus a supported directory role: Conditional Access Administrator, Global Reader, Global Secure Access Administrator, Security Administrator or Security Reader for delegated access, or admin-consented Policy.Read.All for application access",
335
+ "Deleted policies restore within 30 days; restore is a beta-only mutation outside mg-axi scope",
336
+ "Conditional Access needs P1",
337
+ ];
338
+ const DELETED_LOCATION_DENIAL_HINTS = [
339
+ "Deleted named-location reads need Policy.Read.All plus a supported directory role: Conditional Access Administrator, Global Reader, Global Secure Access Administrator, Security Administrator or Security Reader for delegated access, or admin-consented Policy.Read.All for application access",
340
+ "Deleted named locations restore within 30 days; restore is a beta-only mutation outside mg-axi scope",
341
+ "Conditional Access and named locations need P1",
342
+ ];
183
343
  const POLICY_LIST = {
184
344
  command: "entra conditional-access policy list",
185
345
  key: "policies",
@@ -188,6 +348,7 @@ const POLICY_LIST = {
188
348
  known: POLICY_KNOWN,
189
349
  knownList: KNOWN_POLICY_FIELDS,
190
350
  defaultSelect: DEFAULT_POLICY_LIST_SELECT,
351
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
191
352
  denialHints: POLICY_DENIAL_HINTS,
192
353
  showHint: "mg-axi entra conditional-access policy show --id <policy-id>",
193
354
  emptyHint: "0 conditional-access policies matched; the absence of results is the answer, not an error",
@@ -201,6 +362,7 @@ const LOCATION_LIST = {
201
362
  known: LOCATION_KNOWN,
202
363
  knownList: KNOWN_LOCATION_FIELDS,
203
364
  defaultSelect: DEFAULT_LOCATION_LIST_SELECT,
365
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
204
366
  denialHints: LOCATION_DENIAL_HINTS,
205
367
  showHint: "mg-axi entra conditional-access named-location show --id <named-location-id>",
206
368
  emptyHint: "0 named locations matched; the absence of results is the answer, not an error",
@@ -212,12 +374,19 @@ async function listCollection(shape, session, flags, profile, operation, help, p
212
374
  throw new AxiError("--cursor needs the opaque cursor from a partial result", "VALIDATION_ERROR", [help]);
213
375
  const savedSelect = cursor === undefined ? undefined : session.cursorQuery(operation, cursor).$select;
214
376
  const { select, fields } = selectedFields(flags, savedSelect === undefined ? shape.defaultSelect : fieldList(savedSelect, "select", shape.known, shape.knownList, shape.noun, help), shape.known, shape.knownList, shape.noun, help);
215
- const scopes = scopesFor(flags, profile, help);
377
+ const scopes = scopesFor(flags, profile, help, shape.defaultScopes);
216
378
  const full = flags.full === true;
217
379
  const query = { $select: select.join(",") };
218
380
  if (flags.filter !== undefined)
219
381
  query.$filter = String(flags.filter);
220
382
  const args = { profile, operation, query, scopes };
383
+ if (shape.parent !== undefined) {
384
+ const raw = flags[shape.parent.flag];
385
+ if (raw === undefined || !String(raw).trim()) {
386
+ throw new AxiError(`--${shape.parent.flag} needs the ${shape.parent.label}`, "VALIDATION_ERROR", [help]);
387
+ }
388
+ args.params = { [shape.parent.param]: String(raw) };
389
+ }
221
390
  if (cursor !== undefined)
222
391
  args.cursor = cursor;
223
392
  if (flags.all === true) {
@@ -273,6 +442,7 @@ const POLICY_SHOW = {
273
442
  known: POLICY_KNOWN,
274
443
  knownList: KNOWN_POLICY_FIELDS,
275
444
  defaultSelect: DEFAULT_POLICY_SHOW_SELECT,
445
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
276
446
  denialHints: POLICY_DENIAL_HINTS,
277
447
  preserveType: false,
278
448
  };
@@ -284,17 +454,26 @@ const LOCATION_SHOW = {
284
454
  known: LOCATION_KNOWN,
285
455
  knownList: KNOWN_LOCATION_FIELDS,
286
456
  defaultSelect: DEFAULT_LOCATION_SHOW_SELECT,
457
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
287
458
  denialHints: LOCATION_DENIAL_HINTS,
288
459
  preserveType: true,
289
460
  };
290
461
  async function showSingle(shape, session, flags, profile, operation, help, profileName) {
291
462
  const { select, fields } = selectedFields(flags, shape.defaultSelect, shape.known, shape.knownList, shape.noun, help);
292
- const scopes = scopesFor(flags, profile, help);
463
+ const scopes = scopesFor(flags, profile, help, shape.defaultScopes);
293
464
  const full = flags.full === true;
465
+ const params = { [shape.param]: String(flags.id) };
466
+ if (shape.parent !== undefined) {
467
+ const raw = flags[shape.parent.flag];
468
+ if (raw === undefined || !String(raw).trim()) {
469
+ throw new AxiError(`--${shape.parent.flag} needs the ${shape.parent.label}`, "VALIDATION_ERROR", [help]);
470
+ }
471
+ params[shape.parent.param] = String(raw);
472
+ }
294
473
  const raw = await withGuidance(shape.denialHints, () => session.execute({
295
474
  profile,
296
475
  operation,
297
- params: { [shape.param]: String(flags.id) },
476
+ params,
298
477
  query: { $select: select.join(",") },
299
478
  scopes,
300
479
  }));
@@ -320,3 +499,229 @@ export async function listNamedLocations(session, flags, profile, operation, hel
320
499
  export async function showNamedLocation(session, flags, profile, operation, help, profileName) {
321
500
  return showSingle(LOCATION_SHOW, session, flags, profile, operation, help, profileName);
322
501
  }
502
+ const STRENGTH_LIST = {
503
+ command: "entra conditional-access auth-strength-policy list",
504
+ key: "authStrengthPolicies",
505
+ noun: "auth-strength-policy",
506
+ known: STRENGTH_KNOWN,
507
+ knownList: KNOWN_STRENGTH_FIELDS,
508
+ defaultSelect: DEFAULT_STRENGTH_LIST_SELECT,
509
+ defaultScopes: DEFAULT_STRENGTH_SCOPES,
510
+ denialHints: STRENGTH_DENIAL_HINTS,
511
+ showHint: "mg-axi entra conditional-access auth-strength-policy show --id <auth-strength-policy-id>",
512
+ emptyHint: "0 authentication-strength policies matched; the absence of results is the answer, not an error",
513
+ preserveType: false,
514
+ plural: "auth-strength policies",
515
+ };
516
+ const COMBO_LIST = {
517
+ command: "entra conditional-access combination-configuration list",
518
+ key: "combinationConfigurations",
519
+ noun: "combination-configuration",
520
+ known: COMBO_KNOWN,
521
+ knownList: KNOWN_COMBO_FIELDS,
522
+ defaultSelect: DEFAULT_COMBO_LIST_SELECT,
523
+ defaultScopes: DEFAULT_STRENGTH_SCOPES,
524
+ denialHints: STRENGTH_DENIAL_HINTS,
525
+ showHint: "mg-axi entra conditional-access combination-configuration show --policy <auth-strength-policy-id> --id <combination-configuration-id>",
526
+ emptyHint: "0 combination configurations matched; the absence of results is the answer, not an error",
527
+ preserveType: true,
528
+ parent: { param: "authenticationStrengthPolicy-id", flag: "policy", label: "authentication-strength policy ID owning the combination configurations" },
529
+ plural: "combination configurations",
530
+ };
531
+ const MODE_LIST = {
532
+ command: "entra conditional-access auth-method-mode list",
533
+ key: "authMethodModes",
534
+ noun: "auth-method-mode",
535
+ known: MODE_KNOWN,
536
+ knownList: KNOWN_MODE_FIELDS,
537
+ defaultSelect: DEFAULT_MODE_LIST_SELECT,
538
+ defaultScopes: DEFAULT_STRENGTH_SCOPES,
539
+ denialHints: STRENGTH_DENIAL_HINTS,
540
+ showHint: "mg-axi entra conditional-access auth-method-mode show --id <auth-method-mode-id>",
541
+ emptyHint: "0 authentication method modes matched; the absence of results is the answer, not an error",
542
+ preserveType: false,
543
+ plural: "auth-method modes",
544
+ };
545
+ const TEMPLATE_LIST = {
546
+ command: "entra conditional-access template list",
547
+ key: "templates",
548
+ noun: "template",
549
+ known: TEMPLATE_KNOWN,
550
+ knownList: KNOWN_TEMPLATE_FIELDS,
551
+ defaultSelect: DEFAULT_TEMPLATE_LIST_SELECT,
552
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
553
+ denialHints: TEMPLATE_DENIAL_HINTS,
554
+ showHint: "mg-axi entra conditional-access template show --id <template-id>",
555
+ emptyHint: "0 Conditional Access templates matched; the absence of results is the answer, not an error",
556
+ preserveType: false,
557
+ plural: "templates",
558
+ };
559
+ const STRENGTH_SHOW = {
560
+ command: "entra conditional-access auth-strength-policy show",
561
+ key: "authStrengthPolicy",
562
+ param: "authenticationStrengthPolicy-id",
563
+ noun: "auth-strength-policy",
564
+ known: STRENGTH_KNOWN,
565
+ knownList: KNOWN_STRENGTH_FIELDS,
566
+ defaultSelect: DEFAULT_STRENGTH_SHOW_SELECT,
567
+ defaultScopes: DEFAULT_STRENGTH_SCOPES,
568
+ denialHints: STRENGTH_DENIAL_HINTS,
569
+ preserveType: false,
570
+ };
571
+ const COMBO_SHOW = {
572
+ command: "entra conditional-access combination-configuration show",
573
+ key: "combinationConfiguration",
574
+ param: "authenticationCombinationConfiguration-id",
575
+ noun: "combination-configuration",
576
+ known: COMBO_KNOWN,
577
+ knownList: KNOWN_COMBO_FIELDS,
578
+ defaultSelect: DEFAULT_COMBO_SHOW_SELECT,
579
+ defaultScopes: DEFAULT_STRENGTH_SCOPES,
580
+ denialHints: STRENGTH_DENIAL_HINTS,
581
+ preserveType: true,
582
+ parent: { param: "authenticationStrengthPolicy-id", flag: "policy", label: "authentication-strength policy ID owning the combination configuration" },
583
+ };
584
+ const MODE_SHOW = {
585
+ command: "entra conditional-access auth-method-mode show",
586
+ key: "authMethodMode",
587
+ param: "authenticationMethodModeDetail-id",
588
+ noun: "auth-method-mode",
589
+ known: MODE_KNOWN,
590
+ knownList: KNOWN_MODE_FIELDS,
591
+ defaultSelect: DEFAULT_MODE_SHOW_SELECT,
592
+ defaultScopes: DEFAULT_STRENGTH_SCOPES,
593
+ denialHints: STRENGTH_DENIAL_HINTS,
594
+ preserveType: false,
595
+ };
596
+ const TEMPLATE_SHOW = {
597
+ command: "entra conditional-access template show",
598
+ key: "template",
599
+ param: "conditionalAccessTemplate-id",
600
+ noun: "template",
601
+ known: TEMPLATE_KNOWN,
602
+ knownList: KNOWN_TEMPLATE_FIELDS,
603
+ defaultSelect: DEFAULT_TEMPLATE_SHOW_SELECT,
604
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
605
+ denialHints: TEMPLATE_DENIAL_HINTS,
606
+ preserveType: false,
607
+ };
608
+ export async function listAuthStrengthPolicies(session, flags, profile, operation, help, profileName) {
609
+ return listCollection(STRENGTH_LIST, session, flags, profile, operation, help, profileName);
610
+ }
611
+ export async function showAuthStrengthPolicy(session, flags, profile, operation, help, profileName) {
612
+ return showSingle(STRENGTH_SHOW, session, flags, profile, operation, help, profileName);
613
+ }
614
+ export async function listCombinationConfigurations(session, flags, profile, operation, help, profileName) {
615
+ return listCollection(COMBO_LIST, session, flags, profile, operation, help, profileName);
616
+ }
617
+ export async function showCombinationConfiguration(session, flags, profile, operation, help, profileName) {
618
+ return showSingle(COMBO_SHOW, session, flags, profile, operation, help, profileName);
619
+ }
620
+ export async function listAuthMethodModes(session, flags, profile, operation, help, profileName) {
621
+ return listCollection(MODE_LIST, session, flags, profile, operation, help, profileName);
622
+ }
623
+ export async function showAuthMethodMode(session, flags, profile, operation, help, profileName) {
624
+ return showSingle(MODE_SHOW, session, flags, profile, operation, help, profileName);
625
+ }
626
+ export async function listTemplates(session, flags, profile, operation, help, profileName) {
627
+ return listCollection(TEMPLATE_LIST, session, flags, profile, operation, help, profileName);
628
+ }
629
+ export async function showTemplate(session, flags, profile, operation, help, profileName) {
630
+ return showSingle(TEMPLATE_SHOW, session, flags, profile, operation, help, profileName);
631
+ }
632
+ const AUTH_CONTEXT_LIST = {
633
+ command: "entra conditional-access auth-context list",
634
+ key: "authContexts",
635
+ noun: "auth-context",
636
+ plural: "auth contexts",
637
+ known: AUTH_CONTEXT_KNOWN,
638
+ knownList: KNOWN_AUTH_CONTEXT_FIELDS,
639
+ defaultSelect: DEFAULT_AUTH_CONTEXT_LIST_SELECT,
640
+ defaultScopes: DEFAULT_AUTH_CONTEXT_SCOPES,
641
+ denialHints: AUTH_CONTEXT_DENIAL_HINTS,
642
+ showHint: "mg-axi entra conditional-access auth-context show --id <auth-context-id>",
643
+ emptyHint: "0 authentication context class references matched; the absence of results is the answer, not an error",
644
+ preserveType: false,
645
+ };
646
+ const DELETED_POLICY_LIST = {
647
+ command: "entra conditional-access deleted-policy list",
648
+ key: "deletedPolicies",
649
+ noun: "deleted-policy",
650
+ plural: "deleted policies",
651
+ known: DELETED_POLICY_KNOWN,
652
+ knownList: KNOWN_DELETED_POLICY_FIELDS,
653
+ defaultSelect: DEFAULT_DELETED_POLICY_LIST_SELECT,
654
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
655
+ denialHints: DELETED_POLICY_DENIAL_HINTS,
656
+ showHint: "mg-axi entra conditional-access deleted-policy show --id <deleted-policy-id>",
657
+ emptyHint: "0 deleted policies matched; the absence of results is the answer, not an error",
658
+ preserveType: false,
659
+ };
660
+ const DELETED_LOCATION_LIST = {
661
+ command: "entra conditional-access deleted-named-location list",
662
+ key: "deletedNamedLocations",
663
+ noun: "deleted-named-location",
664
+ plural: "deleted named locations",
665
+ known: DELETED_LOCATION_KNOWN,
666
+ knownList: KNOWN_DELETED_LOCATION_FIELDS,
667
+ defaultSelect: DEFAULT_DELETED_LOCATION_LIST_SELECT,
668
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
669
+ denialHints: DELETED_LOCATION_DENIAL_HINTS,
670
+ showHint: "mg-axi entra conditional-access deleted-named-location show --id <deleted-named-location-id>",
671
+ emptyHint: "0 deleted named locations matched; the absence of results is the answer, not an error",
672
+ preserveType: true,
673
+ };
674
+ const AUTH_CONTEXT_SHOW = {
675
+ command: "entra conditional-access auth-context show",
676
+ key: "authContext",
677
+ param: "authenticationContextClassReference-id",
678
+ noun: "auth-context",
679
+ known: AUTH_CONTEXT_KNOWN,
680
+ knownList: KNOWN_AUTH_CONTEXT_FIELDS,
681
+ defaultSelect: DEFAULT_AUTH_CONTEXT_SHOW_SELECT,
682
+ defaultScopes: DEFAULT_AUTH_CONTEXT_SCOPES,
683
+ denialHints: AUTH_CONTEXT_DENIAL_HINTS,
684
+ preserveType: false,
685
+ };
686
+ const DELETED_POLICY_SHOW = {
687
+ command: "entra conditional-access deleted-policy show",
688
+ key: "deletedPolicy",
689
+ param: "conditionalAccessPolicy-id",
690
+ noun: "deleted-policy",
691
+ known: DELETED_POLICY_KNOWN,
692
+ knownList: KNOWN_DELETED_POLICY_FIELDS,
693
+ defaultSelect: DEFAULT_DELETED_POLICY_SHOW_SELECT,
694
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
695
+ denialHints: DELETED_POLICY_DENIAL_HINTS,
696
+ preserveType: false,
697
+ };
698
+ const DELETED_LOCATION_SHOW = {
699
+ command: "entra conditional-access deleted-named-location show",
700
+ key: "deletedNamedLocation",
701
+ param: "namedLocation-id",
702
+ noun: "deleted-named-location",
703
+ known: DELETED_LOCATION_KNOWN,
704
+ knownList: KNOWN_DELETED_LOCATION_FIELDS,
705
+ defaultSelect: DEFAULT_DELETED_LOCATION_SHOW_SELECT,
706
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
707
+ denialHints: DELETED_LOCATION_DENIAL_HINTS,
708
+ preserveType: true,
709
+ };
710
+ export async function listAuthContexts(session, flags, profile, operation, help, profileName) {
711
+ return listCollection(AUTH_CONTEXT_LIST, session, flags, profile, operation, help, profileName);
712
+ }
713
+ export async function showAuthContext(session, flags, profile, operation, help, profileName) {
714
+ return showSingle(AUTH_CONTEXT_SHOW, session, flags, profile, operation, help, profileName);
715
+ }
716
+ export async function listDeletedPolicies(session, flags, profile, operation, help, profileName) {
717
+ return listCollection(DELETED_POLICY_LIST, session, flags, profile, operation, help, profileName);
718
+ }
719
+ export async function showDeletedPolicy(session, flags, profile, operation, help, profileName) {
720
+ return showSingle(DELETED_POLICY_SHOW, session, flags, profile, operation, help, profileName);
721
+ }
722
+ export async function listDeletedNamedLocations(session, flags, profile, operation, help, profileName) {
723
+ return listCollection(DELETED_LOCATION_LIST, session, flags, profile, operation, help, profileName);
724
+ }
725
+ export async function showDeletedNamedLocation(session, flags, profile, operation, help, profileName) {
726
+ return showSingle(DELETED_LOCATION_SHOW, session, flags, profile, operation, help, profileName);
727
+ }
@@ -1,4 +1,5 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
+ import { listTotals } from "./list-totals.js";
2
3
  // EXT-01 directory federation-configurations subfamily: the read mapping
3
4
  // behind `mg-axi entra federation-configuration list/show/count/available-types`.
4
5
  // Operation construction stays beside its command; the shared session owns
@@ -203,20 +204,22 @@ export async function listFederationConfigurations(session, flags, profile, oper
203
204
  if (!result.complete) {
204
205
  return {
205
206
  federationConfigurations: configurations,
206
- count: { returned: configurations.length, complete: false, reason: result.reason },
207
+ ...listTotals(configurations.length, result.total, "federation configurations", false),
208
+ complete: false,
209
+ reason: result.reason,
207
210
  cursor: result.cursor,
208
211
  help: [...truncationHints, `Resume losslessly with the same flags plus --cursor <cursor-from-output> ${profileHint(profileName)}`, showHint, CERT_BLOB_NOTE, WORKFORCE_NOTE],
209
212
  };
210
213
  }
211
- const count = { returned: configurations.length, complete: true };
212
214
  if (!configurations.length) {
213
215
  return {
214
216
  federationConfigurations: configurations,
215
- count,
217
+ ...listTotals(configurations.length, result.total, "federation configurations", true),
218
+ complete: true,
216
219
  help: ["0 federation configurations matched; no SAML or WS-Fed federation is configured for the tenant, or no row passed --filter", CERT_BLOB_NOTE, WORKFORCE_NOTE],
217
220
  };
218
221
  }
219
- return { federationConfigurations: configurations, count, help: [...truncationHints, showHint, CERT_BLOB_NOTE, WORKFORCE_NOTE] };
222
+ return { federationConfigurations: configurations, ...listTotals(configurations.length, result.total, "federation configurations", true), complete: true, help: [...truncationHints, showHint, CERT_BLOB_NOTE, WORKFORCE_NOTE] };
220
223
  }
221
224
  export async function showFederationConfiguration(session, flags, profile, operation, help, profileName) {
222
225
  const { select, fields } = selectedFields(flags, DEFAULT_SHOW_SELECT, help);
@@ -1,4 +1,5 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
+ import { listTotals } from "./list-totals.js";
2
3
  // READ-08: the Entra consent-grant read mapping behind
3
4
  // `mg-axi entra service-principal oauth2-grant list` and
4
5
  // `mg-axi entra service-principal app-role-assignment list` for a named
@@ -181,6 +182,7 @@ const APP_ROLE_DENIAL_HINTS = [
181
182
  const OAUTH2_GRANT_LIST = {
182
183
  command: "entra service-principal oauth2-grant list",
183
184
  key: "oauth2PermissionGrants",
185
+ noun: "oauth2 grants",
184
186
  known: GRANT_KNOWN,
185
187
  knownList: KNOWN_GRANT_FIELDS,
186
188
  defaultSelect: DEFAULT_GRANT_SELECT,
@@ -192,6 +194,7 @@ const OAUTH2_GRANT_LIST = {
192
194
  const APP_ROLE_LIST = {
193
195
  command: "entra service-principal app-role-assignment list",
194
196
  key: "appRoleAssignments",
197
+ noun: "app role assignments",
195
198
  known: APP_ROLE_KNOWN,
196
199
  knownList: KNOWN_APP_ROLE_FIELDS,
197
200
  defaultSelect: DEFAULT_APP_ROLE_SELECT,
@@ -253,16 +256,17 @@ async function listCollection(shape, session, flags, profile, operation, help, p
253
256
  if (!result.complete) {
254
257
  return {
255
258
  [shape.key]: rows,
256
- count: { returned: rows.length, complete: false, reason: result.reason },
259
+ ...listTotals(rows.length, result.total, shape.noun, false),
260
+ complete: false,
261
+ reason: result.reason,
257
262
  cursor: result.cursor,
258
263
  help: [...truncationHints, `Resume losslessly with the same flags plus --cursor <cursor-from-output> ${profileHint(profileName)}`, ...standing],
259
264
  };
260
265
  }
261
- const count = { returned: rows.length, complete: true };
262
266
  if (!rows.length) {
263
- return { [shape.key]: rows, count, help: [shape.emptyNote, ...standing] };
267
+ return { [shape.key]: rows, ...listTotals(rows.length, result.total, shape.noun, true), complete: true, help: [shape.emptyNote, ...standing] };
264
268
  }
265
- return { [shape.key]: rows, count, help: [...truncationHints, ...standing] };
269
+ return { [shape.key]: rows, ...listTotals(rows.length, result.total, shape.noun, true), complete: true, help: [...truncationHints, ...standing] };
266
270
  }
267
271
  export async function listOAuth2Grants(session, flags, profile, operation, help, profileName) {
268
272
  return listCollection(OAUTH2_GRANT_LIST, session, flags, profile, operation, help, profileName);
@@ -1,4 +1,5 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
+ import { listTotals } from "./list-totals.js";
2
3
  // EXT-01 group lifecycle subfamily: the read mapping behind
3
4
  // `mg-axi entra group-lifecycle-policy list/show/count` and
4
5
  // `mg-axi entra group-setting-template list/show/count`. Operation
@@ -241,20 +242,22 @@ export async function listLifecyclePolicies(session, flags, profile, operation,
241
242
  if (!result.complete) {
242
243
  return {
243
244
  groupLifecyclePolicies: policies,
244
- count: { returned: policies.length, complete: false, reason: result.reason },
245
+ ...listTotals(policies.length, result.total, "lifecycle policies", false),
246
+ complete: false,
247
+ reason: result.reason,
245
248
  cursor: result.cursor,
246
249
  help: [...truncationHints, resumeHint(profileName), showHint],
247
250
  };
248
251
  }
249
- const count = { returned: policies.length, complete: true };
250
252
  if (!policies.length) {
251
253
  return {
252
254
  groupLifecyclePolicies: policies,
253
- count,
255
+ ...listTotals(policies.length, result.total, "lifecycle policies", true),
256
+ complete: true,
254
257
  help: ["0 lifecycle policies matched; the absence of results is the answer, not an error"],
255
258
  };
256
259
  }
257
- return { groupLifecyclePolicies: policies, count, help: [...truncationHints, showHint] };
260
+ return { groupLifecyclePolicies: policies, ...listTotals(policies.length, result.total, "lifecycle policies", true), complete: true, help: [...truncationHints, showHint] };
258
261
  }
259
262
  export async function showLifecyclePolicy(session, flags, profile, operation, help, profileName) {
260
263
  const { select, fields } = selectedFields(flags, KNOWN_LIFECYCLE, KNOWN_LIFECYCLE_FIELDS, DEFAULT_LIFECYCLE_SHOW_SELECT, help);
@@ -305,20 +308,22 @@ export async function listSettingTemplates(session, flags, profile, operation, h
305
308
  if (!result.complete) {
306
309
  return {
307
310
  groupSettingTemplates: templates,
308
- count: { returned: templates.length, complete: false, reason: result.reason },
311
+ ...listTotals(templates.length, result.total, "setting templates", false),
312
+ complete: false,
313
+ reason: result.reason,
309
314
  cursor: result.cursor,
310
315
  help: [...truncationHints, resumeHint(profileName), showHint, filterNote],
311
316
  };
312
317
  }
313
- const count = { returned: templates.length, complete: true };
314
318
  if (!templates.length) {
315
319
  return {
316
320
  groupSettingTemplates: templates,
317
- count,
321
+ ...listTotals(templates.length, result.total, "setting templates", true),
322
+ complete: true,
318
323
  help: [filterNote, "0 setting templates matched; the absence of results is the answer, not an error"],
319
324
  };
320
325
  }
321
- return { groupSettingTemplates: templates, count, help: [...truncationHints, showHint, filterNote] };
326
+ return { groupSettingTemplates: templates, ...listTotals(templates.length, result.total, "setting templates", true), complete: true, help: [...truncationHints, showHint, filterNote] };
322
327
  }
323
328
  export async function showSettingTemplate(session, flags, profile, operation, help, profileName) {
324
329
  const { select, fields } = selectedFields(flags, KNOWN_TEMPLATES, KNOWN_TEMPLATE_FIELDS, DEFAULT_TEMPLATE_SHOW_SELECT, help);