@knowttl/mg-axi 0.1.1 → 0.1.2

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.
Files changed (40) hide show
  1. package/README.md +149 -38
  2. package/dist/api.js +134 -8
  3. package/dist/bin/mg-axi-hook.js +31 -0
  4. package/dist/catalogue.js +390 -2
  5. package/dist/cli.js +116 -8
  6. package/dist/docs.js +45 -5
  7. package/dist/doctor.js +2 -2
  8. package/dist/entra-access-reviews.js +11 -4
  9. package/dist/entra-apps.js +13 -8
  10. package/dist/entra-audit-logs.js +8 -4
  11. package/dist/entra-auth-methods.js +8 -4
  12. package/dist/entra-certificate-auth.js +12 -8
  13. package/dist/entra-conditional-access.js +9 -4
  14. package/dist/entra-contacts.js +19 -12
  15. package/dist/entra-contracts.js +7 -4
  16. package/dist/entra-custom-security-attributes.js +19 -12
  17. package/dist/entra-data-policy-operations.js +7 -4
  18. package/dist/entra-delegated-admin.js +55 -6
  19. package/dist/entra-deleted-items.js +6 -4
  20. package/dist/entra-directory-objects.js +6 -4
  21. package/dist/entra-directory.js +19 -12
  22. package/dist/entra-domains.js +13 -8
  23. package/dist/entra-entitlement-management.js +12 -4
  24. package/dist/entra-group-pim.js +431 -0
  25. package/dist/entra-groups.js +13 -8
  26. package/dist/entra-lifecycle-workflows.js +136 -9
  27. package/dist/entra-organization.js +13 -8
  28. package/dist/entra-risk-prevention.js +436 -0
  29. package/dist/entra-risk.js +213 -5
  30. package/dist/entra-roles.js +10 -4
  31. package/dist/entra-subscriptions.js +6 -4
  32. package/dist/entra-users.js +7 -4
  33. package/dist/graph-session.js +29 -5
  34. package/dist/hook.js +84 -0
  35. package/dist/list-totals.js +52 -0
  36. package/dist/setup.js +5 -0
  37. package/docs/coverage.md +79 -13
  38. package/inventory/operations.json +2 -2
  39. package/package.json +4 -3
  40. package/skills/mg-axi/SKILL.md +56 -1
@@ -1,8 +1,9 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
2
  // EXT-02 lifecycle-workflows reads: the read-only workflow, workflow
3
3
  // template, task definition and tenant-settings first part, the
4
- // second-part top-level run and user/subject processing-result reads and
5
- // the third-part task-report reads. See
4
+ // second-part top-level run and user/subject processing-result reads, the
5
+ // third-part task-report reads and the fourth-part run-nested processing
6
+ // results. See
6
7
  // README.md for the supported commands and usage. Operation construction
7
8
  // stays beside its command; the shared session owns URLs, credentials,
8
9
  // paging, retries and error translation, and the SDK owns TOON rendering.
@@ -14,11 +15,13 @@ import { AxiError } from "axi-sdk-js";
14
15
  // workflowtemplate-get, lifecycleworkflowscontainer-list-taskdefinitions,
15
16
  // taskdefinition-get, lifecyclemanagementsettings-get, workflow-list-runs,
16
17
  // run-get, workflow-list-userprocessingresults, userprocessingresult-get,
17
- // run-list-subjectprocessingresults, subjectprocessingresult-get,
18
+ // run-list-userprocessingresults, run-list-subjectprocessingresults,
19
+ // subjectprocessingresult-get, run-list-taskprocessingresults,
18
20
  // workflow-list-taskreports and task-get operation documentation and the
19
21
  // reviewed resource property sets for workflow, workflowTemplate,
20
22
  // taskDefinition, lifecycleManagementSettings, run, userProcessingResult,
21
- // subjectProcessingResult, taskReport and task, on 2026-10-06.
23
+ // subjectProcessingResult, taskProcessingResult, taskReport and task, on
24
+ // 2026-10-06.
22
25
  // Workflow list/show take D/A
