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:
parent
daf29fa332
commit
8471752827
4 changed files with 52 additions and 12 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue