// Persisted Clay-level access rules for parent projects and exact worktrees. // Filesystem and Git permissions are deliberately outside this module. var worktreePreferences = require("./worktree-preferences"); function parentSlugFor(slug) { if (typeof slug !== "string") return null; var marker = slug.indexOf("--"); return marker > 0 ? slug.substring(0, marker) : null; } function findProject(config, slug) { var projects = config && Array.isArray(config.projects) ? config.projects : []; for (var i = 0; i < projects.length; i++) { if (projects[i].slug === slug) return projects[i]; } return null; } function uniqueUsers(values) { var source = Array.isArray(values) ? values : []; var seen = {}; var out = []; for (var i = 0; i < source.length; i++) { if (typeof source[i] !== "string" || !source[i] || seen[source[i]]) continue; seen[source[i]] = true; out.push(source[i]); } return out; } function pendingLifecycleOperation(config, project) { return Object.values(config.projectLifecycleOperations || {}).find(function (operation) { return operation.phase === 'pending' && operation.slug === project.slug && operation.projectKnowledgeId === project.projectKnowledgeId && operation.expected === JSON.stringify(project); }); } function getProjectAccess(config, slug, osUsers, worktreeIdentity) { var parentSlug = parentSlugFor(slug); var project = findProject(config, parentSlug || slug); if (!project) return { error: "Project not found" }; var isMateProject = project.isMate === true || project.slug.indexOf("mate-") === 0; if (isMateProject) { return { slug: slug, isMate: true, visibility: "private", ownerId: project.ownerId || null, allowedUsers: [], sessionVisibilityDefault: "private", isWorktree: !!parentSlug }; } var visibility = isMateProject ? "private" : (project.visibility || (osUsers ? "private" : "public")); var parentAllowedUsers = uniqueUsers(project.allowedUsers); if (!parentSlug) { return { slug: slug, lifecycleExecutionPaused: project.lifecycleExecutionPaused === true || !!pendingLifecycleOperation(config, project), ownershipRecovery: project.ownershipRecovery || null, visibility: visibility, allowedUsers: parentAllowedUsers, ownerId: project.ownerId || null, sessionVisibilityDefault: project.sessionVisibilityDefault === "shared" ? "shared" : "private", isWorktree: false, }; } var grants = project.worktreeAllowedUsers && project.worktreeAllowedUsers[slug]; var worktreeAllowedUsers = uniqueUsers(grants); var preference = worktreePreferences.entry(config, parentSlug, slug); var identityKnown = worktreeIdentity && worktreeIdentity.slug === slug && typeof worktreeIdentity.path === "string" && worktreeIdentity.path && typeof worktreeIdentity.changeSetId === "string" && worktreeIdentity.changeSetId; var boundPreference = identityKnown ? worktreePreferences.blocked(config, parentSlug, slug, worktreeIdentity.path, worktreeIdentity.changeSetId) : preference && preference.lifecycleState; return { slug: slug, lifecycleExecutionPaused: project.lifecycleExecutionPaused === true || !!pendingLifecycleOperation(config, project) || !!boundPreference, ownershipRecovery: project.ownershipRecovery || null, visibility: visibility, allowedUsers: uniqueUsers(parentAllowedUsers.concat(worktreeAllowedUsers)), ownerId: project.ownerId || null, sessionVisibilityDefault: project.sessionVisibilityDefault === "shared" ? "shared" : "private", isWorktree: true, parentSlug: parentSlug, parentAllowedUsers: parentAllowedUsers, worktreeAllowedUsers: worktreeAllowedUsers, parentAccess: { slug: parentSlug, visibility: visibility, allowedUsers: parentAllowedUsers, ownerId: project.ownerId || null, sessionVisibilityDefault: project.sessionVisibilityDefault === "shared" ? "shared" : "private", }, }; } function setAllowedUsers(config, slug, allowedUsers) { var parentSlug = parentSlugFor(slug); var project = findProject(config, parentSlug || slug); if (!project) return { error: "Project not found" }; if (project.slug.indexOf("mate-") === 0 || project.isMate) return { error: "Clay and Mates cannot be shared." }; var clean = uniqueUsers(allowedUsers); if (!parentSlug) { project.allowedUsers = clean; return { ok: true, isWorktree: false, allowedUsers: clean }; } if (!project.worktreeAllowedUsers || typeof project.worktreeAllowedUsers !== "object" || Array.isArray(project.worktreeAllowedUsers)) { project.worktreeAllowedUsers = {}; } if (clean.length > 0) project.worktreeAllowedUsers[slug] = clean; else delete project.worktreeAllowedUsers[slug]; if (Object.keys(project.worktreeAllowedUsers).length === 0) delete project.worktreeAllowedUsers; return { ok: true, isWorktree: true, allowedUsers: clean }; } function setSessionVisibilityDefault(config, slug, visibility) { if (visibility !== "private" && visibility !== "shared") return { error: "Invalid session visibility default" }; if (parentSlugFor(slug)) return { error: "Worktrees inherit this setting from their parent project." }; var project = findProject(config, slug); if (!project) return { error: "Project not found" }; if (project.slug.indexOf("mate-") === 0 || project.isMate) return { error: "Clay and Mates are always private." }; if (visibility === "shared") project.sessionVisibilityDefault = "shared"; else delete project.sessionVisibilityDefault; return { ok: true, visibility: visibility }; } function clearWorktreeGrant(config, slug) { var parentSlug = parentSlugFor(slug); var project = parentSlug ? findProject(config, parentSlug) : null; if (!project || !project.worktreeAllowedUsers || !Object.prototype.hasOwnProperty.call(project.worktreeAllowedUsers, slug)) return false; delete project.worktreeAllowedUsers[slug]; if (Object.keys(project.worktreeAllowedUsers).length === 0) delete project.worktreeAllowedUsers; return true; } function reconcileWorktreeGrants(config, parentSlug, existingSlugs) { var project = findProject(config, parentSlug); if (!project || !project.worktreeAllowedUsers) return false; var existing = {}; for (var i = 0; i < existingSlugs.length; i++) existing[existingSlugs[i]] = true; var changed = false; var granted = Object.keys(project.worktreeAllowedUsers); for (var j = 0; j < granted.length; j++) { if (existing[granted[j]]) continue; delete project.worktreeAllowedUsers[granted[j]]; changed = true; } if (Object.keys(project.worktreeAllowedUsers).length === 0) delete project.worktreeAllowedUsers; return changed; } module.exports = { pendingLifecycleOperation: pendingLifecycleOperation, parentSlugFor: parentSlugFor, getProjectAccess: getProjectAccess, setAllowedUsers: setAllowedUsers, setSessionVisibilityDefault: setSessionVisibilityDefault, clearWorktreeGrant: clearWorktreeGrant, reconcileWorktreeGrants: reconcileWorktreeGrants, };