23
26
  // LifecycleWorkflows-Workflow.ReadBasic.All as the least privileged choice
24
27
  // (LifecycleWorkflows-Workflow.Read.All or LifecycleWorkflows.Read.All for
@@ -46,6 +49,15 @@ import { AxiError } from "axi-sdk-js";
46
49
  // taskDefinition navigations return the related objects whole (task
47
50
  // arguments and definition parameters ride along as reviewed fields) while
48
51
  // taskProcessingResults, the subject link and every $expand stay out.
52
+ // The run-nested user, subject and task processing-result singles share
53
+ // their top-level query contracts: the userprocessingresult-get page covers
54
+ // the run-nested user single and documents $select, the
55
+ // subjectprocessingresult-get page covers the run-nested subject single
56
+ // and documents $expand only (whole rows), and the run-nested task single
57
+ // has no operation-level page (like the taskReport-nested singles), so it
58
+ // arrives whole with --select refused. The taskProcessingResult subject,
59
+ // task and workflowSubject navigations stay out with $expand: task results
60
+ // are personal data and carry failure detail, never subject identity.
49
61
  // The taskReports list documents $select/$filter (with $orderby/$expand
50
62
  // unreviewed); the taskReport get and the taskReport-nested task and
51
63
  // taskDefinition singles have no operation-level page (the task-get page
@@ -53,14 +65,18 @@ import { AxiError } from "axi-sdk-js";
53
65
  // there), so those three singles arrive whole and are projected locally
54
66
  // with --fields (--select is refused before credentials). Processing results
55
67
  // are personal data: user and subject defaults carry status and counts
56
- // only, never the subject link. Deeper execution detail (run-nested and
57
- // third-level processing results, reprocessed runs, subjects, workflow and
58
- // template tasks, createdBy, lastModifiedBy, previewScope, executionScope,
59
- // versions, insights, deleted items, custom task extensions) needs its own
68
+ // only, never the subject link. Deeper execution detail (third-level
69
+ // processing results under a user or subject result, reprocessed runs,
70
+ // subjects, workflow and template tasks, createdBy, lastModifiedBy,
71
+ // previewScope, executionScope, versions, insights, deleted items, custom
72
+ // task extensions) needs its own
60
73
  // sub-read and stays out, as do the summary functions (bracketed start/end
61
74
  // arguments outside the session-guard function-binding allowlist).
62
75
  // Lifecycle workflows need Microsoft Entra ID Governance or Microsoft Entra
63
- // Suite (every governed user, not only administrators). No mutation (no
76
+ // Suite (every governed user, not only administrators). Run-nested results
77
+ // scope one run's processing outcomes: user results aggregate one user's
78
+ // execution, subject results mirror them for non-user subjects, and task
79
+ // results carry one task's outcome with failure detail. No mutation (no
64
80
  // workflow create/update/delete/activate/run, no settings update) and no
65
81
  // beta.
66
82
  // Workflows expose the configured joiner/mover/leaver automations. Only
@@ -163,6 +179,19 @@ export const KNOWN_SUBJECT_PROCESSING_FIELDS = [
163
179
  "workflowExecutionType",
164
180
  "workflowVersion",
165
181
  ];
182
+ // Task processing results carry one task's outcome within one run.
183
+ // failureReason and processingInfo are human-readable outcome detail (not
184
+ // identity); the subject, task and workflowSubject navigations carry the
185
+ // targeted identity and need $expand, so they stay out with it.
186
+ export const KNOWN_TASK_PROCESSING_FIELDS = [
187
+ "id",
188
+ "completedDateTime",
189
+ "createdDateTime",
190
+ "failureReason",
191
+ "processingInfo",
192
+ "processingStatus",
193
+ "startedDateTime",
194
+ ];
166
195
  // Task reports aggregate one workflow task's processing results within one
167
196
  // run. runId names the owning run; the task, taskDefinition and
168
197
  // taskProcessingResults relationships need their own sub-reads ($expand
