@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 +351 -66
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +51 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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) && !
|
|
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
|
|
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 =
|
|
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
|
|
27761
|
-
|
|
27762
|
-
|
|
27763
|
-
|
|
27764
|
-
|
|
27765
|
-
|
|
27766
|
-
|
|
27767
|
-
|
|
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
|
-
|
|
27772
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29061
|
-
|
|
29062
|
-
|
|
29063
|
-
|
|
29064
|
-
|
|
29065
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
29152
|
-
selectedProfileIds
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
31480
|
-
|
|
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
|
|
31484
|
-
|
|
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
|
-
|
|
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
|
|
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);
|