@wairon/cli 5.0.2-dev.14 → 5.0.2-dev.15

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/dist/cli/index.js CHANGED
@@ -65,7 +65,7 @@ var init_defaults = __esm({
65
65
  copilot: ".github/prompts",
66
66
  codex: ".codex/agents"
67
67
  };
68
- WAIRON_VERSION = "5.0.2-dev.14";
68
+ WAIRON_VERSION = "5.0.2-dev.15";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  ARCHITECT_AGENT_ID = "agent-architect";
71
71
  ARCHITECT_TEMPLATE_ID = "architect";
@@ -458,7 +458,7 @@ var init_domain = __esm({
458
458
  });
459
459
 
460
460
  // src/models/project.ts
461
- var import_zod3, BuiltinTargetConfigSchema, CustomTargetConfigSchema, TargetConfigSchema, NamingRuleConfigSchema, DocumentationRuleConfigSchema, ComplexityRuleConfigSchema, DesignDepthSchema, RulesConfigSchema, PathsConfigSchema, ProjectConfigSchema;
461
+ var import_zod3, BuiltinTargetConfigSchema, CustomTargetConfigSchema, TargetConfigSchema, NamingRuleConfigSchema, DocumentationRuleConfigSchema, ComplexityRuleConfigSchema, DesignDepthSchema, RulesConfigSchema, PathsConfigSchema, ProfileSelectionSubjectSchema, ProjectProfileSelectionSchema, ProjectConfigSchema;
462
462
  var init_project = __esm({
463
463
  "src/models/project.ts"() {
464
464
  "use strict";
@@ -582,6 +582,24 @@ var init_project = __esm({
582
582
  /** Base directory containing SDD specification files, relative to project root */
583
583
  specsDir: import_zod3.z.string().default(".wai/specs")
584
584
  });
585
+ ProfileSelectionSubjectSchema = import_zod3.z.object({
586
+ userId: import_zod3.z.string(),
587
+ kind: import_zod3.z.string(),
588
+ issuer: import_zod3.z.string(),
589
+ externalSubject: import_zod3.z.string().optional(),
590
+ displayName: import_zod3.z.string().optional(),
591
+ email: import_zod3.z.string().optional()
592
+ });
593
+ ProjectProfileSelectionSchema = import_zod3.z.object({
594
+ /** Selected architectural profile ids. The first resolvable one is applied as projectType. */
595
+ profileIds: import_zod3.z.array(import_zod3.z.string()).default([]),
596
+ /** Pack names the governing policy requires for this project. */
597
+ requiredPackNames: import_zod3.z.array(import_zod3.z.string()).default([]),
598
+ /** Pack names applied by default unless explicitly overridden. */
599
+ defaultPackNames: import_zod3.z.array(import_zod3.z.string()).optional(),
600
+ selectedBy: ProfileSelectionSubjectSchema.optional(),
601
+ selectedAt: import_zod3.z.string()
602
+ });
585
603
  ProjectConfigSchema = import_zod3.z.object({
586
604
  /**
587
605
  * Schema version — used to detect incompatible config formats in future
@@ -622,6 +640,13 @@ var init_project = __esm({
622
640
  useGlobalPacks: import_zod3.z.boolean().default(true)
623
641
  }).optional(),
624
642
  paths: PathsConfigSchema.default({}),
643
+ /**
644
+ * The profile/pack selection a hosted policy workflow applied to this project.
645
+ * The RECORD of what was chosen; `projectType` above is what actually governs
646
+ * validation. Modeled so the parse/write round trip preserves it (see
647
+ * ProjectProfileSelectionSchema).
648
+ */
649
+ profileSelection: ProjectProfileSelectionSchema.optional(),
625
650
  /**
626
651
  * Path to a directory containing org/user-level default templates.
627
652
  * Resolved before built-in templates but after project-local templates.
@@ -7313,7 +7338,7 @@ var init_extensions = __esm({
7313
7338
  });
7314
7339
 
7315
7340
  // src/core/rules/types.ts
7316
- var BUILTIN_PROFILES;
7341
+ var BUILTIN_PROFILES, PROJECT_KINDS;
7317
7342
  var init_types = __esm({
7318
7343
  "src/core/rules/types.ts"() {
7319
7344
  "use strict";
@@ -7326,6 +7351,7 @@ var init_types = __esm({
7326
7351
  "realtime-embedded",
7327
7352
  "plc-cyclic"
7328
7353
  ];
7354
+ PROJECT_KINDS = ["fullstack", "system-of-systems", "monorepo"];
7329
7355
  }
7330
7356
  });
7331
7357
 
@@ -10509,14 +10535,14 @@ var init_declarative_assertions = __esm({
10509
10535
  });
10510
10536
 
10511
10537
  // src/core/rules/profiles.ts
10512
- var BACKEND_LIKE, FRONTEND_LIKE, PROJECT_KINDS, profilesRule;
10538
+ var BACKEND_LIKE, FRONTEND_LIKE, PROJECT_KINDS2, profilesRule;
10513
10539
  var init_profiles = __esm({
10514
10540
  "src/core/rules/profiles.ts"() {
10515
10541
  "use strict";
10516
10542
  init_types();
10517
10543
  BACKEND_LIKE = /* @__PURE__ */ new Set(["backend", "lowlevel-os", "game-ecs", "realtime-embedded", "plc-cyclic"]);
10518
10544
  FRONTEND_LIKE = /* @__PURE__ */ new Set(["frontend-reactive", "frontend-controller"]);
10519
- PROJECT_KINDS = /* @__PURE__ */ new Set(["fullstack", "system-of-systems", "monorepo"]);
10545
+ PROJECT_KINDS2 = new Set(PROJECT_KINDS);
10520
10546
  profilesRule = {
10521
10547
  name: "architectural-profiles",
10522
10548
  description: "Per-profile stereotype constraints: View/FeatureComponent/RouterComponent only in frontend profiles; Actor/Supervisor forbidden in plc-cyclic (single scan cycle); Actor/Supervisor in frontend profiles warned. Extension packs may register custom profiles (family + forbidden/discouraged stereotype lists); unknown profile names are flagged.",
@@ -10540,7 +10566,7 @@ var init_profiles = __esm({
10540
10566
  );
10541
10567
  }
10542
10568
  }
10543
- if (!registered.has(ctx.projectType) && !PROJECT_KINDS.has(ctx.projectType)) {
10569
+ if (!registered.has(ctx.projectType) && !PROJECT_KINDS2.has(ctx.projectType)) {
10544
10570
  ctx.addIssue(
10545
10571
  "warning",
10546
10572
  "UNKNOWN_PROFILE",
@@ -27126,7 +27152,15 @@ var hostCore = {
27126
27152
  /** The ids of wairon's built-in architectural profiles, read from the core rules
27127
27153
  * registry's built-in profile set (BUILTIN_PROFILES) — a pure, side-effect-free
27128
27154
  * read of a bundled constant. */
27129
- builtinProfileIds: () => [...BUILTIN_PROFILES]
27155
+ builtinProfileIds: () => [...BUILTIN_PROFILES],
27156
+ /** The ids of wairon's built-in COMPOSITE PROJECT KINDS, read from the core
27157
+ * rules registry's bundled constant (PROJECT_KINDS) — a pure, side-effect-free
27158
+ * read. The counterpart of builtinProfileIds: legal projectType values that
27159
+ * are not architectural profiles and carry no profile doctrine of their own,
27160
+ * so the hosted profile-application path recognizes a project kind as
27161
+ * resolvable-as-is (no contributing pack to adopt) instead of refusing it as
27162
+ * an unknown profile. */
27163
+ builtinProjectKinds: () => [...PROJECT_KINDS]
27130
27164
  };
27131
27165
  function resolveContainedProjectPath(projectRoot2, projectPath) {
27132
27166
  return assertContainedProjectPath(projectRoot2, projectPath);
@@ -27747,6 +27781,35 @@ function storeListGlobalPacks() {
27747
27781
  );
27748
27782
  return [...instance, ...image];
27749
27783
  }
27784
+ function scanGlobalPackProfiles() {
27785
+ const out = [];
27786
+ for (const dir of [hostCore.globalPacksDir(), imagePacksDir()]) {
27787
+ for (const full of hostCore.discoverPacks(dir)) {
27788
+ try {
27789
+ const loaded = hostCore.loadExtensionPacks([{ ref: full, scope: "global" }], path45.dirname(full));
27790
+ if (loaded.errors.length) continue;
27791
+ const source = loaded.packNames[0] ?? path45.basename(full);
27792
+ for (const [id, def] of Object.entries(loaded.profiles)) out.push({ id, source, family: def.family });
27793
+ } catch {
27794
+ }
27795
+ }
27796
+ }
27797
+ return out;
27798
+ }
27799
+ function scanProjectPackProfiles() {
27800
+ const root = getProjectRoot();
27801
+ const out = [];
27802
+ for (const ref of loadProjectConfig().extensions?.packs ?? []) {
27803
+ try {
27804
+ const loaded = hostCore.loadExtensionPacks([{ ref, scope: "project" }], root);
27805
+ if (loaded.errors.length) continue;
27806
+ const source = loaded.packNames[0] ?? stem(ref);
27807
+ for (const [id, def] of Object.entries(loaded.profiles)) out.push({ id, source, family: def.family });
27808
+ } catch {
27809
+ }
27810
+ }
27811
+ return out;
27812
+ }
27750
27813
  function storeListAvailableProfiles() {
27751
27814
  const out = [];
27752
27815
  const seen = /* @__PURE__ */ new Set();
@@ -27757,19 +27820,21 @@ function storeListAvailableProfiles() {
27757
27820
  out.push(family ? { id, source, family } : { id, source });
27758
27821
  };
27759
27822
  for (const id of hostCore.builtinProfileIds()) emit(id, "builtin");
27760
- const scanTier = (dir) => {
27761
- for (const full of hostCore.discoverPacks(dir)) {
27762
- try {
27763
- const loaded = hostCore.loadExtensionPacks([{ ref: full, scope: "global" }], path45.dirname(full));
27764
- if (loaded.errors.length) continue;
27765
- const source = loaded.packNames[0] ?? path45.basename(full);
27766
- for (const [id, def] of Object.entries(loaded.profiles)) emit(id, source, def.family);
27767
- } catch {
27768
- }
27769
- }
27823
+ for (const c of scanGlobalPackProfiles()) emit(c.id, c.source, c.family);
27824
+ return out;
27825
+ }
27826
+ function storeListProjectProfiles() {
27827
+ const out = [];
27828
+ const seen = /* @__PURE__ */ new Set();
27829
+ const emit = (id, source, installed, family) => {
27830
+ const key = JSON.stringify([id, source]);
27831
+ if (seen.has(key)) return;
27832
+ seen.add(key);
27833
+ out.push({ id, source, ...family ? { family } : {}, installed });
27770
27834
  };
27771
- scanTier(hostCore.globalPacksDir());
27772
- scanTier(imagePacksDir());
27835
+ for (const id of hostCore.builtinProfileIds()) emit(id, "builtin", true);
27836
+ for (const c of scanProjectPackProfiles()) emit(c.id, c.source, true, c.family);
27837
+ for (const c of scanGlobalPackProfiles()) emit(c.id, c.source, false, c.family);
27773
27838
  return out;
27774
27839
  }
27775
27840
  function readPackContent(full) {
@@ -27962,6 +28027,10 @@ function listAvailableProfiles(cfg, credential) {
27962
28027
  requirePrincipal2(cfg, credential);
27963
28028
  return storeListAvailableProfiles();
27964
28029
  }
28030
+ function listProjectProfiles(cfg, credential, project2) {
28031
+ requireCap(cfg, credential, "project:read", "project", project2, "Forbidden \u2014 listing a project's selectable profiles requires project:read over the project");
28032
+ return executeApprovedListProjectProfiles(cfg, project2);
28033
+ }
27965
28034
  function listAdoptableProjectPacks(cfg, credential, project2) {
27966
28035
  requireCap(cfg, credential, "project:read", "project", project2, "Forbidden \u2014 listing adoptable packs requires project:read over the project");
27967
28036
  return storeListGlobalPacks();
@@ -27984,6 +28053,26 @@ function executeApprovedInstallProjectPack(cfg, project2, name, content) {
27984
28053
  function executeApprovedResolveGlobalPacks(names) {
27985
28054
  return storeResolveGlobalPacks(names);
27986
28055
  }
28056
+ function executeApprovedListProjectProfiles(cfg, project2) {
28057
+ return runWithProjectRoot(boundProject2(cfg, project2), () => storeListProjectProfiles());
28058
+ }
28059
+ function executeApprovedEnsureProfileInstalled(cfg, project2, profileId) {
28060
+ if (hostCore.builtinProjectKinds().includes(profileId) || hostCore.builtinProfileIds().includes(profileId)) {
28061
+ return { profileId, source: "builtin" };
28062
+ }
28063
+ const contributors = executeApprovedListProjectProfiles(cfg, project2).filter((p) => p.id === profileId);
28064
+ const installed = contributors.find((p) => p.installed);
28065
+ if (installed) return { profileId, source: installed.source };
28066
+ const adoptable = contributors[0];
28067
+ const resolved = adoptable ? executeApprovedResolveGlobalPacks([adoptable.source]).resolved[0] : void 0;
28068
+ if (!resolved) {
28069
+ throw new Error(
28070
+ `Unknown profile "${profileId}" \u2014 no built-in profile or project kind carries it, no pack registered in project "${project2}" contributes it, and no server-global pack (mutable instance tier or immutable image tier) contributes it. Writing an unresolvable id as the projectType would silently disable the whole profile doctrine (UNKNOWN_PROFILE), so it is refused instead of applied.`
28071
+ );
28072
+ }
28073
+ executeApprovedInstallProjectPack(cfg, project2, resolved.name, resolved.content);
28074
+ return { profileId, source: resolved.name, adoptedPackName: resolved.name };
28075
+ }
27987
28076
  function removeProjectPack(cfg, credential, project2, name) {
27988
28077
  requireCap(cfg, credential, "project:admin", "project", project2, "Forbidden \u2014 removing a project pack requires project:admin over the project");
27989
28078
  runWithProjectRoot(boundProject2(cfg, project2), () => storeRemoveProjectPack(name));
@@ -28978,6 +29067,40 @@ function requestPackNames(request) {
28978
29067
  if (!sel) return [];
28979
29068
  return [.../* @__PURE__ */ new Set([...sel.requiredPackNames ?? [], ...sel.defaultPackNames ?? []])];
28980
29069
  }
29070
+ function classifyProfile(projectType, catalog) {
29071
+ if (hostCore.builtinProfileIds().includes(projectType) || hostCore.builtinProjectKinds().includes(projectType)) {
29072
+ return { source: "builtin", resolvable: true };
29073
+ }
29074
+ const installed = catalog.find((p) => p.id === projectType && p.installed);
29075
+ if (installed) return { source: installed.source, resolvable: true };
29076
+ return { resolvable: false };
29077
+ }
29078
+ function firstApplicableProfileId(candidates, catalog) {
29079
+ const kinds = hostCore.builtinProjectKinds();
29080
+ const catalogIds = new Set(catalog.map((p) => p.id));
29081
+ return candidates.find((id) => kinds.includes(id) || catalogIds.has(id));
29082
+ }
29083
+ function unappliedIds(selectedProfileIds, governingProfileId) {
29084
+ return selectedProfileIds.filter((id) => id !== governingProfileId);
29085
+ }
29086
+ function foldAppliedProfile(root, profileId, actor) {
29087
+ const recorded = readProjectProfileSelection(root);
29088
+ const folded = {
29089
+ profileIds: [profileId, ...unappliedIds(recorded?.profileIds ?? [], profileId)],
29090
+ requiredPackNames: recorded?.requiredPackNames ?? [],
29091
+ selectedBy: actor,
29092
+ selectedAt: (/* @__PURE__ */ new Date()).toISOString()
29093
+ };
29094
+ if (recorded?.defaultPackNames) folded.defaultPackNames = recorded.defaultPackNames;
29095
+ recordProjectProfileSelection(root, folded);
29096
+ return folded;
29097
+ }
29098
+ function overridingSubsystemIds(root) {
29099
+ return runWithProjectRoot(
29100
+ root,
29101
+ () => hostCore.loadSubsystemSpecs().filter((s) => !!s.profile).map((s) => s.id)
29102
+ );
29103
+ }
28981
29104
  function resolvedSelection(request, policy, selectedBy) {
28982
29105
  const sel = request.profileSelection;
28983
29106
  const selection = {
@@ -28996,7 +29119,8 @@ function buildEvaluation(input) {
28996
29119
  selectedProfileIds,
28997
29120
  hasSelection,
28998
29121
  countMissingPacksAsViolation,
28999
- requiredDefaultResolution
29122
+ requiredDefaultResolution,
29123
+ governingProfileId
29000
29124
  } = input;
29001
29125
  const present = new Set(presentPackNames);
29002
29126
  let missingPackNames;
@@ -29017,6 +29141,7 @@ function buildEvaluation(input) {
29017
29141
  const allowed = policy.allowedProfileIds;
29018
29142
  const disallowedProfileIds = allowed && allowed.length > 0 ? selectedProfileIds.filter((id) => !allowed.includes(id)) : [];
29019
29143
  const selectionRequiredUnmet = policy.requireProfileSelection && !hasSelection;
29144
+ const unappliedProfileIds = governingProfileId ? unappliedIds(selectedProfileIds, governingProfileId) : [];
29020
29145
  const messages = [];
29021
29146
  if (selectionRequiredUnmet) {
29022
29147
  messages.push("Profile selection is required by policy but none was provided.");
@@ -29028,16 +29153,24 @@ function buildEvaluation(input) {
29028
29153
  for (const n of blockedPackNames) messages.push(`Pack "${n}" is blocked by policy.`);
29029
29154
  for (const id of missingProfileIds) messages.push(`Required profile "${id}" is not selected.`);
29030
29155
  for (const id of disallowedProfileIds) messages.push(`Profile "${id}" is not permitted by policy.`);
29156
+ for (const id of unappliedProfileIds) {
29157
+ messages.push(
29158
+ `Selected profile "${id}" is recorded but does not govern the project (projectType is "${governingProfileId}") \u2014 a project has exactly one governing profile.`
29159
+ );
29160
+ }
29031
29161
  const violation = selectionRequiredUnmet || blockedPackNames.length > 0 || missingProfileIds.length > 0 || disallowedProfileIds.length > 0 || countMissingPacksAsViolation && (missingPackNames.length > 0 || unresolvedPacks.length > 0);
29032
- return {
29162
+ const result = {
29033
29163
  compliant: !violation,
29034
29164
  mode: policy.enforcementMode,
29035
29165
  missingPackNames,
29036
29166
  blockedPackNames,
29037
29167
  missingProfileIds,
29038
29168
  unresolvedPacks,
29169
+ unappliedProfileIds,
29039
29170
  messages
29040
29171
  };
29172
+ if (governingProfileId) result.governingProfileId = governingProfileId;
29173
+ return result;
29041
29174
  }
29042
29175
  function performInit(cfg, request, principal) {
29043
29176
  const policy = effectivePolicy(cfg.dataDir);
@@ -29057,17 +29190,24 @@ function performInit(cfg, request, principal) {
29057
29190
  request.ownerUnitId,
29058
29191
  principal ? principalSubject3(principal) : void 0
29059
29192
  );
29060
- installResolvedPacks(
29061
- cfg,
29062
- record2.id,
29063
- executeApprovedResolveGlobalPacks([
29064
- ...policy.requiredGlobalPacks,
29065
- ...policy.defaultProjectPacks,
29066
- ...requestPackNames(request)
29067
- ])
29068
- );
29193
+ const packResolution = executeApprovedResolveGlobalPacks([
29194
+ ...policy.requiredGlobalPacks,
29195
+ ...policy.defaultProjectPacks,
29196
+ ...requestPackNames(request)
29197
+ ]);
29198
+ installResolvedPacks(cfg, record2.id, packResolution);
29069
29199
  const selectedBy = principal ? principalSubject3(principal) : void 0;
29070
- recordProjectProfileSelection(record2.rootPath, resolvedSelection(request, policy, selectedBy));
29200
+ const selection = resolvedSelection(request, policy, selectedBy);
29201
+ recordProjectProfileSelection(record2.rootPath, selection);
29202
+ const catalog = executeApprovedListProjectProfiles(cfg, record2.id);
29203
+ const appliedProfileId = firstApplicableProfileId(selection.profileIds, catalog);
29204
+ let appliedSource;
29205
+ if (appliedProfileId) {
29206
+ const application = executeApprovedEnsureProfileInstalled(cfg, record2.id, appliedProfileId);
29207
+ writeProjectType(record2.rootPath, application.profileId);
29208
+ appliedSource = application.source;
29209
+ }
29210
+ const unapplied = appliedProfileId ? unappliedIds(selection.profileIds, appliedProfileId) : [...selection.profileIds];
29071
29211
  const actor = principal ? principalSubject3(principal) : SYSTEM_SUBJECT;
29072
29212
  tryAppendAudit2(
29073
29213
  cfg,
@@ -29076,7 +29216,18 @@ function performInit(cfg, request, principal) {
29076
29216
  "project.init.policy",
29077
29217
  "info",
29078
29218
  "project",
29079
- { target: record2.id, projectId: record2.id },
29219
+ {
29220
+ target: record2.id,
29221
+ projectId: record2.id,
29222
+ metadata: JSON.stringify({
29223
+ ...appliedProfileId ? { appliedProfileId, profileSource: appliedSource } : {
29224
+ appliedProfileId: null,
29225
+ profileNotApplied: selection.profileIds.length === 0 ? "no profile was selected \u2014 the default projectType stands" : "no selected profile is resolvable on this instance \u2014 the default projectType stands"
29226
+ },
29227
+ ...unapplied.length > 0 ? { unappliedProfileIds: unapplied } : {},
29228
+ ...packResolution.unresolved.length > 0 ? { unresolvedPacks: packResolution.unresolved } : {}
29229
+ })
29230
+ },
29080
29231
  principal?.tokenId
29081
29232
  )
29082
29233
  );
@@ -29117,6 +29268,7 @@ function evaluateProjectPolicy(cfg, credential, projectId) {
29117
29268
  const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
29118
29269
  if (!root) throw new Error(`Unknown project "${projectId}".`);
29119
29270
  const policy = effectivePolicy(cfg.dataDir);
29271
+ const governingProfileId = readProjectType(root);
29120
29272
  const selection = readProjectProfileSelection(root);
29121
29273
  return buildEvaluation({
29122
29274
  policy,
@@ -29124,7 +29276,8 @@ function evaluateProjectPolicy(cfg, credential, projectId) {
29124
29276
  selectedProfileIds: selection?.profileIds ?? [],
29125
29277
  hasSelection: !!selection,
29126
29278
  countMissingPacksAsViolation: true,
29127
- requiredDefaultResolution: executeApprovedResolveGlobalPacks(requiredDefaultNames(policy))
29279
+ requiredDefaultResolution: executeApprovedResolveGlobalPacks(requiredDefaultNames(policy)),
29280
+ governingProfileId
29128
29281
  });
29129
29282
  }
29130
29283
  function reconcileProjectPolicy(cfg, credential, projectId) {
@@ -29139,20 +29292,41 @@ function reconcileProjectPolicy(cfg, credential, projectId) {
29139
29292
  const policy = effectivePolicy(cfg.dataDir);
29140
29293
  const selection = readProjectProfileSelection(root);
29141
29294
  const resolution = executeApprovedResolveGlobalPacks(requiredDefaultNames(policy));
29142
- let installed = installedPackNames(cfg, projectId);
29143
- const installedSet = new Set(installed);
29295
+ const installedSet = new Set(installedPackNames(cfg, projectId));
29144
29296
  const toApply = resolution.resolved.filter((p) => !installedSet.has(p.name));
29297
+ const appliedPackNames = toApply.map((p) => p.name);
29145
29298
  if (toApply.length > 0) {
29146
29299
  installResolvedPacks(cfg, projectId, { resolved: toApply, unresolved: [] });
29147
- installed = installedPackNames(cfg, projectId);
29300
+ }
29301
+ const catalog = executeApprovedListProjectProfiles(cfg, projectId);
29302
+ const previousProfileId = readProjectType(root);
29303
+ let governingProfileId = previousProfileId;
29304
+ const requiredProfileIds = policy.requiredProfileIds ?? [];
29305
+ const policyUnsatisfied = requiredProfileIds.length > 0 && !requiredProfileIds.includes(governingProfileId);
29306
+ const governingUnresolvable = !classifyProfile(governingProfileId, catalog).resolvable;
29307
+ let repairedProfileId;
29308
+ let selectedProfileIds = selection?.profileIds ?? [];
29309
+ if (policyUnsatisfied || governingUnresolvable) {
29310
+ const target = firstApplicableProfileId(
29311
+ [...requiredProfileIds, ...selection?.profileIds ?? []],
29312
+ catalog
29313
+ );
29314
+ if (target) {
29315
+ const application = executeApprovedEnsureProfileInstalled(cfg, projectId, target);
29316
+ writeProjectType(root, application.profileId);
29317
+ governingProfileId = application.profileId;
29318
+ repairedProfileId = application.profileId;
29319
+ selectedProfileIds = foldAppliedProfile(root, application.profileId, principalSubject3(principal)).profileIds;
29320
+ }
29148
29321
  }
29149
29322
  const result = buildEvaluation({
29150
29323
  policy,
29151
- presentPackNames: installed,
29152
- selectedProfileIds: selection?.profileIds ?? [],
29324
+ presentPackNames: installedPackNames(cfg, projectId),
29325
+ selectedProfileIds,
29153
29326
  hasSelection: !!selection,
29154
29327
  countMissingPacksAsViolation: true,
29155
- requiredDefaultResolution: resolution
29328
+ requiredDefaultResolution: resolution,
29329
+ governingProfileId
29156
29330
  });
29157
29331
  tryAppendAudit2(
29158
29332
  cfg,
@@ -29161,7 +29335,15 @@ function reconcileProjectPolicy(cfg, credential, projectId) {
29161
29335
  "policy.reconcile",
29162
29336
  "info",
29163
29337
  "policy",
29164
- { target: projectId, projectId },
29338
+ {
29339
+ target: projectId,
29340
+ projectId,
29341
+ metadata: JSON.stringify({
29342
+ ...appliedPackNames.length > 0 ? { appliedPackNames } : {},
29343
+ ...repairedProfileId ? { repairedProfileId, previousProfileId } : {},
29344
+ ...resolution.unresolved.length > 0 ? { unresolvedPacks: resolution.unresolved } : {}
29345
+ })
29346
+ },
29165
29347
  principal.tokenId
29166
29348
  )
29167
29349
  );
@@ -29177,8 +29359,19 @@ function getProjectConfig(cfg, credential, projectId) {
29177
29359
  const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
29178
29360
  if (!root) throw new Error(`Unknown project "${projectId}".`);
29179
29361
  const projectType = readProjectType(root);
29362
+ const selection = readProjectProfileSelection(root);
29363
+ const classified = classifyProfile(projectType, executeApprovedListProjectProfiles(cfg, projectId));
29180
29364
  const locked = runWithProjectRoot(root, () => hostCore.readLockRecord() !== null);
29181
- return { projectType, locked };
29365
+ const overriding = overridingSubsystemIds(root);
29366
+ const view = {
29367
+ projectType,
29368
+ locked,
29369
+ profileResolvable: classified.resolvable,
29370
+ unappliedProfileIds: unappliedIds(selection?.profileIds ?? [], projectType),
29371
+ overridingSubsystemIds: overriding
29372
+ };
29373
+ if (classified.source) view.profileSource = classified.source;
29374
+ return view;
29182
29375
  }
29183
29376
  function setProjectType(cfg, credential, projectId, projectType) {
29184
29377
  const principal = requirePrincipal4(cfg, credential);
@@ -29189,9 +29382,23 @@ function setProjectType(cfg, credential, projectId, projectType) {
29189
29382
  }
29190
29383
  const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
29191
29384
  if (!root) throw new Error(`Unknown project "${projectId}".`);
29192
- writeProjectType(root, projectType);
29385
+ const application = executeApprovedEnsureProfileInstalled(cfg, projectId, projectType);
29386
+ writeProjectType(root, application.profileId);
29387
+ const folded = foldAppliedProfile(root, application.profileId, principalSubject3(principal));
29388
+ const remainder = folded.profileIds.slice(1);
29193
29389
  const locked = runWithProjectRoot(root, () => hostCore.readLockRecord() !== null);
29194
- return { projectType, locked };
29390
+ const overriding = overridingSubsystemIds(root);
29391
+ const view = {
29392
+ projectType: application.profileId,
29393
+ locked,
29394
+ profileSource: application.source,
29395
+ // The write path guarantees resolvability — the ensure seam refused anything else.
29396
+ profileResolvable: true,
29397
+ unappliedProfileIds: remainder,
29398
+ overridingSubsystemIds: overriding
29399
+ };
29400
+ if (application.adoptedPackName) view.adoptedPackName = application.adoptedPackName;
29401
+ return view;
29195
29402
  }
29196
29403
  function getPackPolicy(cfg, credential) {
29197
29404
  requirePrincipal4(cfg, credential);
@@ -32185,6 +32392,9 @@ function getProjectConfig2(cfg, credential, projectId) {
32185
32392
  function setProjectType2(cfg, credential, projectId, projectType) {
32186
32393
  return setProjectType(cfg, credential, projectId, projectType);
32187
32394
  }
32395
+ function listProjectProfiles2(cfg, credential, project2) {
32396
+ return listProjectProfiles(cfg, credential, project2);
32397
+ }
32188
32398
  function listProducers2(cfg, credential, project2) {
32189
32399
  return listProducers(cfg, credential, project2);
32190
32400
  }
@@ -35546,6 +35756,9 @@ function opsGetProjectConfig(cfg, sessionId, url, res) {
35546
35756
  function opsSetProjectConfig(cfg, sessionId, body, res) {
35547
35757
  sendJson(res, 200, setProjectType2(cfg, sessionId, String(body?.projectId ?? ""), String(body?.projectType ?? "")));
35548
35758
  }
35759
+ function opsListProjectProfiles(cfg, sessionId, url, res) {
35760
+ sendJson(res, 200, { profiles: listProjectProfiles2(cfg, sessionId, q(url, "projectId") ?? "") });
35761
+ }
35549
35762
  function opsListProducers(cfg, sessionId, url, res) {
35550
35763
  sendJson(res, 200, { producers: listProducers2(cfg, sessionId, q(url, "projectId") ?? "") });
35551
35764
  }
@@ -35785,6 +35998,9 @@ async function handleWebRequest(cfg, req, res, body, url, ctx) {
35785
35998
  if (req.method === "POST" && parts.length === 3 && parts[2] === "config") {
35786
35999
  return opsSetProjectConfig(cfg, sessionId, body, res);
35787
36000
  }
36001
+ if (req.method === "GET" && parts.length === 3 && parts[2] === "profiles") {
36002
+ return opsListProjectProfiles(cfg, sessionId, url, res);
36003
+ }
35788
36004
  if (req.method === "GET" && parts.length === 3 && parts[2] === "policy") {
35789
36005
  return opsPolicyEvaluate(cfg, sessionId, url, res);
35790
36006
  }