@@ -202,6 +231,7 @@ const RUN_KNOWN = new Set(KNOWN_RUN_FIELDS);
202
231
  const USER_PROCESSING_KNOWN = new Set(KNOWN_USER_PROCESSING_FIELDS);
203
232
  const SUBJECT_PROCESSING_KNOWN = new Set(KNOWN_SUBJECT_PROCESSING_FIELDS);
204
233
  const TASK_REPORT_KNOWN = new Set(KNOWN_TASK_REPORT_FIELDS);
234
+ const TASK_PROCESSING_KNOWN = new Set(KNOWN_TASK_PROCESSING_FIELDS);
205
235
  const TASK_KNOWN = new Set(KNOWN_TASK_FIELDS);
206
236
  // Compact rows: identifiers plus category and enablement state.
207
237
  const DEFAULT_WORKFLOW_LIST_SELECT = ["id", "displayName", "category", "isEnabled", "isSchedulingEnabled"];
@@ -221,6 +251,8 @@ const DEFAULT_USER_PROCESSING_LIST_SELECT = ["id", "processingStatus", "failedTa
221
251
  const DEFAULT_USER_PROCESSING_SHOW_SELECT = [...DEFAULT_USER_PROCESSING_LIST_SELECT];
222
252
  const DEFAULT_SUBJECT_PROCESSING_FIELDS = ["id", "subjectType", "processingStatus", "failedTasksCount", "totalTasksCount", "totalUnprocessedTasksCount"];
223
253
  // Compact task-report rows: identifiers plus the owning run and user counts.
254
+ const DEFAULT_TASK_PROCESSING_LIST_SELECT = ["id", "processingStatus", "failureReason"];
255
+ const DEFAULT_TASK_PROCESSING_SHOW_SELECT = [...KNOWN_TASK_PROCESSING_FIELDS];
224
256
  const DEFAULT_TASK_REPORT_LIST_SELECT = ["id", "runId", "processingStatus", "totalUsersCount", "failedUsersCount", "successfulUsersCount"];
225
257
  const DEFAULT_TASK_REPORT_SHOW_SELECT = [...KNOWN_TASK_REPORT_FIELDS];
226
258
  const DEFAULT_TASK_SHOW_SELECT = [...KNOWN_TASK_FIELDS];
@@ -361,6 +393,13 @@ function reportId(flags, help) {
361
393
  }
362
394
  return report;
363
395
  }
396
+ function runId(flags, help) {
397
+ const run = flags.run === undefined ? "" : String(flags.run);
398
+ if (!run.trim()) {
399
+ throw new AxiError("--run needs the parent lifecycle workflow run ID", "VALIDATION_ERROR", [help]);
400
+ }
401
+ return run;
402
+ }
364
403
  const WORKFLOW_LIST = {
365
404
  command: "entra lifecycle workflow list",
366
405
  key: "workflows",
@@ -460,6 +499,49 @@ const TASK_REPORT_LIST = {
460
499
  `Show one task report: mg-axi entra lifecycle task-report show --workflow <workflow-id> --id <report-id> ${profileHint(profileName)}`,
461
500
  ],
462
501
  };
