refactor(approvals/t-paliad-160 slice3 / M2): drop required_role column
Cleanup of the t-paliad-160 dual-read shim. After slice 1+2 every writer
hits both `required_role` and the new `(requires_approval, min_role)`
columns, and every reader prefers the new ones. M2 (migration 065) drops
the legacy column from `paliad.approval_policies` and rewrites
`paliad.approval_policy_effective()` to a 4-column return shape.
`paliad.approval_requests.required_role` is intentionally untouched —
that's the in-flight policy snapshot at submission time, a separate
concern from the policy authoring grammar.
Go side:
- models.ApprovalPolicy and models.EffectivePolicy lose RequiredRole.
The MinRole pointer is now the only seniority-threshold surface.
- LookupPolicy / GetEffectivePolicyOne / List* / snapshotProjectRows
drop the required_role SELECT projection.
- UpsertProjectPolicySplit / UpsertUnitPolicySplit /
DeleteProjectPolicy / DeleteUnitPolicy / ApplyMatrixToDescendants
drop the required_role write. The audit-log row still uses the
legacy string format ('partner|...|none'); composed via
legacyFromSplit() from the new columns so the audit table layout
keeps working without a parallel migration.
- submit() reads policy.MinRole directly (LookupPolicy guarantees
non-nil when a non-nil policy is returned).
- nullToPtr helper retired (no remaining callers).
Frontend side:
- admin-approval-policies.ts UnitPolicy / EffectivePolicy lose the
legacy required_role optional. The 2-control UI was already on the
split-grammar path.
- deadlines-new.ts + appointments-new.ts form-time hint readers prefer
requires_approval+min_role. They keep a soft-fall back to the
legacy required_role for one cycle in case any cached pre-M2 server
is still serving the old shape — that path is dead-code post-deploy
and can be dropped later.
Test:
- TestApprovalService_PolicyCRUD asserts MinRole instead of
RequiredRole after re-upsert.
Build: bun build OK, go build ./... OK, go test ./... OK.
Deploy ordering: this slice MUST land after slice 2 is merged so the
pre-deploy code paths that still reference required_role have already
been retired.
This commit is contained in:
@@ -20,12 +20,9 @@ interface UnitPolicy {
|
||||
project_id: string | null;
|
||||
entity_type: string;
|
||||
lifecycle_event: string;
|
||||
// t-paliad-160 split-grammar — present on every fresh response. The
|
||||
// legacy required_role string is still served for one release as a
|
||||
// dual-read mirror (M1 → M2 in migration 064).
|
||||
// t-paliad-160 split-grammar.
|
||||
requires_approval: boolean;
|
||||
min_role?: string | null;
|
||||
required_role?: string | null;
|
||||
}
|
||||
|
||||
interface EffectivePolicy {
|
||||
@@ -33,8 +30,6 @@ interface EffectivePolicy {
|
||||
lifecycle_event: string;
|
||||
requires_approval: boolean;
|
||||
min_role?: string | null;
|
||||
// Legacy mirror, kept for the M1 dual-read window. Drop in M2.
|
||||
required_role?: string | null;
|
||||
source?: string | null;
|
||||
source_id?: string | null;
|
||||
source_name?: string | null;
|
||||
|
||||
@@ -126,16 +126,23 @@ async function refreshApprovalHint(): Promise<void> {
|
||||
hint.style.display = "none";
|
||||
return;
|
||||
}
|
||||
// t-paliad-160 split-grammar — read requires_approval + min_role.
|
||||
// Fall back to the legacy required_role mirror (M1 dual-read window
|
||||
// only — drops in M2).
|
||||
const eff = await resp.json() as {
|
||||
requires_approval?: boolean;
|
||||
min_role?: string | null;
|
||||
required_role?: string | null;
|
||||
source?: string | null;
|
||||
source_name?: string | null;
|
||||
};
|
||||
if (!eff.required_role || eff.required_role === "none") {
|
||||
const role = eff.min_role || eff.required_role || null;
|
||||
const required = (eff.requires_approval === true) || (role !== null && role !== "none");
|
||||
if (!required || !role) {
|
||||
hint.style.display = "none";
|
||||
return;
|
||||
}
|
||||
const roleLabel = tDyn("admin.approval_policies.role." + eff.required_role) || eff.required_role;
|
||||
const roleLabel = tDyn("admin.approval_policies.role." + role) || role;
|
||||
const sourceLabel = eff.source_name
|
||||
? ` · ${tDyn("admin.approval_policies.source." + (eff.source || "")) || ""}: ${eff.source_name}`
|
||||
: "";
|
||||
|
||||
@@ -193,16 +193,21 @@ async function refreshApprovalHint(): Promise<void> {
|
||||
hint.style.display = "none";
|
||||
return;
|
||||
}
|
||||
// t-paliad-160 split-grammar (with M1 legacy fallback).
|
||||
const eff = await resp.json() as {
|
||||
requires_approval?: boolean;
|
||||
min_role?: string | null;
|
||||
required_role?: string | null;
|
||||
source?: string | null;
|
||||
source_name?: string | null;
|
||||
};
|
||||
if (!eff.required_role || eff.required_role === "none") {
|
||||
const role = eff.min_role || eff.required_role || null;
|
||||
const required = (eff.requires_approval === true) || (role !== null && role !== "none");
|
||||
if (!required || !role) {
|
||||
hint.style.display = "none";
|
||||
return;
|
||||
}
|
||||
const roleLabel = tDyn("admin.approval_policies.role." + eff.required_role) || eff.required_role;
|
||||
const roleLabel = tDyn("admin.approval_policies.role." + role) || role;
|
||||
const sourceLabel = eff.source_name
|
||||
? ` · ${tDyn("admin.approval_policies.source." + (eff.source || "")) || ""}: ${eff.source_name}`
|
||||
: "";
|
||||
|
||||
Reference in New Issue
Block a user