/** * cli:derive-permission-floor — block.ts * * The `ba:rbac-floor` machine-owned block of rbac.md: deterministic render + * idempotent whole-block splice. Coexists with the `ba:rbac-derived-lookups` * block (each splice targets its OWN markers only); its rows carry no * `BA-…` actor code, so the human-row parser (rbac-rows.ts ROW_RE) never * picks them up. */ import type { FloorNodeRow } from './types.js' export const FLOOR_BLOCK_BEGIN = '' export const FLOOR_BLOCK_END = '' /** One rendered table row — also the drift-comparison unit for mode=check. */ export function renderFloorRowLine(row: FloorNodeRow): string { const actions = row.actions.map((a) => `\`${a}\``).join(' ') return `| \`${row.nodePath}\` | ${row.grain} | ${actions} |` } /** * Render the full machine-owned block. Deterministic — rows are expected * already stably ordered by the caller (derive.ts: app → module → sections → * their resources). */ export function renderFloorBlock(rows: FloorNodeRow[], floorPaths: number): string { const lines: string[] = [ FLOOR_BLOCK_BEGIN, '### Socle de permissions — bloc machine (ne pas éditer à la main)', '', "> Généré par `derive-permission-floor` depuis l'arbre du menu. Ces lignes", '> sont seedées AUTOMATIQUEMENT par `scaffold-core-seed` (dérivées de la', '> même navigation — le socle ne peut pas être oublié), **sans grant** :', "> elles existent pour être attribuées dans la matrice d'administration", '> (seul le `.access` est grandfathered v3.62 : un rôle le reçoit ssi il', '> détient déjà une permission sous le nœud). La matrice humaine ci-dessus', '> porte les GRANTS (qui a quoi) et les actions SUPPLÉMENTAIRES (approve,', '> export, `.read.all`, …). Chemins **app-qualifiés** — 2e exception', '> assumée à la prohibition n°10. Relancer le CLI après toute modification', '> du menu.', '', '| Nœud | Grain | Permissions du socle |', '|------|-------|----------------------|', ...rows.map(renderFloorRowLine), '', `_${floorPaths} lignes de socle sur ${rows.length} nœuds — seedées sans grant. La ligne` + ' `application` est de portée PROJET (répétée dans le bloc de chaque module)._', FLOOR_BLOCK_END, ] return lines.join('\n') } /** * Replace the whole-line span between the markers with `block`. Returns the * new source, `null` when the block is already byte-identical, or appends the * block at EOF when no markers exist yet. */ export function spliceFloorBlock(source: string, block: string): string | null { const beginIdx = source.indexOf(FLOOR_BLOCK_BEGIN) const endIdx = source.indexOf(FLOOR_BLOCK_END) if (beginIdx >= 0 && endIdx > beginIdx) { const lineStart = source.lastIndexOf('\n', beginIdx) + 1 const lineEnd = source.indexOf('\n', endIdx) const before = source.slice(0, lineStart) const after = lineEnd >= 0 ? source.slice(lineEnd + 1) : '' const next = before + block + '\n' + after return next === source ? null : next } const sep = source.endsWith('\n\n') ? '' : source.endsWith('\n') ? '\n' : '\n\n' return source + sep + block + '\n' } /** The raw text between the markers (markers included), or null when absent. */ export function extractFloorBlock(source: string): string | null { const beginIdx = source.indexOf(FLOOR_BLOCK_BEGIN) const endIdx = source.indexOf(FLOOR_BLOCK_END) if (beginIdx < 0 || endIdx <= beginIdx) return null return source.slice(beginIdx, endIdx + FLOOR_BLOCK_END.length) } /** Source with the floor block removed. */ export function stripFloorBlock(source: string): string { const beginIdx = source.indexOf(FLOOR_BLOCK_BEGIN) const endIdx = source.indexOf(FLOOR_BLOCK_END) if (beginIdx < 0 || endIdx <= beginIdx) return source const lineStart = source.lastIndexOf('\n', beginIdx) + 1 const lineEnd = source.indexOf('\n', endIdx) return source.slice(0, lineStart) + (lineEnd >= 0 ? source.slice(lineEnd + 1) : '') } /** * DATA row lines inside a rendered/extracted block — a backticked node path * followed by a grain keyword cell (excludes the header + the footer note). */ export function extractFloorRowLines(block: string): string[] { return block .split('\n') .map((l) => l.trim()) .filter((l) => /^\|\s*`[^`]+`\s*\|\s*(application|module|section|resource)\s*\|/.test(l)) }