502
+ const RUN_USER_PROCESSING_LIST = {
503
+ command: "entra lifecycle run user-processing-result list",
504
+ key: "userProcessingResults",
505
+ known: USER_PROCESSING_KNOWN,
506
+ knownList: KNOWN_USER_PROCESSING_FIELDS,
507
+ defaultSelect: DEFAULT_USER_PROCESSING_LIST_SELECT,
508
+ defaultScopes: DEFAULT_REPORTS_SCOPES,
509
+ hints: REPORTS_DENIAL_HINTS,
510
+ emptyNote: "0 run user processing results matched; the absence of results is the answer, not an error",
511
+ standing: profileName => [
512
+ "Run user processing results aggregate one user's execution within one run and carry personal data: defaults stay minimal and the subject link is never requested",
513
+ `Show one run user processing result: mg-axi entra lifecycle run user-processing-result show --workflow <workflow-id> --run <run-id> --id <result-id> ${profileHint(profileName)}`,
514
+ ],
515
+ };
516
+ const RUN_SUBJECT_PROCESSING_LIST = {
517
+ command: "entra lifecycle run subject-processing-result list",
518
+ key: "subjectProcessingResults",
519
+ known: SUBJECT_PROCESSING_KNOWN,
520
+ knownList: KNOWN_SUBJECT_PROCESSING_FIELDS,
521
+ defaultSelect: DEFAULT_SUBJECT_PROCESSING_FIELDS,
522
+ defaultScopes: DEFAULT_REPORTS_SCOPES,
523
+ hints: REPORTS_DENIAL_HINTS,
524
+ emptyNote: "0 run subject processing results matched; the absence of results is the answer, not an error",
525
+ queryless: true,
526
+ standing: profileName => [
527
+ "Run subject processing results mirror user results for non-user subjects within one run and carry personal data: rows arrive whole (Graph documents no $select on the list and $expand only on the get) and defaults stay minimal",
528
+ `Show one run subject processing result: mg-axi entra lifecycle run subject-processing-result show --workflow <workflow-id> --run <run-id> --id <result-id> ${profileHint(profileName)}`,
529
+ ],
530
+ };
531
+ const RUN_TASK_PROCESSING_LIST = {
532
+ command: "entra lifecycle run task-processing-result list",
533
+ key: "taskProcessingResults",
534
+ known: TASK_PROCESSING_KNOWN,
535
+ knownList: KNOWN_TASK_PROCESSING_FIELDS,
536
+ defaultSelect: DEFAULT_TASK_PROCESSING_LIST_SELECT,
537
+ defaultScopes: DEFAULT_REPORTS_SCOPES,
538
+ hints: REPORTS_DENIAL_HINTS,
539
+ emptyNote: "0 run task processing results matched; the absence of results is the answer, not an error",
540
+ standing: profileName => [
541
+ "Run task processing results carry one task's outcome within one run with failure detail: a task row never carries its subject, task or workflowSubject navigations; those belong to later slices",
542
+ `Show one run task processing result: mg-axi entra lifecycle run task-processing-result show --workflow <workflow-id> --run <run-id> --id <result-id> ${profileHint(profileName)}`,
543
+ ],
544
+ };
463
545
  async function listCollection(shape, session, flags, profile, operation, help, profileName, params = {}, resumeHint) {
464
546
  const cursor = flags.cursor === undefined ? undefined : String(flags.cursor);
465
547
  if (cursor !== undefined && !cursor.trim())
@@ -671,3 +753,48 @@ export async function showTaskReportTaskDefinition(session, flags, profile, oper
671
753
  const report = reportId(flags, help);
672
754
  return showOne("taskDefinition", "entra lifecycle task-report task-definition show", TASK_DEFINITION_KNOWN, KNOWN_TASK_DEFINITION_FIELDS, DEFAULT_TASK_DEFINITION_SHOW_SELECT, DEFAULT_REPORTS_SCOPES, REPORTS_DENIAL_HINTS, session, flags, profile, operation, help, profileName, { "workflow-id": workflow, "taskReport-id": report }, "Graph returned a malformed task definition body", true);
673
755
  }
756
+ export async function listRunUserProcessingResults(session, flags, profile, operation, help, profileName) {
757
+ const workflow = workflowId(flags, help);
758
+ const run = runId(flags, help);
759
+ return listCollection(RUN_USER_PROCESSING_LIST, session, flags, profile, operation, help, profileName, { "workflow-id": workflow, "run-id": run }, `Resume losslessly with the same --workflow --run and flags plus --cursor - ${profileHint(profileName)} and supply the returned cursor on stdin`);
760
+ }
761
+ export async function showRunUserProcessingResult(session, flags, profile, operation, help, profileName) {
762
+ const workflow = workflowId(flags, help);
763
+ const run = runId(flags, help);
764
+ return showOne("userProcessingResult", "entra lifecycle run user-processing-result show", USER_PROCESSING_KNOWN, KNOWN_USER_PROCESSING_FIELDS, DEFAULT_USER_PROCESSING_SHOW_SELECT, DEFAULT_REPORTS_SCOPES, REPORTS_DENIAL_HINTS, session, flags, profile, operation, help, profileName, { "workflow-id": workflow, "run-id": run, "userProcessingResult-id": String(flags.id) }, "Graph returned a malformed user processing result body");
765
+ }
766
+ export async function countRunUserProcessingResults(session, flags, profile, operation, help, profileName) {
767
+ const workflow = workflowId(flags, help);
768
+ const run = runId(flags, help);
769
+ return countCollection("run user processing result", RUN_USER_PROCESSING_LIST.emptyNote, DEFAULT_REPORTS_SCOPES, REPORTS_DENIAL_HINTS, session, flags, profile, operation, help, profileName, { "workflow-id": workflow, "run-id": run });
770
+ }
771
+ export async function listRunSubjectProcessingResults(session, flags, profile, operation, help, profileName) {
772
+ const workflow = workflowId(flags, help);
773
+ const run = runId(flags, help);
774
+ return listCollection(RUN_SUBJECT_PROCESSING_LIST, session, flags, profile, operation, help, profileName, { "workflow-id": workflow, "run-id": run }, `Resume losslessly with the same --workflow --run and flags plus --cursor - ${profileHint(profileName)} and supply the returned cursor on stdin`);
775
+ }
776
+ export async function showRunSubjectProcessingResult(session, flags, profile, operation, help, profileName) {
777
+ const workflow = workflowId(flags, help);
778
+ const run = runId(flags, help);
779
+ return showOne("subjectProcessingResult", "entra lifecycle run subject-processing-result show", SUBJECT_PROCESSING_KNOWN, KNOWN_SUBJECT_PROCESSING_FIELDS, DEFAULT_SUBJECT_PROCESSING_FIELDS, DEFAULT_REPORTS_SCOPES, REPORTS_DENIAL_HINTS, session, flags, profile, operation, help, profileName, { "workflow-id": workflow, "run-id": run, "subjectProcessingResult-id": String(flags.id) }, "Graph returned a malformed subject processing result body", true);
780
+ }
781
+ export async function countRunSubjectProcessingResults(session, flags, profile, operation, help, profileName) {
782
+ const workflow = workflowId(flags, help);
783
+ const run = runId(flags, help);
784
+ return countCollection("run subject processing result", RUN_SUBJECT_PROCESSING_LIST.emptyNote, DEFAULT_REPORTS_SCOPES, REPORTS_DENIAL_HINTS, session, flags, profile, operation, help, profileName, { "workflow-id": workflow, "run-id": run });
785
+ }
786
+ export async function listRunTaskProcessingResults(session, flags, profile, operation, help, profileName) {
787
+ const workflow = workflowId(flags, help);
788
+ const run = runId(flags, help);
789
+ return listCollection(RUN_TASK_PROCESSING_LIST, session, flags, profile, operation, help, profileName, { "workflow-id": workflow, "run-id": run }, `Resume losslessly with the same --workflow --run and flags plus --cursor - ${profileHint(profileName)} and supply the returned cursor on stdin`);
790
+ }
791
+ export async function showRunTaskProcessingResult(session, flags, profile, operation, help, profileName) {
792
+ const workflow = workflowId(flags, help);
793
+ const run = runId(flags, help);
794
+ return showOne("taskProcessingResult", "entra lifecycle run task-processing-result show", TASK_PROCESSING_KNOWN, KNOWN_TASK_PROCESSING_FIELDS, DEFAULT_TASK_PROCESSING_SHOW_SELECT, DEFAULT_REPORTS_SCOPES, REPORTS_DENIAL_HINTS, session, flags, profile, operation, help, profileName, { "workflow-id": workflow, "run-id": run, "taskProcessingResult-id": String(flags.id) }, "Graph returned a malformed task processing result body", true);
795
+ }
796
+ export async function countRunTaskProcessingResults(session, flags, profile, operation, help, profileName) {
797
+ const workflow = workflowId(flags, help);
798
+ const run = runId(flags, help);
799
+ return countCollection("run task processing result", RUN_TASK_PROCESSING_LIST.emptyNote, DEFAULT_REPORTS_SCOPES, REPORTS_DENIAL_HINTS, session, flags, profile, operation, help, profileName, { "workflow-id": workflow, "run-id": run });
800
+ }
@@ -1,4 +1,5 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
+ import { listTotals } from "./list-totals.js";
2
3
  // EXT-01 organization subfamily: the read mapping behind
3
4
  // `mg-axi entra organization list/show`, the default-branding read behind
4
5
  // `mg-axi entra organization branding show`, and the branding-localization
@@ -290,20 +291,22 @@ export async function listOrganizations(session, flags, profile, operation, help
290
291
  if (!result.complete) {
291
292
  return {
292
293
  organizations,
293
- count: { returned: organizations.length, complete: false, reason: result.reason },
294
+ ...listTotals(organizations.length, result.total, "organizations", false),
295
+ complete: false,
296
+ reason: result.reason,
294
297
  cursor: result.cursor,
295
298
  help: [...truncationHints, resumeHint(profileName), showHint, filterNote],
296
299
  };
297
300
  }
298
- const count = { returned: organizations.length, complete: true };
299
301
  if (!organizations.length) {
300
302
  return {
301
303
  organizations,
302
- count,
304
+ ...listTotals(organizations.length, result.total, "organizations", true),
305
+ complete: true,
303
306
  help: [filterNote, "0 organizations matched; the absence of results is the answer, not an error"],
304
307
  };
305
308
  }
306
- return { organizations, count, help: [...truncationHints, showHint, filterNote] };
309
+ return { organizations, ...listTotals(organizations.length, result.total, "organizations", true), complete: true, help: [...truncationHints, showHint, filterNote] };
307
310
  }
308
311
  export async function showOrganization(session, flags, profile, operation, help, profileName) {
309
312
  const { select, fields } = selectedFields(flags, KNOWN_ORGANIZATIONS, KNOWN_ORGANIZATION_FIELDS, DEFAULT_ORGANIZATION_SHOW_SELECT, help);
@@ -361,20 +364,22 @@ export async function listBrandingLocalizations(session, flags, profile, operati
361
364
  if (!result.complete) {
362
365
  return {
363
366
  brandingLocalizations: localizations,
364
- count: { returned: localizations.length, complete: false, reason: result.reason },
367
+ ...listTotals(localizations.length, result.total, "branding localizations", false),
368
+ complete: false,
369
+ reason: result.reason,
365
370
  cursor: result.cursor,
366
371
  help: [...truncationHints, resumeHint(profileName), showHint, filterNote],
367
372
  };
368
373
  }
369
- const count = { returned: localizations.length, complete: true };
370
374
  if (!localizations.length) {
371
375
  return {
372
376
  brandingLocalizations: localizations,
373
- count,
377
+ ...listTotals(localizations.length, result.total, "branding localizations", true),
378
+ complete: true,
374
379
  help: [filterNote, "0 branding localizations matched; the absence of results is the answer, not an error"],
375
380
  };
376
381
  }
377
- return { brandingLocalizations: localizations, count, help: [...truncationHints, showHint, filterNote] };
382
+ return { brandingLocalizations: localizations, ...listTotals(localizations.length, result.total, "branding localizations", true), complete: true, help: [...truncationHints, showHint, filterNote] };
378
383
  }
379
384
  export async function showBrandingLocalization(session, flags, profile, operation, help, profileName) {
380
385
  const { select, fields } = selectedFields(flags, KNOWN_BRANDING, KNOWN_BRANDING_FIELDS, DEFAULT_LOCALIZATION_SHOW_SELECT, help, STREAM_HINT);