@knowttl/mg-axi 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -41
- package/dist/api.js +119 -0
- package/dist/catalogue.js +303 -0
- package/dist/cli.js +39 -9
- package/dist/docs.js +135 -46
- package/dist/entra-apps.js +158 -6
- package/dist/entra-audit-logs.js +93 -12
- package/dist/entra-conditional-access.js +416 -11
- package/dist/entra-federation-configurations.js +7 -4
- package/dist/entra-grants.js +8 -4
- package/dist/entra-group-lifecycle.js +13 -8
- package/dist/entra-identity-providers.js +7 -4
- package/dist/entra-lifecycle-workflows.js +16 -4
- package/dist/entra-multi-tenant-organization.js +7 -4
- package/dist/entra-on-premises-synchronization.js +6 -4
- package/dist/entra-risk-prevention.js +10 -4
- package/dist/entra-risk.js +11 -4
- package/dist/entra-roles.js +194 -8
- package/dist/entra-terms-of-use.js +13 -8
- package/dist/graph-session.js +1 -0
- package/dist/setup.js +4 -1
- package/docs/coverage.md +721 -654
- package/inventory/operations.json +2 -2
- package/inventory/schema.json +1 -1
- package/package.json +1 -1
- package/skills/mg-axi/SKILL.md +30 -0
package/dist/cli.js
CHANGED
|
@@ -11,15 +11,15 @@ import { updateUserAccount } from "./entra-user-update.js";
|
|
|
11
11
|
import { revokeUserSessions } from "./entra-user-revoke-sessions.js";
|
|
12
12
|
import { createMutationCoordinator, mutationFetchTransport } from "./mutations.js";
|
|
13
13
|
import { TRANSITIVE_OPERATION, listGroupMemberOf, listGroupMembers, listGroups, showGroup } from "./entra-groups.js";
|
|
14
|
-
import { listDirectoryRoles, showDirectoryRole, listRoleAssignments, listPimEligible, listPimActive } from "./entra-roles.js";
|
|
14
|
+
import { listDirectoryRoles, showDirectoryRole, listDirectoryRoleTemplates, showDirectoryRoleTemplate, listRoleMembers, listScopedMembers, showScopedMember, listScopedRoleMemberships, showScopedRoleMembership, listRoleAssignments, listPimEligible, listPimActive } from "./entra-roles.js";
|
|
15
15
|
import { listGroupAssignmentSchedules, showGroupAssignmentSchedule, listGroupAssignmentInstances, showGroupAssignmentInstance, listGroupEligibilitySchedules, showGroupEligibilitySchedule, listGroupEligibilityInstances, showGroupEligibilityInstance, listGroupEligibilityRequests, showGroupEligibilityRequest } from "./entra-group-pim.js";
|
|
16
16
|
import { listAdministrativeUnitMembers, listAdministrativeUnits, listDevices, showAdministrativeUnit, showDevice } from "./entra-directory.js";
|
|
17
|
-
import { listSignIns, showSignIn, listDirectoryAudits, showDirectoryAudit } from "./entra-audit-logs.js";
|
|
18
|
-
import { listApplicationOwners, listApplications, listServicePrincipalOwners, listServicePrincipals, showApplication, showServicePrincipal } from "./entra-apps.js";
|
|
17
|
+
import { listSignIns, showSignIn, listDirectoryAudits, showDirectoryAudit, listProvisioning, showProvisioning } from "./entra-audit-logs.js";
|
|
18
|
+
import { listApplicationFederatedCredentials, listApplicationOwners, listApplications, listServicePrincipalAssignedTo, listServicePrincipalFederatedCredentials, listServicePrincipalOwners, listServicePrincipals, showApplication, showApplicationFederatedCredential, showServicePrincipal, showServicePrincipalAppRoleAssignment, showServicePrincipalAssignedTo, showServicePrincipalFederatedCredential } from "./entra-apps.js";
|
|
19
19
|
import { listAppRoleAssignments, listOAuth2Grants } from "./entra-grants.js";
|
|
20
20
|
import { listRiskyUsers, showRiskyUser, listRiskDetections, showRiskDetection, listRiskyServicePrincipals, showRiskyServicePrincipal, listRiskyServicePrincipalHistory, showRiskyServicePrincipalHistory, listServicePrincipalRiskDetections, showServicePrincipalRiskDetection } from "./entra-risk.js";
|
|
21
21
|
import { dismissRiskyUser } from "./entra-risk-dismiss.js";
|
|
22
|
-
import { listPolicies, showPolicy, listNamedLocations, showNamedLocation } from "./entra-conditional-access.js";
|
|
22
|
+
import { listPolicies, showPolicy, listNamedLocations, showNamedLocation, listAuthStrengthPolicies, showAuthStrengthPolicy, listCombinationConfigurations, showCombinationConfiguration, listAuthMethodModes, showAuthMethodMode, listTemplates, showTemplate, listAuthContexts, showAuthContext, listDeletedPolicies, showDeletedPolicy, listDeletedNamedLocations, showDeletedNamedLocation } from "./entra-conditional-access.js";
|
|
23
23
|
import { listBrandingLocalizations, listOrganizations, showBranding, showBrandingLocalization, showOrganization } from "./entra-organization.js";
|
|
24
24
|
import { updateCaPolicy } from "./entra-ca-policy-update.js";
|
|
25
25
|
import { listDomains, showDomain, listVerificationDnsRecords, showVerificationDnsRecord, listServiceConfigurationRecords, showServiceConfigurationRecord, listDomainDnsRecords, showDomainDnsRecord } from "./entra-domains.js";
|
|
@@ -164,7 +164,7 @@ export async function executeArgv(argv, overrides = {}) {
|
|
|
164
164
|
const { MsalProvider } = await import("./msal-provider.js");
|
|
165
165
|
return { profile: selected.name, ...await new DelegatedAuth(new MsalProvider()).login(selected.profile, String(flags.method ?? "browser"), String(flags.scopes).split(",")) };
|
|
166
166
|
}
|
|
167
|
-
if (leaf.path === "entra conditional-access policy list" || leaf.path === "entra conditional-access policy show" || leaf.path === "entra conditional-access named-location list" || leaf.path === "entra conditional-access named-location show") {
|
|
167
|
+
if (leaf.path === "entra conditional-access policy list" || leaf.path === "entra conditional-access policy show" || leaf.path === "entra conditional-access named-location list" || leaf.path === "entra conditional-access named-location show" || leaf.path === "entra conditional-access auth-strength-policy list" || leaf.path === "entra conditional-access auth-strength-policy show" || leaf.path === "entra conditional-access combination-configuration list" || leaf.path === "entra conditional-access combination-configuration show" || leaf.path === "entra conditional-access auth-method-mode list" || leaf.path === "entra conditional-access auth-method-mode show" || leaf.path === "entra conditional-access template list" || leaf.path === "entra conditional-access template show" || leaf.path === "entra conditional-access auth-context list" || leaf.path === "entra conditional-access auth-context show" || leaf.path === "entra conditional-access deleted-policy list" || leaf.path === "entra conditional-access deleted-policy show" || leaf.path === "entra conditional-access deleted-named-location list" || leaf.path === "entra conditional-access deleted-named-location show") {
|
|
168
168
|
const selected = profiles.resolve(flags.profile);
|
|
169
169
|
const operation = operationFor(leaf, String(flags["api-version"] ?? "v1.0"));
|
|
170
170
|
if (!operation || operation.method !== "GET") {
|
|
@@ -190,7 +190,21 @@ export async function executeArgv(argv, overrides = {}) {
|
|
|
190
190
|
case "entra conditional-access policy list": return listPolicies(session, flags, selected.profile, operation, help, selected.name);
|
|
191
191
|
case "entra conditional-access policy show": return showPolicy(session, flags, selected.profile, operation, help, selected.name);
|
|
192
192
|
case "entra conditional-access named-location list": return listNamedLocations(session, flags, selected.profile, operation, help, selected.name);
|
|
193
|
-
|
|
193
|
+
case "entra conditional-access named-location show": return showNamedLocation(session, flags, selected.profile, operation, help, selected.name);
|
|
194
|
+
case "entra conditional-access auth-strength-policy list": return listAuthStrengthPolicies(session, flags, selected.profile, operation, help, selected.name);
|
|
195
|
+
case "entra conditional-access auth-strength-policy show": return showAuthStrengthPolicy(session, flags, selected.profile, operation, help, selected.name);
|
|
196
|
+
case "entra conditional-access combination-configuration list": return listCombinationConfigurations(session, flags, selected.profile, operation, help, selected.name);
|
|
197
|
+
case "entra conditional-access combination-configuration show": return showCombinationConfiguration(session, flags, selected.profile, operation, help, selected.name);
|
|
198
|
+
case "entra conditional-access auth-method-mode list": return listAuthMethodModes(session, flags, selected.profile, operation, help, selected.name);
|
|
199
|
+
case "entra conditional-access auth-method-mode show": return showAuthMethodMode(session, flags, selected.profile, operation, help, selected.name);
|
|
200
|
+
case "entra conditional-access template list": return listTemplates(session, flags, selected.profile, operation, help, selected.name);
|
|
201
|
+
case "entra conditional-access auth-context list": return listAuthContexts(session, flags, selected.profile, operation, help, selected.name);
|
|
202
|
+
case "entra conditional-access auth-context show": return showAuthContext(session, flags, selected.profile, operation, help, selected.name);
|
|
203
|
+
case "entra conditional-access deleted-policy list": return listDeletedPolicies(session, flags, selected.profile, operation, help, selected.name);
|
|
204
|
+
case "entra conditional-access deleted-policy show": return showDeletedPolicy(session, flags, selected.profile, operation, help, selected.name);
|
|
205
|
+
case "entra conditional-access deleted-named-location list": return listDeletedNamedLocations(session, flags, selected.profile, operation, help, selected.name);
|
|
206
|
+
case "entra conditional-access deleted-named-location show": return showDeletedNamedLocation(session, flags, selected.profile, operation, help, selected.name);
|
|
207
|
+
default: return showTemplate(session, flags, selected.profile, operation, help, selected.name);
|
|
194
208
|
}
|
|
195
209
|
}
|
|
196
210
|
if (leaf.path === "entra access-review definition list" || leaf.path === "entra access-review definition show" || leaf.path === "entra access-review instance list" || leaf.path === "entra access-review instance show" || leaf.path === "entra access-review decision list" || leaf.path === "entra access-review decision show" || leaf.path === "entra access-review contacted-reviewer list" || leaf.path === "entra access-review contacted-reviewer show" || leaf.path === "entra access-review stage list" || leaf.path === "entra access-review stage show") {
|
|
@@ -585,7 +599,7 @@ export async function executeArgv(argv, overrides = {}) {
|
|
|
585
599
|
help: leafHelp(leaf),
|
|
586
600
|
});
|
|
587
601
|
}
|
|
588
|
-
if (leaf.path === "entra directory-role list" || leaf.path === "entra directory-role show" || leaf.path === "entra role-assignment list" || leaf.path === "entra pim eligible list" || leaf.path === "entra pim active list"
|
|
602
|
+
if (leaf.path === "entra directory-role list" || leaf.path === "entra directory-role show" || leaf.path === "entra directory-role-template list" || leaf.path === "entra directory-role-template show" || leaf.path === "entra directory-role member list" || leaf.path === "entra directory-role scoped-member list" || leaf.path === "entra directory-role scoped-member show" || leaf.path === "entra scoped-role-membership list" || leaf.path === "entra scoped-role-membership show" || leaf.path === "entra role-assignment list" || leaf.path === "entra pim eligible list" || leaf.path === "entra pim active list"
|
|
589
603
|
|| leaf.path === "entra pim group-assignment-schedule list" || leaf.path === "entra pim group-assignment-schedule show"
|
|
590
604
|
|| leaf.path === "entra pim group-assignment-instance list" || leaf.path === "entra pim group-assignment-instance show"
|
|
591
605
|
|| leaf.path === "entra pim group-eligibility-schedule list" || leaf.path === "entra pim group-eligibility-schedule show"
|
|
@@ -616,6 +630,13 @@ export async function executeArgv(argv, overrides = {}) {
|
|
|
616
630
|
switch (leaf.path) {
|
|
617
631
|
case "entra directory-role list": return listDirectoryRoles(session, flags, selected.profile, operation, help, selected.name);
|
|
618
632
|
case "entra directory-role show": return showDirectoryRole(session, flags, selected.profile, operation, help, selected.name);
|
|
633
|
+
case "entra directory-role-template list": return listDirectoryRoleTemplates(session, flags, selected.profile, operation, help, selected.name);
|
|
634
|
+
case "entra directory-role-template show": return showDirectoryRoleTemplate(session, flags, selected.profile, operation, help, selected.name);
|
|
635
|
+
case "entra directory-role member list": return listRoleMembers(session, flags, selected.profile, operation, help, selected.name);
|
|
636
|
+
case "entra directory-role scoped-member list": return listScopedMembers(session, flags, selected.profile, operation, help, selected.name);
|
|
637
|
+
case "entra directory-role scoped-member show": return showScopedMember(session, flags, selected.profile, operation, help, selected.name);
|
|
638
|
+
case "entra scoped-role-membership list": return listScopedRoleMemberships(session, flags, selected.profile, operation, help, selected.name);
|
|
639
|
+
case "entra scoped-role-membership show": return showScopedRoleMembership(session, flags, selected.profile, operation, help, selected.name);
|
|
619
640
|
case "entra role-assignment list": return listRoleAssignments(session, flags, selected.profile, operation, help, selected.name);
|
|
620
641
|
case "entra pim eligible list": return listPimEligible(session, flags, selected.profile, operation, help, selected.name);
|
|
621
642
|
case "entra pim active list": return listPimActive(session, flags, selected.profile, operation, help, selected.name);
|
|
@@ -666,13 +687,13 @@ export async function executeArgv(argv, overrides = {}) {
|
|
|
666
687
|
default: return showBrandingLocalization(session, flags, selected.profile, operation, help, selected.name);
|
|
667
688
|
}
|
|
668
689
|
}
|
|
669
|
-
if (leaf.path === "entra sign-in list" || leaf.path === "entra sign-in show" || leaf.path === "entra directory-audit list" || leaf.path === "entra directory-audit show" || leaf.path === "entra application list" || leaf.path === "entra application show" || leaf.path === "entra service-principal list" || leaf.path === "entra service-principal show" || leaf.path === "entra application owner list" || leaf.path === "entra service-principal owner list") {
|
|
690
|
+
if (leaf.path === "entra sign-in list" || leaf.path === "entra sign-in show" || leaf.path === "entra directory-audit list" || leaf.path === "entra directory-audit show" || leaf.path === "entra provisioning list" || leaf.path === "entra provisioning show" || leaf.path === "entra application list" || leaf.path === "entra application show" || leaf.path === "entra service-principal list" || leaf.path === "entra service-principal show" || leaf.path === "entra application owner list" || leaf.path === "entra service-principal owner list" || leaf.path === "entra application federated-credential list" || leaf.path === "entra application federated-credential show" || leaf.path === "entra service-principal federated-credential list" || leaf.path === "entra service-principal federated-credential show" || leaf.path === "entra service-principal app-role-assigned-to list" || leaf.path === "entra service-principal app-role-assigned-to show" || leaf.path === "entra service-principal app-role-assignment show") {
|
|
670
691
|
const selected = profiles.resolve(flags.profile);
|
|
671
692
|
const operation = operationFor(leaf, String(flags["api-version"] ?? "v1.0"));
|
|
672
693
|
if (!operation || operation.method !== "GET") {
|
|
673
694
|
throw new AxiError(`Unknown catalogued Graph operation for ${leaf.path}`, "VALIDATION_ERROR", [leafHelp(leaf)]);
|
|
674
695
|
}
|
|
675
|
-
if ((leaf.path.startsWith("entra sign-in ") || leaf.path.startsWith("entra directory-audit ")) && flags.cursor !== undefined)
|
|
696
|
+
if ((leaf.path.startsWith("entra sign-in ") || leaf.path.startsWith("entra directory-audit ") || leaf.path.startsWith("entra provisioning ")) && flags.cursor !== undefined)
|
|
676
697
|
flags.cursor = (await readCursor(String(flags.cursor)));
|
|
677
698
|
let delegated = overrides.delegated;
|
|
678
699
|
let application = overrides.application;
|
|
@@ -693,11 +714,20 @@ export async function executeArgv(argv, overrides = {}) {
|
|
|
693
714
|
case "entra sign-in show": return showSignIn(session, flags, selected.profile, operation, help, selected.name);
|
|
694
715
|
case "entra directory-audit list": return listDirectoryAudits(session, flags, selected.profile, operation, help, selected.name);
|
|
695
716
|
case "entra directory-audit show": return showDirectoryAudit(session, flags, selected.profile, operation, help, selected.name);
|
|
717
|
+
case "entra provisioning list": return listProvisioning(session, flags, selected.profile, operation, help, selected.name);
|
|
718
|
+
case "entra provisioning show": return showProvisioning(session, flags, selected.profile, operation, help, selected.name);
|
|
696
719
|
case "entra application list": return listApplications(session, flags, selected.profile, operation, help, selected.name);
|
|
697
720
|
case "entra application show": return showApplication(session, flags, selected.profile, operation, help, selected.name);
|
|
698
721
|
case "entra service-principal list": return listServicePrincipals(session, flags, selected.profile, operation, help, selected.name);
|
|
699
722
|
case "entra service-principal show": return showServicePrincipal(session, flags, selected.profile, operation, help, selected.name);
|
|
700
723
|
case "entra application owner list": return listApplicationOwners(session, flags, selected.profile, operation, help, selected.name);
|
|
724
|
+
case "entra application federated-credential list": return listApplicationFederatedCredentials(session, flags, selected.profile, operation, help, selected.name);
|
|
725
|
+
case "entra application federated-credential show": return showApplicationFederatedCredential(session, flags, selected.profile, operation, help, selected.name);
|
|
726
|
+
case "entra service-principal federated-credential list": return listServicePrincipalFederatedCredentials(session, flags, selected.profile, operation, help, selected.name);
|
|
727
|
+
case "entra service-principal federated-credential show": return showServicePrincipalFederatedCredential(session, flags, selected.profile, operation, help, selected.name);
|
|
728
|
+
case "entra service-principal app-role-assigned-to list": return listServicePrincipalAssignedTo(session, flags, selected.profile, operation, help, selected.name);
|
|
729
|
+
case "entra service-principal app-role-assigned-to show": return showServicePrincipalAssignedTo(session, flags, selected.profile, operation, help, selected.name);
|
|
730
|
+
case "entra service-principal app-role-assignment show": return showServicePrincipalAppRoleAssignment(session, flags, selected.profile, operation, help, selected.name);
|
|
701
731
|
default: return listServicePrincipalOwners(session, flags, selected.profile, operation, help, selected.name);
|
|
702
732
|
}
|
|
703
733
|
}
|
package/dist/docs.js
CHANGED
|
@@ -37,6 +37,8 @@ const READ_AREA_LABELS = [
|
|
|
37
37
|
["entra user ", "users"],
|
|
38
38
|
["entra group ", "groups"],
|
|
39
39
|
["entra directory-role ", "roles"],
|
|
40
|
+
["entra directory-role-template ", "roles"],
|
|
41
|
+
["entra scoped-role-membership ", "roles"],
|
|
40
42
|
["entra role-assignment ", "roles"],
|
|
41
43
|
["entra pim ", "roles"],
|
|
42
44
|
["entra device ", "devices"],
|
|
@@ -70,6 +72,7 @@ const READ_AREA_LABELS = [
|
|
|
70
72
|
["entra tenant-information ", "tenant information"],
|
|
71
73
|
["entra sign-in ", "sign-ins and audit logs"],
|
|
72
74
|
["entra directory-audit ", "sign-ins and audit logs"],
|
|
75
|
+
["entra provisioning ", "sign-ins and audit logs"],
|
|
73
76
|
["entra application ", "applications and consent grants"],
|
|
74
77
|
["entra service-principal ", "applications and consent grants"],
|
|
75
78
|
["entra risky-user ", "risk"],
|
|
@@ -242,14 +245,14 @@ export function skillDocument() {
|
|
|
242
245
|
// remains unimplemented. Family membership comes from the inventory
|
|
243
246
|
// owningSlice and shipped status comes from the catalogue, so a merged
|
|
244
247
|
// write slice flips its own row without touching this template. Blocked,
|
|
245
|
-
// deprecated and
|
|
248
|
+
// deprecated, unavailable and deferred-out-of-v1 variants never count as pending.
|
|
246
249
|
function initialWriteStatus(rows) {
|
|
247
250
|
const families = new Map();
|
|
248
251
|
for (const row of rows) {
|
|
249
252
|
const operation = /^v1\.0:(.+)$/.exec(row.id)?.[1];
|
|
250
253
|
if (operation === undefined || row.owningSlice === null || !/^WRITE-0[1-9]$/.test(row.owningSlice))
|
|
251
254
|
continue;
|
|
252
|
-
if (row.disposition === "intentionally-blocked" || row.disposition === "deprecated" || row.disposition === "unavailable")
|
|
255
|
+
if (row.disposition === "intentionally-blocked" || row.disposition === "deprecated" || row.disposition === "unavailable" || row.disposition === "deferred")
|
|
253
256
|
continue;
|
|
254
257
|
families.set(row.owningSlice, [...(families.get(row.owningSlice) ?? []), operation]);
|
|
255
258
|
}
|
|
@@ -264,7 +267,79 @@ function initialWriteStatus(rows) {
|
|
|
264
267
|
}
|
|
265
268
|
return { shipped, pending };
|
|
266
269
|
}
|
|
267
|
-
const DISPOSITIONS = ["named-command", "reviewed-raw-read", "scheduled", "intentionally-blocked", "deprecated", "unavailable", "excluded"];
|
|
270
|
+
const DISPOSITIONS = ["named-command", "reviewed-raw-read", "scheduled", "deferred", "intentionally-blocked", "deprecated", "unavailable", "excluded"];
|
|
271
|
+
// FULL-01 audit record (2026-10-07, latest origin/main at PR #78): the cutoff
|
|
272
|
+
// is met when no v1.0 row is left scheduled, so the counts below are read
|
|
273
|
+
// from the same generated inventory the Counts section reports. Beta stays
|
|
274
|
+
// scheduled by definition and was never agreed for v1.
|
|
275
|
+
function auditRecord(rows) {
|
|
276
|
+
const v1 = (disposition) => rows.filter(row => row.id.startsWith("v1.0:") && row.disposition === disposition).length;
|
|
277
|
+
const betaScheduled = rows.filter(row => row.id.startsWith("beta:") && row.disposition === "scheduled").length;
|
|
278
|
+
const named = v1("named-command");
|
|
279
|
+
const raw = v1("reviewed-raw-read");
|
|
280
|
+
const scheduled = v1("scheduled");
|
|
281
|
+
if (scheduled !== 0)
|
|
282
|
+
throw new Error(`FULL-01 audit blocked: ${scheduled} v1.0 rows remain scheduled`);
|
|
283
|
+
return [
|
|
284
|
+
"## FULL-01 audit record",
|
|
285
|
+
"",
|
|
286
|
+
"FULL-01 audit (2026-10-07): every agreed v1.0 read has a named command or an",
|
|
287
|
+
"explicit reviewed blocked/unavailable/deprecated/deferred disposition, and no",
|
|
288
|
+
"v1.0 row is left scheduled. The deferred tail is declared out of v1.",
|
|
289
|
+
`- v1.0 named-command: ${named}, reviewed-raw-read: ${raw}, scheduled: ${scheduled}`,
|
|
290
|
+
`- v1.0 deferred (out of v1): ${v1("deferred")}, intentionally-blocked: ${v1("intentionally-blocked")}, deprecated: ${v1("deprecated")}, unavailable: ${v1("unavailable")}`,
|
|
291
|
+
`- beta scheduled (never agreed for v1): ${betaScheduled}`,
|
|
292
|
+
`PR #78 (historical point-in-time) proposed 275 named / 19 raw (294 backed); the generated inventory carries ${named} named / ${raw} raw (${named + raw} backed).`,
|
|
293
|
+
"",
|
|
294
|
+
];
|
|
295
|
+
}
|
|
296
|
+
// COMPLETE-01 audit record (2026-10-07, latest origin/main at this change):
|
|
297
|
+
// the full agreed Entra capability audit including later writes. Five writes
|
|
298
|
+
// ship as named, gated commands through the WRITE-00 mutation coordinator,
|
|
299
|
+
// WRITE-N carries explicit reviewed deferred/blocked/deprecated dispositions
|
|
300
|
+
// rather than scheduled, and no agreed v1.0 operation, read or write, is
|
|
301
|
+
// left merely scheduled. Beta stays scheduled by definition and was never
|
|
302
|
+
// agreed for v1. The declined EXT-02c access-review history reads stay out.
|
|
303
|
+
function completeAuditRecord(rows) {
|
|
304
|
+
const v1 = (disposition) => rows.filter(row => row.id.startsWith("v1.0:") && row.disposition === disposition).length;
|
|
305
|
+
const betaScheduled = rows.filter(row => row.id.startsWith("beta:") && row.disposition === "scheduled").length;
|
|
306
|
+
const named = v1("named-command");
|
|
307
|
+
const raw = v1("reviewed-raw-read");
|
|
308
|
+
const scheduled = v1("scheduled");
|
|
309
|
+
const writeRows = rows.filter(row => row.id.startsWith("v1.0:") && row.disposition === "named-command" && !row.id.includes(":GET:"));
|
|
310
|
+
const writeN = (version, disposition) => rows.filter(row => row.owningSlice === "WRITE-N" && row.id.startsWith(`${version}:`) && row.disposition === disposition).length;
|
|
311
|
+
const writeNScheduled = rows.filter(row => row.owningSlice === "WRITE-N" && row.disposition === "scheduled").length;
|
|
312
|
+
const declinedOut = rows.filter(row => row.owningSlice === "EXT-02c"
|
|
313
|
+
&& (row.disposition === "named-command" || row.disposition === "reviewed-raw-read"));
|
|
314
|
+
if (scheduled !== 0)
|
|
315
|
+
throw new Error(`COMPLETE-01 audit blocked: ${scheduled} v1.0 rows remain scheduled`);
|
|
316
|
+
if (writeNScheduled !== 0)
|
|
317
|
+
throw new Error(`COMPLETE-01 audit blocked: ${writeNScheduled} WRITE-N rows remain scheduled`);
|
|
318
|
+
const writeStatus = initialWriteStatus(rows);
|
|
319
|
+
const expectedWrites = ["WRITE-01", "WRITE-02", "WRITE-03", "WRITE-04", "WRITE-05"];
|
|
320
|
+
const missing = expectedWrites.filter(slice => !writeStatus.shipped.some(entry => entry.startsWith(`${slice} `)));
|
|
321
|
+
if (writeStatus.pending.length > 0 || missing.length > 0) {
|
|
322
|
+
throw new Error(`COMPLETE-01 audit blocked: pending writes: ${[...missing, ...writeStatus.pending].join(", ")}`);
|
|
323
|
+
}
|
|
324
|
+
if (declinedOut.length > 0)
|
|
325
|
+
throw new Error(`COMPLETE-01 audit blocked: declined EXT-02c rows ship: ${declinedOut.map(row => row.id).join(", ")}`);
|
|
326
|
+
return [
|
|
327
|
+
"## COMPLETE-01 audit record",
|
|
328
|
+
"",
|
|
329
|
+
"COMPLETE-01 audit (2026-10-07): the full agreed Entra capability audit including later writes.",
|
|
330
|
+
"Five writes ship as named, gated commands through the WRITE-00 mutation coordinator; WRITE-N carries explicit",
|
|
331
|
+
"reviewed deferred/blocked/deprecated dispositions rather than scheduled; no agreed v1.0 operation, read or",
|
|
332
|
+
"write, is left merely scheduled. Beta stays scheduled by definition and was never agreed for v1.",
|
|
333
|
+
"The declined EXT-02c access-review history reads (ReadWrite least privilege, SAS URLs) stay out.",
|
|
334
|
+
`- v1.0 named-command: ${named} (${named - writeRows.length} reads plus ${writeRows.length} writes below), reviewed-raw-read: ${raw}, scheduled: ${scheduled}`,
|
|
335
|
+
`- v1.0 deferred (out of v1): ${v1("deferred")}, intentionally-blocked: ${v1("intentionally-blocked")}, deprecated: ${v1("deprecated")}, unavailable: ${v1("unavailable")}`,
|
|
336
|
+
`- beta scheduled (never agreed for v1): ${betaScheduled}`,
|
|
337
|
+
`- shipped writes: ${writeStatus.shipped.join(", ")}.`,
|
|
338
|
+
`- WRITE-N (remaining agreed mutations): deferred ${writeN("v1.0", "deferred")}, intentionally-blocked ${writeN("v1.0", "intentionally-blocked")} v1.0 plus ${writeN("beta", "intentionally-blocked")} beta, deprecated ${writeN("v1.0", "deprecated")} v1.0 plus ${writeN("beta", "deprecated")} beta, scheduled ${writeNScheduled}.`,
|
|
339
|
+
"- outstanding upstream limitations (already documented in the repo): P1 for Conditional Access and P2 for risk-based CA, PIM P2 or ID Governance, riskyUsers P2 and Workload Identities Premium for workload risk with hidden riskDetail/riskLevel without it (docs/graph-coverage.md); conservative P1/P2 Graph prerequisite for sign-in/audit logs, registration report excludes disabled users, no tenant-wide method enumeration (docs/graph-coverage.md); v1.0 group-members route omits service principals (README.md member-read warning); 14 unavailable rows with no documented v1.0 GET contract, invitation Create-only Methods table, org-scoped certificateBasedAuthConfiguration routes only, contacts sync-behavior reads without operation permission docs (inventory reasons); EXT-02c history reads need AccessReview.ReadWrite.All with no read scope and return SAS download URLs (docs/coverage.md); write-side limits beside each command, revoke lag/external-user scope/unknown outcome, CA update lockout gates with no concurrency promise, dismissal is not remediation (README.md, docs/execution.md).",
|
|
340
|
+
"",
|
|
341
|
+
];
|
|
342
|
+
}
|
|
268
343
|
// The capability report is generated from the catalogue: every executable
|
|
269
344
|
// read leaf owns one row with its inventory disposition and owning slice,
|
|
270
345
|
// and the counts separate implemented leaves from inventory dispositions.
|
|
@@ -309,6 +384,7 @@ export function capabilityDocument() {
|
|
|
309
384
|
const deferredCustomSecurityRows = rows.filter(row => row.owningSlice === "EXT-01" && row.reason.startsWith("Deferred by firstmate custom-security-attributes scope "));
|
|
310
385
|
const deferredAccessReviewRows = rows.filter(row => row.owningSlice === "EXT-02c");
|
|
311
386
|
const deferredEntitlementRows = rows.filter(row => row.owningSlice === "EXT-02" && row.reason.startsWith("Deferred by firstmate entitlement-management scope "));
|
|
387
|
+
const deferredAppLookupRows = rows.filter(row => row.owningSlice === "READ-07" && row.reason.startsWith("Deferred by firstmate application-lookups scope "));
|
|
312
388
|
const readCount = readLeaves.length + 2;
|
|
313
389
|
const localCount = LEAVES.length - readCount - writeLeaves.length;
|
|
314
390
|
return [
|
|
@@ -318,9 +394,11 @@ export function capabilityDocument() {
|
|
|
318
394
|
"",
|
|
319
395
|
"This is the supported Entra read surface, not full Entra coverage.",
|
|
320
396
|
"Only the commands below have an implemented, tested leaf; every other",
|
|
321
|
-
"operation
|
|
322
|
-
"
|
|
323
|
-
"
|
|
397
|
+
"v1.0 operation is deferred out of v1, and every other beta operation remains",
|
|
398
|
+
"scheduled (never agreed for v1), blocked, deprecated, unavailable or excluded.",
|
|
399
|
+
"Inventory dispositions are generated records from INV-01 joined to the catalogue;",
|
|
400
|
+
"the command list below is the implemented truth and never counts deferred or",
|
|
401
|
+
"scheduled work as complete.",
|
|
324
402
|
"",
|
|
325
403
|
"## Counts",
|
|
326
404
|
"",
|
|
@@ -329,6 +407,8 @@ export function capabilityDocument() {
|
|
|
329
407
|
`- local leaves: ${localCount} (home, profile, login and setup views)`,
|
|
330
408
|
...DISPOSITIONS.map(disposition => `- inventory ${disposition}: ${count(disposition)}`),
|
|
331
409
|
"",
|
|
410
|
+
...auditRecord(rows),
|
|
411
|
+
...completeAuditRecord(rows),
|
|
332
412
|
"## Commands",
|
|
333
413
|
"",
|
|
334
414
|
"| Command | Operation | Inventory (v1.0) | Owning slice |",
|
|
@@ -340,61 +420,61 @@ export function capabilityDocument() {
|
|
|
340
420
|
"## EXT-01 domain scope decisions",
|
|
341
421
|
"",
|
|
342
422
|
"Firstmate decision R1: approve narrowing this change to the eight v1.0 domain reads above.",
|
|
343
|
-
"The operations below
|
|
423
|
+
"The v1.0 operations below carry the deferred disposition (out of v1) and the beta operations remain scheduled (never agreed for v1); no new commands or raw access are approved.",
|
|
344
424
|
"Federation stays out because it can carry signing-certificate material.",
|
|
345
425
|
"Firstmate decision R2: keep the write-family status section as the coverage fix added to scope (resolved-kept).",
|
|
346
426
|
"Both firstmate decisions must also be stated in the PR body by the delivery phase.",
|
|
347
427
|
"",
|
|
348
428
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
349
429
|
"|---|---|---|---|",
|
|
350
|
-
...deferredDomainRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
430
|
+
...deferredDomainRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
351
431
|
"",
|
|
352
432
|
"## EXT-03 identity-provider scope decisions",
|
|
353
433
|
"",
|
|
354
434
|
"This change covers the four v1.0 workforce identity-provider reads above (list, show, count, available-types).",
|
|
355
|
-
"The four beta operations below remain scheduled
|
|
435
|
+
"The four beta operations below remain scheduled (never agreed for v1) for a later EXT-03 subfamily.",
|
|
356
436
|
"Identity-provider reads support v1.0 only; beta needs its own review.",
|
|
357
437
|
"Workforce context only; no external-customer (B2C/External ID tenant) support is claimed.",
|
|
358
438
|
"",
|
|
359
439
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
360
440
|
"|---|---|---|---|",
|
|
361
|
-
...deferredProviderRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
441
|
+
...deferredProviderRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
362
442
|
"",
|
|
363
443
|
"## EXT-01 federation-configuration scope decisions",
|
|
364
444
|
"",
|
|
365
445
|
"This change covers the four v1.0 workforce directory federation-configuration reads above (federation-configuration list, show, count and available-types).",
|
|
366
|
-
"The four beta operations below remain scheduled
|
|
446
|
+
"The four beta operations below remain scheduled (never agreed for v1) for a later EXT-01 subfamily; no new commands or raw access are approved.",
|
|
367
447
|
"Federation-configuration reads support v1.0 only; beta needs its own review.",
|
|
368
448
|
"signingCertificate carries the public token-signing key only and is omitted from default selects; no private key material exists on these resources and the domains navigation property needs its own review.",
|
|
369
|
-
"Workforce context only; domain federationConfiguration sub-reads (/domains/{domain-id}/federationConfiguration)
|
|
449
|
+
"Workforce context only; domain federationConfiguration sub-reads (/domains/{domain-id}/federationConfiguration) carry the deferred disposition in v1.0 (out of v1) and remain scheduled in beta (never agreed for v1) in the later domain federation subfamily.",
|
|
370
450
|
"",
|
|
371
451
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
372
452
|
"|---|---|---|---|",
|
|
373
|
-
...deferredFederationRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
453
|
+
...deferredFederationRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
374
454
|
"",
|
|
375
455
|
"## EXT-03 data-policy-operations scope decisions",
|
|
376
456
|
"",
|
|
377
457
|
"This change covers the three v1.0 workforce data-policy-operation reads above (data-policy-operation list, show and count).",
|
|
378
|
-
"The three beta operations below remain scheduled
|
|
458
|
+
"The three beta operations below remain scheduled (never agreed for v1) for a later EXT-03 subfamily; no new commands or raw access are approved.",
|
|
379
459
|
"Data-policy-operation reads support v1.0 only; beta needs its own review.",
|
|
380
460
|
"storageLocation always renders as the redaction marker; export submission (POST /users/{id}/exportPersonalData) belongs to no read slice.",
|
|
381
461
|
"Workforce context only; no external-customer support is claimed.",
|
|
382
462
|
"",
|
|
383
463
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
384
464
|
"|---|---|---|---|",
|
|
385
|
-
...deferredDataPolicyRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
465
|
+
...deferredDataPolicyRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
386
466
|
"",
|
|
387
467
|
"## EXT-03 risk-prevention scope decisions",
|
|
388
468
|
"",
|
|
389
469
|
"This change covers the ten v1.0 workforce risk-prevention reads above (fraud-protection-provider list, show and count; web-application-firewall-provider list, show and count; web-application-firewall-verification list, show, count and provider show).",
|
|
390
|
-
"The eleven beta operations below remain scheduled
|
|
470
|
+
"The eleven beta operations below remain scheduled (never agreed for v1) for a later EXT-03 subfamily; no new commands or raw access are approved.",
|
|
391
471
|
"Risk-prevention reads support v1.0 only; beta needs its own review.",
|
|
392
472
|
"API keys, client secrets and other key material (Arkose privateKey/publicKey, HUMAN serverToken, Akamai clientSecret/clientToken/accessToken, Cloudflare apiToken) are never selectable and never projected; application profiles are refused before credentials because Graph documents no supported application permission.",
|
|
393
473
|
"Workforce context only; no external-customer support is claimed.",
|
|
394
474
|
"",
|
|
395
475
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
396
476
|
"|---|---|---|---|",
|
|
397
|
-
...deferredRiskPreventionRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
477
|
+
...deferredRiskPreventionRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
398
478
|
"",
|
|
399
479
|
"## EXT-03 invitations scope decisions",
|
|
400
480
|
"",
|
|
@@ -410,12 +490,12 @@ export function capabilityDocument() {
|
|
|
410
490
|
"## EXT-01 organization scope decisions",
|
|
411
491
|
"",
|
|
412
492
|
"The organization scope covers the five organization/branding/localization reads above; the remaining subfamilies need the separate contracts described below.",
|
|
413
|
-
"The operations below
|
|
493
|
+
"The v1.0 operations below carry the deferred disposition (out of v1) and the beta operations remain scheduled (never agreed for v1); no new commands or raw access are approved.",
|
|
414
494
|
"Stream image and CSS bytes stay out because they need a separate binary-output contract; beta-only settings, partner and theme contracts need separate review.",
|
|
415
495
|
"",
|
|
416
496
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
417
497
|
"|---|---|---|---|",
|
|
418
|
-
...deferredOrganizationRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
498
|
+
...deferredOrganizationRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
419
499
|
"",
|
|
420
500
|
"## EXT-01 certificate-auth scope decisions",
|
|
421
501
|
"",
|
|
@@ -437,61 +517,61 @@ export function capabilityDocument() {
|
|
|
437
517
|
"## EXT-01 group lifecycle scope decisions",
|
|
438
518
|
"",
|
|
439
519
|
"This change covers the six v1.0 group lifecycle policy and group setting template reads above (list, show, count per family).",
|
|
440
|
-
"The operations below
|
|
520
|
+
"The v1.0 operations below carry the deferred disposition (out of v1) and the beta operations remain scheduled (never agreed for v1); no new commands or raw access are approved.",
|
|
441
521
|
"Beta policies and templates need their own review; delta-token sync needs its own change-tracking contract beyond the approved list/show/count reads; the POST lookup actions need their own request and projection review.",
|
|
442
522
|
"",
|
|
443
523
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
444
524
|
"|---|---|---|---|",
|
|
445
|
-
...deferredGroupLifecycleRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
525
|
+
...deferredGroupLifecycleRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
446
526
|
"",
|
|
447
527
|
"## EXT-01 custom-security-attributes scope decisions",
|
|
448
528
|
"",
|
|
449
529
|
"This change covers the nine v1.0 attribute-set, custom-security-attribute-definition and allowed-value reads above (list, show, count per family).",
|
|
450
|
-
"The nine beta operations below remain scheduled
|
|
530
|
+
"The nine beta operations below remain scheduled (never agreed for v1) for a later EXT-01 subfamily; no new commands or raw access are approved.",
|
|
451
531
|
"Beta attribute sets, definitions and allowed values need their own review; definition $expand (inline allowedValues) is not reviewed here and stays on the dedicated allowed-value list/show reads.",
|
|
452
532
|
"",
|
|
453
533
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
454
534
|
"|---|---|---|---|",
|
|
455
|
-
...deferredCustomSecurityRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
535
|
+
...deferredCustomSecurityRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
456
536
|
"",
|
|
457
537
|
"## EXT-01 on-premises-synchronization scope decisions",
|
|
458
538
|
"",
|
|
459
539
|
"This change covers the two v1.0 on-premises-synchronization reads above (on-premises-synchronization list and show).",
|
|
460
|
-
"The $count scalar and the three beta operations
|
|
540
|
+
"The $count scalar below carries the deferred disposition (out of v1) and the three beta operations remain scheduled (never agreed for v1); no new commands or raw access are approved.",
|
|
461
541
|
"On-premises-synchronization reads support v1.0 only; beta needs its own review.",
|
|
462
542
|
"Scalar counts need a separate query and response contract from the approved list/show reads.",
|
|
463
543
|
"",
|
|
464
544
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
465
545
|
"|---|---|---|---|",
|
|
466
|
-
...deferredOnPremSyncRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
546
|
+
...deferredOnPremSyncRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
467
547
|
"",
|
|
468
548
|
"## EXT-01 terms-of-use scope decisions",
|
|
469
549
|
"",
|
|
470
550
|
"This change covers the six v1.0 agreement and agreement-acceptance reads above (agreement list/show, one agreement's acceptance list/show, and tenant-wide acceptance list/show).",
|
|
471
|
-
"The
|
|
551
|
+
"The v1.0 operations below carry the deferred disposition (out of v1) and the beta operations remain scheduled (never agreed for v1); no new commands or raw access are approved.",
|
|
472
552
|
"Terms-of-use reads support v1.0 only; beta needs its own review.",
|
|
473
553
|
"Scalar counts need a separate query and response contract from the approved list/show reads; agreement file contents and localizations need a separate binary-output and projection contract, and file bytes are never downloaded or printed.",
|
|
474
554
|
"",
|
|
475
555
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
476
556
|
"|---|---|---|---|",
|
|
477
|
-
...deferredTermsOfUseRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
557
|
+
...deferredTermsOfUseRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
478
558
|
"",
|
|
479
559
|
"## EXT-01 directory-objects scope decisions",
|
|
480
560
|
"",
|
|
481
561
|
"This change covers the three v1.0 directory-object reads above (directory-object list, show and count).",
|
|
482
|
-
"The
|
|
562
|
+
"The v1.0 operations below carry the deferred disposition (out of v1) and the beta operations remain scheduled (never agreed for v1); no new commands or raw access are approved.",
|
|
483
563
|
"Directory-object reads support v1.0 only; beta needs its own review.",
|
|
484
564
|
"Delta-token sync needs its own change-tracking contract beyond the approved list/show/count reads; the POST lookup and validation actions need their own request and projection review.",
|
|
485
565
|
"Rows are polymorphic: only the base-type properties plus the @odata.type discriminator are projected, so subtype secrets or credentials can never appear.",
|
|
486
566
|
"",
|
|
487
567
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
488
568
|
"|---|---|---|---|",
|
|
489
|
-
...deferredDirectoryObjectRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
569
|
+
...deferredDirectoryObjectRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
490
570
|
"",
|
|
491
571
|
"## EXT-01 deleted-items scope decisions",
|
|
492
572
|
"",
|
|
493
573
|
"This change covers the eleven v1.0 deleted directory-item reads above (deleted-user, deleted-group, deleted-application, deleted-service-principal and deleted-administrative-unit list and count, plus deleted-item show).",
|
|
494
|
-
"The rows below
|
|
574
|
+
"The rows below carry the deferred (v1.0, out of v1) or scheduled (beta, never agreed for v1) disposition shown; no new commands or raw access are approved for them.",
|
|
495
575
|
"Deleted-item reads support v1.0 only; beta needs its own review.",
|
|
496
576
|
"Untyped list/count commands do not exist because upstream requires the OData cast as part of the list URI.",
|
|
497
577
|
"Device casts carry no documented v1.0 permission contract: the list/get pages name the supported types and device is not among them.",
|
|
@@ -500,12 +580,12 @@ export function capabilityDocument() {
|
|
|
500
580
|
"",
|
|
501
581
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
502
582
|
"|---|---|---|---|",
|
|
503
|
-
...deferredDeletedItemsRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
583
|
+
...deferredDeletedItemsRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
504
584
|
"",
|
|
505
585
|
"## EXT-01 contacts scope decisions",
|
|
506
586
|
"",
|
|
507
587
|
"This change covers the three v1.0 top-level contact reads above (contact list, show and count), the ten v1.0 manager/directReports navigation reads above (show-manager, list/show/count-direct-reports with user/contact casts), and the eighteen v1.0 memberOf/transitiveMemberOf navigation reads above (list/show/count-member-of with transitive and group/administrativeUnit casts).",
|
|
508
|
-
"The
|
|
588
|
+
"The v1.0 operations below carry the deferred disposition (out of v1) and the beta operations remain scheduled (never agreed for v1); no new commands or raw access are approved.",
|
|
509
589
|
"Contact reads support v1.0 only; beta needs its own review.",
|
|
510
590
|
"Delta-token sync needs its own change-tracking contract beyond the approved reads; the POST lookup actions need their own request and projection review.",
|
|
511
591
|
"Contacts are personal data: top-level default rows carry id, displayName, mail and companyName only; navigation rows carry the @odata.type discriminator plus id and displayName only, and identifying fields need an explicit --select.",
|
|
@@ -513,43 +593,43 @@ export function capabilityDocument() {
|
|
|
513
593
|
"",
|
|
514
594
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
515
595
|
"|---|---|---|---|",
|
|
516
|
-
...deferredContactRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
596
|
+
...deferredContactRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
517
597
|
...unavailableContactRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
518
598
|
"",
|
|
519
599
|
"## EXT-04 partner contracts scope decisions",
|
|
520
600
|
"",
|
|
521
601
|
"Firstmate scope: approve narrowing this change to the three v1.0 partner-contract reads above (list, show, count).",
|
|
522
|
-
"The operations below
|
|
602
|
+
"The v1.0 operations below carry the deferred disposition (out of v1) and the beta operations remain scheduled (never agreed for v1); no new commands or raw access are approved.",
|
|
523
603
|
"Beta contracts need their own review; delta-token sync needs its own change-tracking contract beyond the approved list/show/count reads.",
|
|
524
604
|
"",
|
|
525
605
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
526
606
|
"|---|---|---|---|",
|
|
527
|
-
...deferredContractRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
607
|
+
...deferredContractRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`), "",
|
|
528
608
|
"## EXT-04 delegated-admin scope decisions",
|
|
529
609
|
"",
|
|
530
610
|
"Firstmate scope: approve narrowing this change to the eighteen v1.0 delegated-admin reads above (customer list/show/count, relationship list/show/count, access-assignment list/show/count, operation list/show/count, request list/show/count and service-management-detail list/show/count).",
|
|
531
|
-
"The
|
|
611
|
+
"The operation below carries the deferred disposition (out of v1); no new commands or raw access are approved.",
|
|
532
612
|
"The tenantRelationship container root needs its own projection review; multi-tenant-organization reads shipped in the section below, and tenant-lookup functions ship as tenant-information show under the mg-ext-04e function-argument contract.",
|
|
533
613
|
"",
|
|
534
614
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
535
615
|
"|---|---|---|---|",
|
|
536
|
-
...deferredDelegatedAdminRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
616
|
+
...deferredDelegatedAdminRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`), "",
|
|
537
617
|
"## EXT-04 multi-tenant-organization scope decisions",
|
|
538
618
|
"",
|
|
539
619
|
"Firstmate scope: approve narrowing this change to the four v1.0 multi-tenant-organization reads above (organization show, join-request show, tenant list/count).",
|
|
540
|
-
"The single-member read below
|
|
620
|
+
"The single-member read below carries the deferred disposition (out of v1): its documented least privilege is the write scope MultiTenantOrganization.ReadWrite.All in both delegated and application modes, so no named command or raw access is approved for it until its own least-privilege review lands.",
|
|
541
621
|
"",
|
|
542
622
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
543
623
|
"|---|---|---|---|",
|
|
544
|
-
...deferredMtoRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
624
|
+
...deferredMtoRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`), "",
|
|
545
625
|
"## EXT-04 reports scope decisions",
|
|
546
626
|
"",
|
|
547
627
|
"Firstmate scope: approve deferring the v1.0 relying-party summary read below; no named command or raw access is approved for it.",
|
|
548
|
-
"The relying-party summary read below
|
|
628
|
+
"The relying-party summary read below carries the deferred disposition (out of v1): its period function argument needs a new session-guard function-argument binding beyond the three allowlisted tenant-lookup/commerce routes, and the request-path guard must stay byte-identical to origin/main apart from READ_SCOPES additions, so no named command or raw access is approved for it until its own guard review lands.",
|
|
549
629
|
"",
|
|
550
630
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
551
631
|
"|---|---|---|---|",
|
|
552
|
-
...deferredReportsRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
632
|
+
...deferredReportsRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`), "",
|
|
553
633
|
"## EXT-04 tenant-lookup scope decisions",
|
|
554
634
|
"",
|
|
555
635
|
"Firstmate scope: approve the mg-ext-04e function-argument contract with the three v1.0 reads it unblocks (tenant-information show by domain name or tenant ID, subscription show by commerce subscription ID).",
|
|
@@ -559,7 +639,7 @@ export function capabilityDocument() {
|
|
|
559
639
|
"## EXT-02 access-review scope decisions",
|
|
560
640
|
"",
|
|
561
641
|
"The access-review scope covers the ten reads above (definition, instance, decision, contacted-reviewer and stage list/show reads).",
|
|
562
|
-
"The legacy accessReviews and unified alias reads below
|
|
642
|
+
"The legacy accessReviews and unified alias reads below carry the deferred disposition (out of v1) under the captain-declined EXT-02c follow-up (see docs/build-plan.md); beta-only operations remain scheduled (\"beta needs its own review\").", ,
|
|
563
643
|
"The history reads below are blocked: their documented least privilege is the write scope AccessReview.ReadWrite.All (no read scope), and history instances return SAS download URLs in downloadUri.",
|
|
564
644
|
"No new commands or raw access are approved for any row below.",
|
|
565
645
|
"",
|
|
@@ -570,18 +650,27 @@ export function capabilityDocument() {
|
|
|
570
650
|
"## EXT-02 entitlement-management scope decisions",
|
|
571
651
|
"",
|
|
572
652
|
"The entitlement-management scope covers the eighteen reads above (catalog, access-package, assignment-policy and resource-role-scope list/show/count reads plus assignment and assignment-request list/show/count reads).",
|
|
573
|
-
"Approval and subject reads carry personal data and stay
|
|
653
|
+
"Approval and subject reads carry personal data and stay deferred (out of v1) for later parts with no new commands or raw access approved; so do assignment and request navigation sub-reads (assignment target/accessPackage/assignmentPolicy, request accessPackage/assignment/requestor), the top-level assignment-policy and resource-role-scope lists, catalog navigation reads (accessPackages, resources, resourceScopes, resourceRoles, customWorkflowExtensions), package navigation reads (catalog, incompatible sets), policy navigation reads (questions, custom-extension stages), role/scope link expansion, filterByCurrentUser and additionalAccess functions (parenthesised function segments need their own session-guard binding review), while every beta operation below remains scheduled (never agreed for v1). Raw $expand review is a separate change: the named assignment and request reads flatten one fixed documented expansion in code, and no user-supplied $expand exists on any route.",
|
|
574
654
|
"",
|
|
575
655
|
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
576
656
|
"|---|---|---|---|",
|
|
577
|
-
...deferredEntitlementRows.map(row => `| \`${row.id}\` | ${row.disposition}
|
|
657
|
+
...deferredEntitlementRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
578
658
|
"",
|
|
579
659
|
"## EXT-02 lifecycle-workflows scope decisions",
|
|
580
660
|
"",
|
|
581
661
|
"This change covers the thirty-three v1.0 lifecycle-workflows first-, second-, third- and fourth-part reads above (workflow, workflow-template and task-definition list/show/count reads, the tenant settings show, top-level run and user/subject processing-result list/show/count reads, task-report list/show/count reads with the task and taskDefinition singles, plus run-nested user/subject/task processing-result list/show/count reads).",
|
|
582
|
-
"Third-level processing results, taskProcessingResults sub-reads under a user or subject result, reprocessed runs, subject sub-reads, workflow tasks, template tasks, versions, insights, deleted items and custom task extensions stay
|
|
662
|
+
"Third-level processing results, taskProcessingResults sub-reads under a user or subject result, reprocessed runs, subject sub-reads, workflow tasks, template tasks, versions, insights, deleted items and custom task extensions stay deferred (out of v1) for follow-up parts with no new commands or raw access approved; so do the six summary functions (the run user/subject summaries plus the four top-level ones), which stay deferred because they need bracketed start/end arguments outside the session-guard function-binding allowlist while the request-path guard cannot change beyond READ_SCOPES additions. Every beta operation below remains scheduled (never agreed for v1) and every mutation (no workflow create/update/delete/activate/run, no settings update, no restore) stays out.",
|
|
583
663
|
"No new commands or raw access are approved for any lifecycle-workflows row outside the thirty-three above.",
|
|
584
664
|
"",
|
|
665
|
+
"## READ-07 application lookups scope decisions",
|
|
666
|
+
"",
|
|
667
|
+
"This change covers seven v1.0 application and service-principal reads above (application and service-principal federated-credential list/show reads with credential metadata only, service-principal app-role-assigned-to list/show reads, and the service-principal app-role-assignment show companion).",
|
|
668
|
+
"The four parenthesised lookups below carry the deferred disposition (out of v1): the (appId='...') alternate-key forms and the federated-credential (name='...') function forms each need a new session-guard binding, and the request-path guard must stay byte-identical to origin/main apart from READ_SCOPES additions, so no named command or raw access is approved for them until their own guard review lands.", ,
|
|
669
|
+
"",
|
|
670
|
+
"| Inventory operation | Disposition | Owning slice | Deferral reason |",
|
|
671
|
+
"|---|---|---|---|",
|
|
672
|
+
...deferredAppLookupRows.map(row => `| \`${row.id}\` | ${row.disposition} | ${row.owningSlice} | ${row.reason} |`),
|
|
673
|
+
"",
|
|
585
674
|
"## Named writes",
|
|
586
675
|
"",
|
|
587
676
|
"Each write below runs the WRITE-00 mutation coordinator: hand-enabled profile, immutable scope, preview, explicit `--execute`, typed target confirmation for disruptive effects, durable journal intent/outcome and no replay. Inventory dispositions stay discovery-time records until a metadata refresh reviews them; the implemented review lives beside each command.",
|
|
@@ -593,7 +682,7 @@ export function capabilityDocument() {
|
|
|
593
682
|
`Shipped initial writes: ${writeStatus.shipped.length ? writeStatus.shipped.join(", ") : "none"}.`,
|
|
594
683
|
`Pending initial writes: ${writeStatus.pending.length ? writeStatus.pending.join(", ") : "none"}.`,
|
|
595
684
|
"Extended families (EXT-01 through EXT-04) and the full-Entra audit (FULL-01, COMPLETE-01) own the remaining",
|
|
596
|
-
"
|
|
685
|
+
"deferred rows out of v1; see docs/build-plan.md for their dispatch.",
|
|
597
686
|
"",
|
|
598
687
|
].join("\n");
|
|
599
688
|
}
|