feat(hierarchy): M4-1b-ii hierarchy command family, grant evaluation, visibility (#1465)
ci/woodpecker/push/publish Pipeline was canceled

This commit was merged in pull request #1465.
This commit is contained in:
2026-08-29 16:54:39 +00:00
parent 5125fe21b0
commit 215faeda0a
20 changed files with 8407 additions and 132 deletions
@@ -44,7 +44,7 @@ type AnyDb = {
/** Column allowlist — the exact declared sets of §2/§3. Nothing else. */
const COLUMN_ALLOWLIST: Record<string, string[]> = {
companies: ['id', 'name', 'slug', 'created_at', 'updated_at'],
companies: ['id', 'name', 'slug', 'visibility', 'created_at', 'updated_at'],
estates: ['id', 'name', 'slug', 'company_id'],
platform_projects: ['id', 'name', 'slug', 'estate_id'],
workspaces: ['id', 'name', 'slug', 'platform_project_id'],
@@ -377,7 +377,61 @@ function witnessSuite(getHandle: () => AnyDb): void {
// Control: same subject and target with a different role is a new grant.
await db()
.insert(hierarchyGrants)
.values({ userId: userA, companyId, role: `${T}-other-role`, grantedBy: userA });
.values({ userId: userA, companyId, role: 'member', grantedBy: userA });
});
// ── §2.6 role vocabulary CHECK ─────────────────────────────────────────────
it('refuses a grant role outside the ratified vocabulary, accepts each ratified role', async () => {
await expectViolation(
db()
.insert(hierarchyGrants)
.values({ userId: userB, companyId, role: 'superuser', grantedBy: userA }),
/check constraint/i,
);
// Serialized namespaced forms are storage-invalid too: rows hold bare roles.
await expectViolation(
db()
.insert(hierarchyGrants)
.values({ userId: userB, companyId, role: 'hierarchy:owner', grantedBy: userA }),
/check constraint/i,
);
for (const role of ['viewer', 'member', 'owner'] as const) {
await db()
.insert(hierarchyGrants)
.values({ userId: userB, estateId, role, grantedBy: userA });
}
await db().execute(
sql`DELETE FROM hierarchy_grants WHERE user_id = ${userB} AND estate_id = ${estateId}`,
);
});
// ── §2.8 visibility column ─────────────────────────────────────────────────
it('defaults companies.visibility to private and refuses values outside the class', async () => {
const visId = randomUUID();
await db().execute(
sql`INSERT INTO companies (id, name, slug) VALUES (${visId}, 'Vis', ${T + '-vis'})`,
);
const res = rows(await db().execute(sql`SELECT visibility FROM companies WHERE id = ${visId}`));
expect(res[0]!['visibility']).toBe('private');
await db().execute(
sql`INSERT INTO companies (id, name, slug, visibility) VALUES (${randomUUID()}, 'Vis D', ${T + '-vis-d'}, 'directory')`,
);
await expectViolation(
db().execute(
sql`INSERT INTO companies (id, name, slug, visibility) VALUES (${randomUUID()}, 'Vis X', ${T + '-vis-x'}, 'public')`,
),
/check constraint/i,
);
await expectViolation(
db().execute(sql`UPDATE companies SET visibility = 'hidden' WHERE id = ${visId}`),
/check constraint/i,
);
await expectViolation(
db().execute(sql`UPDATE companies SET visibility = NULL WHERE id = ${visId}`),
/null value|not-null/i,
);
});
// ── §6.1 NOT NULLs ─────────────────────────────────────────────────────────
@@ -391,7 +445,7 @@ function witnessSuite(getHandle: () => AnyDb): void {
);
await expectViolation(
db().execute(
sql`INSERT INTO hierarchy_grants (user_id, company_id, role, granted_by) VALUES (${userA}, ${companyId}, 'x', NULL)`,
sql`INSERT INTO hierarchy_grants (user_id, company_id, role, granted_by) VALUES (${userA}, ${companyId}, 'viewer', NULL)`,
),
/null value|not-null/i,
);