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

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.16";
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
 
@@ -7986,11 +8012,15 @@ function projectSubsystemSurface(subsystemId) {
7986
8012
  const interfaces = loadInterfaceSpecs();
7987
8013
  const types = loadTypeSpecs();
7988
8014
  const entries = [];
8015
+ const unprojectable = [];
7989
8016
  for (const pub of target.publicInterfaces ?? []) {
7990
8017
  if (!pub.component) continue;
7991
8018
  const comp = components.find((c) => c.id === pub.component || c.id === `${subsystemId}::${pub.component}`);
7992
8019
  if (!comp) continue;
7993
- if (comp.componentType !== "Portal") continue;
8020
+ if (!CROSS_BOUNDARY_TARGETS.has(comp.componentType)) {
8021
+ unprojectable.push({ component: pub.component, componentType: comp.componentType });
8022
+ continue;
8023
+ }
7994
8024
  const compInterfaces = interfaces.filter((i) => i.component === comp.id && (!pub.interface || i.id === pub.interface || i.id === `${subsystemId}::${pub.interface}`));
7995
8025
  const methods = compInterfaces.flatMap((i) => i.methods);
7996
8026
  entries.push({
@@ -8004,13 +8034,18 @@ function projectSubsystemSurface(subsystemId) {
8004
8034
  component: localName(comp.id),
8005
8035
  methods,
8006
8036
  ...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
8007
- // Project the backing Portal's auth + basePath so the codec can emit
8037
+ // Project the backing component's auth + basePath so the codec can emit
8008
8038
  // OpenAPI security + per-portal servers self-contained from the snapshot.
8009
8039
  ...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
8010
8040
  ...comp.basePath ? { basePath: comp.basePath } : {},
8011
8041
  details: pub.details ?? ""
8012
8042
  });
8013
8043
  }
8044
+ for (const skipped of unprojectable) {
8045
+ console.error(
8046
+ `[surfaces] skipped "${subsystemId}::${skipped.component}": a published ${skipped.componentType} can never serve a cross-boundary caller, so it stays out of every chained child's sibling surface \u2014 publish this surface through a Portal, a Gateway, or an Observer (for events).`
8047
+ );
8048
+ }
8014
8049
  return SurfaceSnapshotSchema.parse({
8015
8050
  projectName: `${system.name}::${subsystemId}`,
8016
8051
  origin: "generated",
@@ -8189,7 +8224,7 @@ function checkChildSurfaceFreshness(rootDir = getProjectRoot()) {
8189
8224
  }
8190
8225
  return issues;
8191
8226
  }
8192
- var fs9, path10, SURFACES_DIRNAME;
8227
+ var fs9, path10, SURFACES_DIRNAME, CROSS_BOUNDARY_TARGETS;
8193
8228
  var init_surfaces = __esm({
8194
8229
  "src/core/surfaces.ts"() {
8195
8230
  "use strict";
@@ -8204,6 +8239,7 @@ var init_surfaces = __esm({
8204
8239
  init_type_analysis();
8205
8240
  init_openapi();
8206
8241
  SURFACES_DIRNAME = "surfaces";
8242
+ CROSS_BOUNDARY_TARGETS = /* @__PURE__ */ new Set(["Portal", "Gateway", "Observer"]);
8207
8243
  }
8208
8244
  });
8209
8245
 
@@ -10509,14 +10545,14 @@ var init_declarative_assertions = __esm({
10509
10545
  });
10510
10546
 
10511
10547
  // src/core/rules/profiles.ts
10512
- var BACKEND_LIKE, FRONTEND_LIKE, PROJECT_KINDS, profilesRule;
10548
+ var BACKEND_LIKE, FRONTEND_LIKE, PROJECT_KINDS2, profilesRule;
10513
10549
  var init_profiles = __esm({
10514
10550
  "src/core/rules/profiles.ts"() {
10515
10551
  "use strict";
10516
10552
  init_types();
10517
10553
  BACKEND_LIKE = /* @__PURE__ */ new Set(["backend", "lowlevel-os", "game-ecs", "realtime-embedded", "plc-cyclic"]);
10518
10554
  FRONTEND_LIKE = /* @__PURE__ */ new Set(["frontend-reactive", "frontend-controller"]);
10519
- PROJECT_KINDS = /* @__PURE__ */ new Set(["fullstack", "system-of-systems", "monorepo"]);
10555
+ PROJECT_KINDS2 = new Set(PROJECT_KINDS);
10520
10556
  profilesRule = {
10521
10557
  name: "architectural-profiles",
10522
10558
  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 +10576,7 @@ var init_profiles = __esm({
10540
10576
  );
10541
10577
  }
10542
10578
  }
10543
- if (!registered.has(ctx.projectType) && !PROJECT_KINDS.has(ctx.projectType)) {
10579
+ if (!registered.has(ctx.projectType) && !PROJECT_KINDS2.has(ctx.projectType)) {
10544
10580
  ctx.addIssue(
10545
10581
  "warning",
10546
10582
  "UNKNOWN_PROFILE",
@@ -27126,7 +27162,15 @@ var hostCore = {
27126
27162
  /** The ids of wairon's built-in architectural profiles, read from the core rules
27127
27163
  * registry's built-in profile set (BUILTIN_PROFILES) — a pure, side-effect-free
27128
27164
  * read of a bundled constant. */
27129
- builtinProfileIds: () => [...BUILTIN_PROFILES]
27165
+ builtinProfileIds: () => [...BUILTIN_PROFILES],
27166
+ /** The ids of wairon's built-in COMPOSITE PROJECT KINDS, read from the core
27167
+ * rules registry's bundled constant (PROJECT_KINDS) — a pure, side-effect-free
27168
+ * read. The counterpart of builtinProfileIds: legal projectType values that
27169
+ * are not architectural profiles and carry no profile doctrine of their own,
27170
+ * so the hosted profile-application path recognizes a project kind as
27171
+ * resolvable-as-is (no contributing pack to adopt) instead of refusing it as
27172
+ * an unknown profile. */
27173
+ builtinProjectKinds: () => [...PROJECT_KINDS]
27130
27174
  };
27131
27175
  function resolveContainedProjectPath(projectRoot2, projectPath) {
27132
27176
  return assertContainedProjectPath(projectRoot2, projectPath);
@@ -27456,9 +27500,14 @@ function lockProject(cfg, credential, project2) {
27456
27500
  }
27457
27501
  return executeApprovedLock(cfg, project2);
27458
27502
  }
27459
- function executeApprovedLock(cfg, projectId) {
27503
+ function boundLifecycleRoot(cfg, projectId, subproject) {
27460
27504
  const root = existingProjectRoot(cfg.dataDir, projectId);
27461
27505
  if (!root) throw new Error(`Unknown project "${projectId}".`);
27506
+ if (!subproject) return root;
27507
+ return resolveSubprojectMounts(projectId, root, subproject.split(SUBPROJECT_SEPARATOR));
27508
+ }
27509
+ function executeApprovedLock(cfg, projectId, subproject) {
27510
+ const root = boundLifecycleRoot(cfg, projectId, subproject);
27462
27511
  return runWithProjectRoot(root, () => {
27463
27512
  hostGit.sync();
27464
27513
  const result = validateProjectAsComplete();
@@ -27630,9 +27679,8 @@ function promoteProject(cfg, credential, project2) {
27630
27679
  }
27631
27680
  return executeApprovedPromote(cfg, project2);
27632
27681
  }
27633
- function executeApprovedPromote(cfg, projectId) {
27634
- const root = existingProjectRoot(cfg.dataDir, projectId);
27635
- if (!root) throw new Error(`Unknown project "${projectId}".`);
27682
+ function executeApprovedPromote(cfg, projectId, subproject) {
27683
+ const root = boundLifecycleRoot(cfg, projectId, subproject);
27636
27684
  return runWithProjectRoot(root, () => {
27637
27685
  const lock = hostCore.readLockRecord();
27638
27686
  if (!lock) {
@@ -27747,6 +27795,35 @@ function storeListGlobalPacks() {
27747
27795
  );
27748
27796
  return [...instance, ...image];
27749
27797
  }
27798
+ function scanGlobalPackProfiles() {
27799
+ const out = [];
27800
+ for (const dir of [hostCore.globalPacksDir(), imagePacksDir()]) {
27801
+ for (const full of hostCore.discoverPacks(dir)) {
27802
+ try {
27803
+ const loaded = hostCore.loadExtensionPacks([{ ref: full, scope: "global" }], path45.dirname(full));
27804
+ if (loaded.errors.length) continue;
27805
+ const source = loaded.packNames[0] ?? path45.basename(full);
27806
+ for (const [id, def] of Object.entries(loaded.profiles)) out.push({ id, source, family: def.family });
27807
+ } catch {
27808
+ }
27809
+ }
27810
+ }
27811
+ return out;
27812
+ }
27813
+ function scanProjectPackProfiles() {
27814
+ const root = getProjectRoot();
27815
+ const out = [];
27816
+ for (const ref of loadProjectConfig().extensions?.packs ?? []) {
27817
+ try {
27818
+ const loaded = hostCore.loadExtensionPacks([{ ref, scope: "project" }], root);
27819
+ if (loaded.errors.length) continue;
27820
+ const source = loaded.packNames[0] ?? stem(ref);
27821
+ for (const [id, def] of Object.entries(loaded.profiles)) out.push({ id, source, family: def.family });
27822
+ } catch {
27823
+ }
27824
+ }
27825
+ return out;
27826
+ }
27750
27827
  function storeListAvailableProfiles() {
27751
27828
  const out = [];
27752
27829
  const seen = /* @__PURE__ */ new Set();
@@ -27757,19 +27834,21 @@ function storeListAvailableProfiles() {
27757
27834
  out.push(family ? { id, source, family } : { id, source });
27758
27835
  };
27759
27836
  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
- }
27837
+ for (const c of scanGlobalPackProfiles()) emit(c.id, c.source, c.family);
27838
+ return out;
27839
+ }
27840
+ function storeListProjectProfiles() {
27841
+ const out = [];
27842
+ const seen = /* @__PURE__ */ new Set();
27843
+ const emit = (id, source, installed, family) => {
27844
+ const key = JSON.stringify([id, source]);
27845
+ if (seen.has(key)) return;
27846
+ seen.add(key);
27847
+ out.push({ id, source, ...family ? { family } : {}, installed });
27770
27848
  };
27771
- scanTier(hostCore.globalPacksDir());
27772
- scanTier(imagePacksDir());
27849
+ for (const id of hostCore.builtinProfileIds()) emit(id, "builtin", true);
27850
+ for (const c of scanProjectPackProfiles()) emit(c.id, c.source, true, c.family);
27851
+ for (const c of scanGlobalPackProfiles()) emit(c.id, c.source, false, c.family);
27773
27852
  return out;
27774
27853
  }
27775
27854
  function readPackContent(full) {
@@ -27962,6 +28041,10 @@ function listAvailableProfiles(cfg, credential) {
27962
28041
  requirePrincipal2(cfg, credential);
27963
28042
  return storeListAvailableProfiles();
27964
28043
  }
28044
+ function listProjectProfiles(cfg, credential, project2) {
28045
+ requireCap(cfg, credential, "project:read", "project", project2, "Forbidden \u2014 listing a project's selectable profiles requires project:read over the project");
28046
+ return executeApprovedListProjectProfiles(cfg, project2);
28047
+ }
27965
28048
  function listAdoptableProjectPacks(cfg, credential, project2) {
27966
28049
  requireCap(cfg, credential, "project:read", "project", project2, "Forbidden \u2014 listing adoptable packs requires project:read over the project");
27967
28050
  return storeListGlobalPacks();
@@ -27984,6 +28067,26 @@ function executeApprovedInstallProjectPack(cfg, project2, name, content) {
27984
28067
  function executeApprovedResolveGlobalPacks(names) {
27985
28068
  return storeResolveGlobalPacks(names);
27986
28069
  }
28070
+ function executeApprovedListProjectProfiles(cfg, project2) {
28071
+ return runWithProjectRoot(boundProject2(cfg, project2), () => storeListProjectProfiles());
28072
+ }
28073
+ function executeApprovedEnsureProfileInstalled(cfg, project2, profileId) {
28074
+ if (hostCore.builtinProjectKinds().includes(profileId) || hostCore.builtinProfileIds().includes(profileId)) {
28075
+ return { profileId, source: "builtin" };
28076
+ }
28077
+ const contributors = executeApprovedListProjectProfiles(cfg, project2).filter((p) => p.id === profileId);
28078
+ const installed = contributors.find((p) => p.installed);
28079
+ if (installed) return { profileId, source: installed.source };
28080
+ const adoptable = contributors[0];
28081
+ const resolved = adoptable ? executeApprovedResolveGlobalPacks([adoptable.source]).resolved[0] : void 0;
28082
+ if (!resolved) {
28083
+ throw new Error(
28084
+ `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.`
28085
+ );
28086
+ }
28087
+ executeApprovedInstallProjectPack(cfg, project2, resolved.name, resolved.content);
28088
+ return { profileId, source: resolved.name, adoptedPackName: resolved.name };
28089
+ }
27987
28090
  function removeProjectPack(cfg, credential, project2, name) {
27988
28091
  requireCap(cfg, credential, "project:admin", "project", project2, "Forbidden \u2014 removing a project pack requires project:admin over the project");
27989
28092
  runWithProjectRoot(boundProject2(cfg, project2), () => storeRemoveProjectPack(name));
@@ -28978,6 +29081,40 @@ function requestPackNames(request) {
28978
29081
  if (!sel) return [];
28979
29082
  return [.../* @__PURE__ */ new Set([...sel.requiredPackNames ?? [], ...sel.defaultPackNames ?? []])];
28980
29083
  }
29084
+ function classifyProfile(projectType, catalog) {
29085
+ if (hostCore.builtinProfileIds().includes(projectType) || hostCore.builtinProjectKinds().includes(projectType)) {
29086
+ return { source: "builtin", resolvable: true };
29087
+ }
29088
+ const installed = catalog.find((p) => p.id === projectType && p.installed);
29089
+ if (installed) return { source: installed.source, resolvable: true };
29090
+ return { resolvable: false };
29091
+ }
29092
+ function firstApplicableProfileId(candidates, catalog) {
29093
+ const kinds = hostCore.builtinProjectKinds();
29094
+ const catalogIds = new Set(catalog.map((p) => p.id));
29095
+ return candidates.find((id) => kinds.includes(id) || catalogIds.has(id));
29096
+ }
29097
+ function unappliedIds(selectedProfileIds, governingProfileId) {
29098
+ return selectedProfileIds.filter((id) => id !== governingProfileId);
29099
+ }
29100
+ function foldAppliedProfile(root, profileId, actor) {
29101
+ const recorded = readProjectProfileSelection(root);
29102
+ const folded = {
29103
+ profileIds: [profileId, ...unappliedIds(recorded?.profileIds ?? [], profileId)],
29104
+ requiredPackNames: recorded?.requiredPackNames ?? [],
29105
+ selectedBy: actor,
29106
+ selectedAt: (/* @__PURE__ */ new Date()).toISOString()
29107
+ };
29108
+ if (recorded?.defaultPackNames) folded.defaultPackNames = recorded.defaultPackNames;
29109
+ recordProjectProfileSelection(root, folded);
29110
+ return folded;
29111
+ }
29112
+ function overridingSubsystemIds(root) {
29113
+ return runWithProjectRoot(
29114
+ root,
29115
+ () => hostCore.loadSubsystemSpecs().filter((s) => !!s.profile).map((s) => s.id)
29116
+ );
29117
+ }
28981
29118
  function resolvedSelection(request, policy, selectedBy) {
28982
29119
  const sel = request.profileSelection;
28983
29120
  const selection = {
@@ -28996,7 +29133,8 @@ function buildEvaluation(input) {
28996
29133
  selectedProfileIds,
28997
29134
  hasSelection,
28998
29135
  countMissingPacksAsViolation,
28999
- requiredDefaultResolution
29136
+ requiredDefaultResolution,
29137
+ governingProfileId
29000
29138
  } = input;
29001
29139
  const present = new Set(presentPackNames);
29002
29140
  let missingPackNames;
@@ -29017,6 +29155,7 @@ function buildEvaluation(input) {
29017
29155
  const allowed = policy.allowedProfileIds;
29018
29156
  const disallowedProfileIds = allowed && allowed.length > 0 ? selectedProfileIds.filter((id) => !allowed.includes(id)) : [];
29019
29157
  const selectionRequiredUnmet = policy.requireProfileSelection && !hasSelection;
29158
+ const unappliedProfileIds = governingProfileId ? unappliedIds(selectedProfileIds, governingProfileId) : [];
29020
29159
  const messages = [];
29021
29160
  if (selectionRequiredUnmet) {
29022
29161
  messages.push("Profile selection is required by policy but none was provided.");
@@ -29028,16 +29167,24 @@ function buildEvaluation(input) {
29028
29167
  for (const n of blockedPackNames) messages.push(`Pack "${n}" is blocked by policy.`);
29029
29168
  for (const id of missingProfileIds) messages.push(`Required profile "${id}" is not selected.`);
29030
29169
  for (const id of disallowedProfileIds) messages.push(`Profile "${id}" is not permitted by policy.`);
29170
+ for (const id of unappliedProfileIds) {
29171
+ messages.push(
29172
+ `Selected profile "${id}" is recorded but does not govern the project (projectType is "${governingProfileId}") \u2014 a project has exactly one governing profile.`
29173
+ );
29174
+ }
29031
29175
  const violation = selectionRequiredUnmet || blockedPackNames.length > 0 || missingProfileIds.length > 0 || disallowedProfileIds.length > 0 || countMissingPacksAsViolation && (missingPackNames.length > 0 || unresolvedPacks.length > 0);
29032
- return {
29176
+ const result = {
29033
29177
  compliant: !violation,
29034
29178
  mode: policy.enforcementMode,
29035
29179
  missingPackNames,
29036
29180
  blockedPackNames,
29037
29181
  missingProfileIds,
29038
29182
  unresolvedPacks,
29183
+ unappliedProfileIds,
29039
29184
  messages
29040
29185
  };
29186
+ if (governingProfileId) result.governingProfileId = governingProfileId;
29187
+ return result;
29041
29188
  }
29042
29189
  function performInit(cfg, request, principal) {
29043
29190
  const policy = effectivePolicy(cfg.dataDir);
@@ -29057,17 +29204,24 @@ function performInit(cfg, request, principal) {
29057
29204
  request.ownerUnitId,
29058
29205
  principal ? principalSubject3(principal) : void 0
29059
29206
  );
29060
- installResolvedPacks(
29061
- cfg,
29062
- record2.id,
29063
- executeApprovedResolveGlobalPacks([
29064
- ...policy.requiredGlobalPacks,
29065
- ...policy.defaultProjectPacks,
29066
- ...requestPackNames(request)
29067
- ])
29068
- );
29207
+ const packResolution = executeApprovedResolveGlobalPacks([
29208
+ ...policy.requiredGlobalPacks,
29209
+ ...policy.defaultProjectPacks,
29210
+ ...requestPackNames(request)
29211
+ ]);
29212
+ installResolvedPacks(cfg, record2.id, packResolution);
29069
29213
  const selectedBy = principal ? principalSubject3(principal) : void 0;
29070
- recordProjectProfileSelection(record2.rootPath, resolvedSelection(request, policy, selectedBy));
29214
+ const selection = resolvedSelection(request, policy, selectedBy);
29215
+ recordProjectProfileSelection(record2.rootPath, selection);
29216
+ const catalog = executeApprovedListProjectProfiles(cfg, record2.id);
29217
+ const appliedProfileId = firstApplicableProfileId(selection.profileIds, catalog);
29218
+ let appliedSource;
29219
+ if (appliedProfileId) {
29220
+ const application = executeApprovedEnsureProfileInstalled(cfg, record2.id, appliedProfileId);
29221
+ writeProjectType(record2.rootPath, application.profileId);
29222
+ appliedSource = application.source;
29223
+ }
29224
+ const unapplied = appliedProfileId ? unappliedIds(selection.profileIds, appliedProfileId) : [...selection.profileIds];
29071
29225
  const actor = principal ? principalSubject3(principal) : SYSTEM_SUBJECT;
29072
29226
  tryAppendAudit2(
29073
29227
  cfg,
@@ -29076,7 +29230,18 @@ function performInit(cfg, request, principal) {
29076
29230
  "project.init.policy",
29077
29231
  "info",
29078
29232
  "project",
29079
- { target: record2.id, projectId: record2.id },
29233
+ {
29234
+ target: record2.id,
29235
+ projectId: record2.id,
29236
+ metadata: JSON.stringify({
29237
+ ...appliedProfileId ? { appliedProfileId, profileSource: appliedSource } : {
29238
+ appliedProfileId: null,
29239
+ 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"
29240
+ },
29241
+ ...unapplied.length > 0 ? { unappliedProfileIds: unapplied } : {},
29242
+ ...packResolution.unresolved.length > 0 ? { unresolvedPacks: packResolution.unresolved } : {}
29243
+ })
29244
+ },
29080
29245
  principal?.tokenId
29081
29246
  )
29082
29247
  );
@@ -29117,6 +29282,7 @@ function evaluateProjectPolicy(cfg, credential, projectId) {
29117
29282
  const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
29118
29283
  if (!root) throw new Error(`Unknown project "${projectId}".`);
29119
29284
  const policy = effectivePolicy(cfg.dataDir);
29285
+ const governingProfileId = readProjectType(root);
29120
29286
  const selection = readProjectProfileSelection(root);
29121
29287
  return buildEvaluation({
29122
29288
  policy,
@@ -29124,7 +29290,8 @@ function evaluateProjectPolicy(cfg, credential, projectId) {
29124
29290
  selectedProfileIds: selection?.profileIds ?? [],
29125
29291
  hasSelection: !!selection,
29126
29292
  countMissingPacksAsViolation: true,
29127
- requiredDefaultResolution: executeApprovedResolveGlobalPacks(requiredDefaultNames(policy))
29293
+ requiredDefaultResolution: executeApprovedResolveGlobalPacks(requiredDefaultNames(policy)),
29294
+ governingProfileId
29128
29295
  });
29129
29296
  }
29130
29297
  function reconcileProjectPolicy(cfg, credential, projectId) {
@@ -29139,20 +29306,41 @@ function reconcileProjectPolicy(cfg, credential, projectId) {
29139
29306
  const policy = effectivePolicy(cfg.dataDir);
29140
29307
  const selection = readProjectProfileSelection(root);
29141
29308
  const resolution = executeApprovedResolveGlobalPacks(requiredDefaultNames(policy));
29142
- let installed = installedPackNames(cfg, projectId);
29143
- const installedSet = new Set(installed);
29309
+ const installedSet = new Set(installedPackNames(cfg, projectId));
29144
29310
  const toApply = resolution.resolved.filter((p) => !installedSet.has(p.name));
29311
+ const appliedPackNames = toApply.map((p) => p.name);
29145
29312
  if (toApply.length > 0) {
29146
29313
  installResolvedPacks(cfg, projectId, { resolved: toApply, unresolved: [] });
29147
- installed = installedPackNames(cfg, projectId);
29314
+ }
29315
+ const catalog = executeApprovedListProjectProfiles(cfg, projectId);
29316
+ const previousProfileId = readProjectType(root);
29317
+ let governingProfileId = previousProfileId;
29318
+ const requiredProfileIds = policy.requiredProfileIds ?? [];
29319
+ const policyUnsatisfied = requiredProfileIds.length > 0 && !requiredProfileIds.includes(governingProfileId);
29320
+ const governingUnresolvable = !classifyProfile(governingProfileId, catalog).resolvable;
29321
+ let repairedProfileId;
29322
+ let selectedProfileIds = selection?.profileIds ?? [];
29323
+ if (policyUnsatisfied || governingUnresolvable) {
29324
+ const target = firstApplicableProfileId(
29325
+ [...requiredProfileIds, ...selection?.profileIds ?? []],
29326
+ catalog
29327
+ );
29328
+ if (target) {
29329
+ const application = executeApprovedEnsureProfileInstalled(cfg, projectId, target);
29330
+ writeProjectType(root, application.profileId);
29331
+ governingProfileId = application.profileId;
29332
+ repairedProfileId = application.profileId;
29333
+ selectedProfileIds = foldAppliedProfile(root, application.profileId, principalSubject3(principal)).profileIds;
29334
+ }
29148
29335
  }
29149
29336
  const result = buildEvaluation({
29150
29337
  policy,
29151
- presentPackNames: installed,
29152
- selectedProfileIds: selection?.profileIds ?? [],
29338
+ presentPackNames: installedPackNames(cfg, projectId),
29339
+ selectedProfileIds,
29153
29340
  hasSelection: !!selection,
29154
29341
  countMissingPacksAsViolation: true,
29155
- requiredDefaultResolution: resolution
29342
+ requiredDefaultResolution: resolution,
29343
+ governingProfileId
29156
29344
  });
29157
29345
  tryAppendAudit2(
29158
29346
  cfg,
@@ -29161,7 +29349,15 @@ function reconcileProjectPolicy(cfg, credential, projectId) {
29161
29349
  "policy.reconcile",
29162
29350
  "info",
29163
29351
  "policy",
29164
- { target: projectId, projectId },
29352
+ {
29353
+ target: projectId,
29354
+ projectId,
29355
+ metadata: JSON.stringify({
29356
+ ...appliedPackNames.length > 0 ? { appliedPackNames } : {},
29357
+ ...repairedProfileId ? { repairedProfileId, previousProfileId } : {},
29358
+ ...resolution.unresolved.length > 0 ? { unresolvedPacks: resolution.unresolved } : {}
29359
+ })
29360
+ },
29165
29361
  principal.tokenId
29166
29362
  )
29167
29363
  );
@@ -29177,8 +29373,19 @@ function getProjectConfig(cfg, credential, projectId) {
29177
29373
  const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
29178
29374
  if (!root) throw new Error(`Unknown project "${projectId}".`);
29179
29375
  const projectType = readProjectType(root);
29376
+ const selection = readProjectProfileSelection(root);
29377
+ const classified = classifyProfile(projectType, executeApprovedListProjectProfiles(cfg, projectId));
29180
29378
  const locked = runWithProjectRoot(root, () => hostCore.readLockRecord() !== null);
29181
- return { projectType, locked };
29379
+ const overriding = overridingSubsystemIds(root);
29380
+ const view = {
29381
+ projectType,
29382
+ locked,
29383
+ profileResolvable: classified.resolvable,
29384
+ unappliedProfileIds: unappliedIds(selection?.profileIds ?? [], projectType),
29385
+ overridingSubsystemIds: overriding
29386
+ };
29387
+ if (classified.source) view.profileSource = classified.source;
29388
+ return view;
29182
29389
  }
29183
29390
  function setProjectType(cfg, credential, projectId, projectType) {
29184
29391
  const principal = requirePrincipal4(cfg, credential);
@@ -29189,9 +29396,23 @@ function setProjectType(cfg, credential, projectId, projectType) {
29189
29396
  }
29190
29397
  const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
29191
29398
  if (!root) throw new Error(`Unknown project "${projectId}".`);
29192
- writeProjectType(root, projectType);
29399
+ const application = executeApprovedEnsureProfileInstalled(cfg, projectId, projectType);
29400
+ writeProjectType(root, application.profileId);
29401
+ const folded = foldAppliedProfile(root, application.profileId, principalSubject3(principal));
29402
+ const remainder = folded.profileIds.slice(1);
29193
29403
  const locked = runWithProjectRoot(root, () => hostCore.readLockRecord() !== null);
29194
- return { projectType, locked };
29404
+ const overriding = overridingSubsystemIds(root);
29405
+ const view = {
29406
+ projectType: application.profileId,
29407
+ locked,
29408
+ profileSource: application.source,
29409
+ // The write path guarantees resolvability — the ensure seam refused anything else.
29410
+ profileResolvable: true,
29411
+ unappliedProfileIds: remainder,
29412
+ overridingSubsystemIds: overriding
29413
+ };
29414
+ if (application.adoptedPackName) view.adoptedPackName = application.adoptedPackName;
29415
+ return view;
29195
29416
  }
29196
29417
  function getPackPolicy(cfg, credential) {
29197
29418
  requirePrincipal4(cfg, credential);
@@ -31344,38 +31565,58 @@ function initializeProject(cfg, credential, request) {
31344
31565
  }
31345
31566
  }
31346
31567
  }
31347
- function lockProject2(cfg, credential, projectId) {
31568
+ function lockProject2(cfg, credential, projectId, subproject) {
31348
31569
  return lifecycleAction(cfg, credential, projectId, {
31349
31570
  action: "project:lock",
31350
31571
  verb: "Lock",
31351
31572
  noun: "lock",
31573
+ subproject,
31352
31574
  execute: () => {
31353
- const lock = executeApprovedLock(cfg, projectId);
31575
+ const lock = executeApprovedLock(cfg, projectId, subproject);
31354
31576
  return {
31355
31577
  status: "completed",
31356
31578
  action: "project:lock",
31357
- summary: `Locked project "${projectId}" (status: ${lock.status}).`,
31579
+ summary: `Locked project "${projectId}"${subprojectSuffix(subproject)} (status: ${lock.status}).`,
31358
31580
  lock
31359
31581
  };
31360
31582
  }
31361
31583
  });
31362
31584
  }
31363
- function promoteProject2(cfg, credential, projectId) {
31585
+ function promoteProject2(cfg, credential, projectId, subproject) {
31364
31586
  return lifecycleAction(cfg, credential, projectId, {
31365
31587
  action: "project:promote",
31366
31588
  verb: "Promote",
31367
31589
  noun: "promotion",
31590
+ subproject,
31368
31591
  execute: () => {
31369
- const promo = executeApprovedPromote(cfg, projectId);
31592
+ const promo = executeApprovedPromote(cfg, projectId, subproject);
31370
31593
  return {
31371
31594
  status: "completed",
31372
31595
  action: "project:promote",
31373
- summary: `Promotion of project "${projectId}": ${promo.status} \u2014 ${promo.message}`,
31596
+ summary: `Promotion of project "${projectId}"${subprojectSuffix(subproject)}: ${promo.status} \u2014 ${promo.message}`,
31374
31597
  promote: promo
31375
31598
  };
31376
31599
  }
31377
31600
  });
31378
31601
  }
31602
+ function subprojectSuffix(subproject) {
31603
+ return subproject ? ` subproject "${subproject}"` : "";
31604
+ }
31605
+ var SUBPROJECT_SCOPE_PAYLOAD = "SubprojectScope";
31606
+ function subprojectScopePayload(subproject) {
31607
+ if (!subproject) return {};
31608
+ return { payloadType: SUBPROJECT_SCOPE_PAYLOAD, payload: JSON.stringify({ subproject }) };
31609
+ }
31610
+ function readSubprojectScope(req) {
31611
+ if (req.payloadType !== SUBPROJECT_SCOPE_PAYLOAD || !req.payload) return void 0;
31612
+ const parsed = JSON.parse(req.payload);
31613
+ if (typeof parsed.subproject !== "string" || !parsed.subproject) {
31614
+ throw new Error(
31615
+ `Approved ${req.kind} request "${req.id}" carries a ${SUBPROJECT_SCOPE_PAYLOAD} payload with no usable subproject qualifier \u2014 refusing to execute, because falling back to the whole project would widen the scope the requester was confined to.`
31616
+ );
31617
+ }
31618
+ return parsed.subproject;
31619
+ }
31379
31620
  function lifecycleAction(cfg, credential, projectId, opts) {
31380
31621
  const principal = requirePrincipal7(cfg, credential);
31381
31622
  const effective = authorize(cfg.dataDir, principal, PROJECT_WRITE_CAPABILITY4, "project", projectId);
@@ -31401,8 +31642,9 @@ function lifecycleAction(cfg, credential, projectId, opts) {
31401
31642
  const pending = buildPendingRequest(
31402
31643
  principal,
31403
31644
  opts.action,
31404
- `${opts.verb} project ${projectId}`,
31405
- projectId
31645
+ `${opts.verb} project ${projectId}${subprojectSuffix(opts.subproject)}`,
31646
+ projectId,
31647
+ subprojectScopePayload(opts.subproject)
31406
31648
  );
31407
31649
  return createPendingOutcome(cfg, principal, pending, opts.action);
31408
31650
  }
@@ -31476,12 +31718,14 @@ function executeApproved(cfg, req) {
31476
31718
  return `Initialized project "${rec.id}" under the active pack policy.`;
31477
31719
  }
31478
31720
  case "project:lock": {
31479
- const lock = executeApprovedLock(cfg, req.projectId ?? "");
31480
- return `Locked project "${req.projectId}" (status: ${lock.status}).`;
31721
+ const scope = readSubprojectScope(req);
31722
+ const lock = executeApprovedLock(cfg, req.projectId ?? "", scope);
31723
+ return `Locked project "${req.projectId}"${subprojectSuffix(scope)} (status: ${lock.status}).`;
31481
31724
  }
31482
31725
  case "project:promote": {
31483
- const promo = executeApprovedPromote(cfg, req.projectId ?? "");
31484
- return `Promotion of project "${req.projectId}": ${promo.status} \u2014 ${promo.message}`;
31726
+ const scope = readSubprojectScope(req);
31727
+ const promo = executeApprovedPromote(cfg, req.projectId ?? "", scope);
31728
+ return `Promotion of project "${req.projectId}"${subprojectSuffix(scope)}: ${promo.status} \u2014 ${promo.message}`;
31485
31729
  }
31486
31730
  default:
31487
31731
  throw new Error(`Unsupported approval kind "${req.kind}".`);
@@ -32185,6 +32429,9 @@ function getProjectConfig2(cfg, credential, projectId) {
32185
32429
  function setProjectType2(cfg, credential, projectId, projectType) {
32186
32430
  return setProjectType(cfg, credential, projectId, projectType);
32187
32431
  }
32432
+ function listProjectProfiles2(cfg, credential, project2) {
32433
+ return listProjectProfiles(cfg, credential, project2);
32434
+ }
32188
32435
  function listProducers2(cfg, credential, project2) {
32189
32436
  return listProducers(cfg, credential, project2);
32190
32437
  }
@@ -35546,6 +35793,9 @@ function opsGetProjectConfig(cfg, sessionId, url, res) {
35546
35793
  function opsSetProjectConfig(cfg, sessionId, body, res) {
35547
35794
  sendJson(res, 200, setProjectType2(cfg, sessionId, String(body?.projectId ?? ""), String(body?.projectType ?? "")));
35548
35795
  }
35796
+ function opsListProjectProfiles(cfg, sessionId, url, res) {
35797
+ sendJson(res, 200, { profiles: listProjectProfiles2(cfg, sessionId, q(url, "projectId") ?? "") });
35798
+ }
35549
35799
  function opsListProducers(cfg, sessionId, url, res) {
35550
35800
  sendJson(res, 200, { producers: listProducers2(cfg, sessionId, q(url, "projectId") ?? "") });
35551
35801
  }
@@ -35785,6 +36035,9 @@ async function handleWebRequest(cfg, req, res, body, url, ctx) {
35785
36035
  if (req.method === "POST" && parts.length === 3 && parts[2] === "config") {
35786
36036
  return opsSetProjectConfig(cfg, sessionId, body, res);
35787
36037
  }
36038
+ if (req.method === "GET" && parts.length === 3 && parts[2] === "profiles") {
36039
+ return opsListProjectProfiles(cfg, sessionId, url, res);
36040
+ }
35788
36041
  if (req.method === "GET" && parts.length === 3 && parts[2] === "policy") {
35789
36042
  return opsPolicyEvaluate(cfg, sessionId, url, res);
35790
36043
  }
@@ -36205,6 +36458,12 @@ var PROJECT_OPS_TOOLS = /* @__PURE__ */ new Set([
36205
36458
  "sdd_host_produce",
36206
36459
  "sdd_host_commit_project"
36207
36460
  ]);
36461
+ var PROJECT_RECORD_TOOLS = /* @__PURE__ */ new Set([
36462
+ "sdd_host_initialize_project",
36463
+ "sdd_host_get_approval_status",
36464
+ "sdd_host_await_approval",
36465
+ ...PROJECT_OPS_TOOLS
36466
+ ]);
36208
36467
  function jsonRpcRequests(body) {
36209
36468
  const arr = Array.isArray(body) ? body : [body];
36210
36469
  return arr.filter((m) => !!m && typeof m === "object" && "method" in m);
@@ -36270,7 +36529,27 @@ function dataPlanePermissionError(cfg, principal, projectId, body) {
36270
36529
  }
36271
36530
  };
36272
36531
  }
36273
- async function dispatchProjectLifecycleTool(cfg, credential, projectId, body) {
36532
+ function subprojectConfinementError(projectId, subproject, body) {
36533
+ if (!subproject) return void 0;
36534
+ const msg = jsonRpcRequest(body);
36535
+ if (!msg || msg.method !== "tools/call") return void 0;
36536
+ const name = msg.params?.name;
36537
+ if (typeof name !== "string" || !PROJECT_RECORD_TOOLS.has(name)) return void 0;
36538
+ return {
36539
+ jsonrpc: "2.0",
36540
+ id: msg.id ?? null,
36541
+ result: {
36542
+ content: [
36543
+ {
36544
+ type: "text",
36545
+ text: `Refused \u2014 ${name} acts on the whole project "${projectId}", but this credential is bound to subproject "${projectId}${SUBPROJECT_SEPARATOR}${subproject}". An unqualified credential for "${projectId}" is required to call ${name}.`
36546
+ }
36547
+ ],
36548
+ isError: true
36549
+ }
36550
+ };
36551
+ }
36552
+ async function dispatchProjectLifecycleTool(cfg, credential, projectId, body, subproject) {
36274
36553
  const msg = jsonRpcRequest(body);
36275
36554
  if (!msg || msg.method !== "tools/call") return void 0;
36276
36555
  const name = msg.params?.name;
@@ -36287,10 +36566,10 @@ async function dispatchProjectLifecycleTool(cfg, credential, projectId, body) {
36287
36566
  value = initializeProject(cfg, credential, args);
36288
36567
  break;
36289
36568
  case "sdd_host_lock_project":
36290
- value = lockProject2(cfg, credential, projectId);
36569
+ value = lockProject2(cfg, credential, projectId, subproject);
36291
36570
  break;
36292
36571
  case "sdd_host_promote_project":
36293
- value = promoteProject2(cfg, credential, projectId);
36572
+ value = promoteProject2(cfg, credential, projectId, subproject);
36294
36573
  break;
36295
36574
  case "sdd_host_await_approval":
36296
36575
  value = await awaitApproval(
@@ -36397,7 +36676,13 @@ async function handleMcpRequest(cfg, req, res, body, credential) {
36397
36676
  await runWithProjectRoot(binding.rootPath, async () => {
36398
36677
  const projectId = binding.projectId;
36399
36678
  const subproject = binding.subproject;
36400
- const dispatchedResponse = await dispatchProjectLifecycleTool(cfg, cred, projectId, body);
36679
+ const confinementError = subprojectConfinementError(projectId, subproject, body);
36680
+ if (confinementError !== void 0) {
36681
+ sendJson(res, 200, confinementError);
36682
+ auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(confinementError), subproject);
36683
+ return;
36684
+ }
36685
+ const dispatchedResponse = await dispatchProjectLifecycleTool(cfg, cred, projectId, body, subproject);
36401
36686
  if (dispatchedResponse !== void 0) {
36402
36687
  sendJson(res, 200, dispatchedResponse);
36403
36688
  auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(dispatchedResponse), subproject);