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
@@ -4,14 +4,14 @@ import { AppModule } from '../app.module.js';
import { HierarchyModule } from '../hierarchy/hierarchy.module.js';
/**
* Hierarchy route-inventory baseline (contract 1 §6.3(a)).
* Hierarchy route inventory (contract 1 §6.3).
*
* M4-1b-i ships the audit event + outbox machinery with NO mutation routes:
* the hierarchy command family (controllers + DTOs) lands in M4-1b-ii once
* contract 2 merges. This witness enumerates every route the AppModule graph
* declares and pins that baseline, so a hierarchy route appearing before its
* command-family witnesses exist fails here first. When M4-1b-ii lands, this
* baseline is replaced by an exact inventory of the command family.
* The hierarchy command family is a CLOSED enumeration asserted here, not a
* prose claim: every hierarchy-flavored route the AppModule graph declares
* must appear in HIERARCHY_COMMAND_FAMILY, and vice versa. Adding or
* removing a hierarchy route without updating this inventory (and its
* witnesses) fails CI first. This replaces the M4-1b-i zero-routes
* baseline.
*/
interface RouteEntry {
@@ -77,7 +77,32 @@ function routesOf(controller: Type<unknown>): RouteEntry[] {
return routes;
}
describe('hierarchy route-inventory baseline (§6.3(a))', () => {
/**
* The closed command family (contract 1 §5, M4-1b-ii). Every entry is a
* mutation audited via the M4-1b-i path or one of the two ratified reads
* (granted companies, the §2.8 directory carve-out).
*/
const HIERARCHY_COMMAND_FAMILY = [
'POST /api/hierarchy/companies',
'GET /api/hierarchy/companies',
'GET /api/hierarchy/companies/directory',
'POST /api/hierarchy/companies/:id/rename',
'POST /api/hierarchy/companies/:id/visibility',
'DELETE /api/hierarchy/companies/:id',
'POST /api/hierarchy/estates',
'POST /api/hierarchy/estates/:id/rename',
'POST /api/hierarchy/estates/:id/transfer',
'DELETE /api/hierarchy/estates/:id',
'POST /api/hierarchy/platform-projects',
'POST /api/hierarchy/platform-projects/:id/rename',
'POST /api/hierarchy/platform-projects/:id/transfer',
'DELETE /api/hierarchy/platform-projects/:id',
'POST /api/hierarchy/grants',
'POST /api/hierarchy/grants/:id/change',
'DELETE /api/hierarchy/grants/:id',
] as const;
describe('hierarchy route inventory (§6.3)', () => {
const inventory = collectControllers(AppModule).flatMap(routesOf);
it('control: the enumeration sees the known route surface', () => {
@@ -88,19 +113,21 @@ describe('hierarchy route-inventory baseline (§6.3(a))', () => {
expect(inventory.length).toBeGreaterThan(20);
});
it('declares zero hierarchy mutation routes before M4-1b-ii', () => {
const hierarchyRoutes = inventory.filter((r) =>
/hierarch|compan|estate|platform[-_]?project/i.test(r.path),
);
expect(
hierarchyRoutes,
'a hierarchy route landed without replacing the §6.3(a) baseline with a command-family inventory',
).toEqual([]);
it('the hierarchy surface is exactly the declared command family', () => {
const hierarchyRoutes = inventory
.filter((r) => /hierarch|compan|estate|platform[-_]?project/i.test(r.path))
.map((r) => `${r.method} ${r.path}`)
.sort();
expect(hierarchyRoutes).toEqual([...HIERARCHY_COMMAND_FAMILY].sort());
});
it('HierarchyModule itself declares no controllers', () => {
expect((Reflect.getMetadata('controllers', HierarchyModule) ?? []) as unknown[]).toEqual([]);
const hierarchyControllers = collectControllers(HierarchyModule);
expect(hierarchyControllers).toEqual([]);
it('every command-family route lives on HierarchyController inside HierarchyModule', () => {
const controllers = collectControllers(HierarchyModule);
expect(controllers.map((c) => c.name)).toEqual(['HierarchyController']);
const declared = controllers
.flatMap(routesOf)
.map((r) => `${r.method} ${r.path}`)
.sort();
expect(declared).toEqual([...HIERARCHY_COMMAND_FAMILY].sort());
});
});