Differentiate declared-only tenant resources

Refine tenant-apply dry runs so non-default tenant resources stay visible as declared state without being misclassified as the normal future-create path for new tenants.

---
Build: pass | Tests: pass — 31 passed (1 file)
This commit is contained in:
Mevy Assistant 2026-04-24 09:43:04 +02:00
parent daf29fa332
commit 8471752827
4 changed files with 52 additions and 12 deletions

View file

@ -302,5 +302,6 @@ Preflight for a future live apply should at minimum verify:
The dry-run apply surface should also report a per-resource checklist:
- `would-create` for tenant-owned resources a future live apply could create
- `exists-in-contract` for resources already satisfied declaratively
- `declared-only` for tenant-specific resources that exist in the registry but
are not part of the default creation contract for new tenants
- `blocked` for anything a future live apply must not touch yet

View file

@ -202,7 +202,7 @@ Also updated:
declarative model and what still blocks any automatic apply
- explicit policy buckets for future automatic candidates, manual-only
steps, and permanent out-of-scope actions
- a per-resource checklist showing `would-create`, `exists-in-contract`, or
- a per-resource checklist showing `would-create`, `declared-only`, or
`blocked` status for databases, datasets, and worker jails
## Recommended first code tasks

View file

@ -137,6 +137,17 @@ describe('tenant-registry', () => {
name: 'zroot/mevy-ai',
status: 'would-create',
}),
expect.objectContaining({
name: 'zroot/home/mevy',
status: 'declared-only',
}),
]),
);
expect(plan.actionPolicy.manualOnly).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'Declared non-default datasets',
}),
]),
);
expect(plan.manualSteps).toContain(

View file

@ -142,17 +142,17 @@ export interface TenantApplyPlan {
resourceChecklist: {
databases: Array<{
name: string;
status: 'would-create' | 'exists-in-contract' | 'blocked';
status: 'would-create' | 'declared-only' | 'blocked';
detail: string;
}>;
workerJails: Array<{
name: string;
status: 'would-create' | 'exists-in-contract' | 'blocked';
status: 'would-create' | 'declared-only' | 'blocked';
detail: string;
}>;
datasets: Array<{
name: string;
status: 'would-create' | 'exists-in-contract' | 'blocked';
status: 'would-create' | 'declared-only' | 'blocked';
detail: string;
}>;
};
@ -710,6 +710,17 @@ export function planTenantApply(
throw new Error(`Unknown tenant: ${deriveTenantId(tenantId)}`);
}
const defaultTenantShape = deriveTenantRecord(tenant.id, {}, registry);
const defaultDatasetSet = new Set(
defaultTenantShape.datasets.map(canonicalDatasetPath),
);
const automaticDatasets = tenant.datasets.filter((dataset) =>
defaultDatasetSet.has(canonicalDatasetPath(dataset)),
);
const declaredOnlyDatasets = tenant.datasets.filter(
(dataset) => !defaultDatasetSet.has(canonicalDatasetPath(dataset)),
);
return {
tenant,
registryPath,
@ -727,12 +738,20 @@ export function planTenantApply(
detail:
'Future live apply could provision this tenant-owned worker jail, subject to host-level review.',
})),
datasets: tenant.datasets.map((name) => ({
name,
status: 'would-create',
detail:
'Future live apply could create this tenant-owned dataset without touching shared platform datasets.',
})),
datasets: [
...automaticDatasets.map((name) => ({
name,
status: 'would-create' as const,
detail:
'Future live apply could create this tenant-owned dataset without touching shared platform datasets.',
})),
...declaredOnlyDatasets.map((name) => ({
name,
status: 'declared-only' as const,
detail:
'This dataset is declared for this tenant but is not part of the default dataset contract for new tenants.',
})),
],
},
actionPolicy: {
automaticCandidates: [
@ -750,7 +769,7 @@ export function planTenantApply(
},
{
name: 'Tenant datasets',
resources: tenant.datasets,
resources: automaticDatasets,
detail:
'A future live apply may create tenant-owned datasets only, without touching shared platform datasets.',
},
@ -766,6 +785,15 @@ export function planTenantApply(
detail:
'Any future jail, dataset, or database creation must run as an explicit host-level step.',
},
...(declaredOnlyDatasets.length > 0
? [
{
name: 'Declared non-default datasets',
detail:
`These datasets are declared today but should not be treated as the default creation path for new tenants: ${declaredOnlyDatasets.join(', ')}`,
},
]
: []),
],
outOfScope: [
{