Scaffold dd0c/portal: AWS+GitHub discovery, catalog service, ownership resolution

- AWS scanner: ECS/Lambda/RDS discovery with partial failure handling
- GitHub scanner: CODEOWNERS parsing, commit-based heuristic ownership, rate limit resilience
- Catalog service: ownership resolution (config > codeowners > aws-tag > heuristic), staged updates for partial scans
- Ownership tests: 6 cases covering full priority chain
- PostgreSQL schema with RLS: services, staged_updates, scan_history, free tier (50 services)
- Fly.io config, Dockerfile
This commit is contained in:
2026-03-01 02:51:02 +00:00
parent ccc4cd1c32
commit 23db74b306
8 changed files with 600 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
import { describe, it, expect } from 'vitest';
import { resolveOwnership, type OwnershipRecord } from '../../src/catalog/service.js';
describe('Ownership Resolution', () => {
it('explicit config overrides AWS tag', () => {
const candidates: OwnershipRecord[] = [
{ owner: 'team-infra', source: 'aws-tag', confidence: 1 },
{ owner: 'team-platform', source: 'config', confidence: 1 },
];
const result = resolveOwnership(candidates);
expect(result.owner).toBe('team-platform');
expect(result.source).toBe('config');
});
it('CODEOWNERS overrides AWS tag', () => {
const candidates: OwnershipRecord[] = [
{ owner: 'team-infra', source: 'aws-tag', confidence: 1 },
{ owner: 'team-platform', source: 'codeowners', confidence: 1 },
];
const result = resolveOwnership(candidates);
expect(result.owner).toBe('team-platform');
expect(result.source).toBe('codeowners');
});
it('AWS tag overrides heuristic', () => {
const candidates: OwnershipRecord[] = [
{ owner: 'dev@other.com', source: 'heuristic', confidence: 0.5 },
{ owner: 'team-infra', source: 'aws-tag', confidence: 1 },
];
const result = resolveOwnership(candidates);
expect(result.owner).toBe('team-infra');
});
it('heuristic does not override explicit config', () => {
const candidates: OwnershipRecord[] = [
{ owner: 'team-platform', source: 'config', confidence: 1 },
{ owner: 'dev@other.com', source: 'heuristic', confidence: 0.8 },
];
const result = resolveOwnership(candidates);
expect(result.owner).toBe('team-platform');
});
it('returns unknown for empty candidates', () => {
const result = resolveOwnership([]);
expect(result.owner).toBe('unknown');
expect(result.source).toBe('heuristic');
expect(result.confidence).toBe(0);
});
it('config > codeowners > aws-tag > heuristic (full chain)', () => {
const candidates: OwnershipRecord[] = [
{ owner: 'heuristic-team', source: 'heuristic', confidence: 0.3 },
{ owner: 'aws-team', source: 'aws-tag', confidence: 0.8 },
{ owner: 'codeowners-team', source: 'codeowners', confidence: 0.9 },
{ owner: 'config-team', source: 'config', confidence: 1 },
];
const result = resolveOwnership(candidates);
expect(result.owner).toBe('config-team');
});
});