Cover land parcels via proper trust setup, not generic nomination fields

Nomination Registry's "trust" channel type previously reused the same
nominee/institution fields as EPF/Takaful/bank nominations — wrong shape
for a trust, which needs a trustee (and successor) holding title, not a
beneficiary named on a policy. Coverage math already counted any
nomination-type entry as fast-path regardless of shape, so land parcels
were silently "covered" without the actual structure a trust requires.

Now: selecting "trust" swaps in trustee/successor-trustee/beneficiaries
fields, and adds a dedicated trust-setup drafting-aid export (same
treatment as the Waqf deed export) explaining what still needs a real
trust company/lawyer to execute.

e2e-trust.cjs: new suite verifying the full path for a land parcel —
exposed -> trust suggestion -> trustee fields -> deed export -> coverage
flips to 100% -> death trigger generates its execution packet. 12/12
passing. Re-verified e2e-uat.cjs (32/32) and e2e-fastpath.cjs (16/16)
with no regressions.
This commit is contained in:
wmj
2026-08-13 17:27:51 +08:00
parent ad2b3c48a3
commit ff47dc9ed2
2 changed files with 175 additions and 10 deletions
+68 -10
View File
@@ -22,14 +22,51 @@
{ value: 'trust', label: 'Pre-funded trust / nominee holding', note: 'For land or illiquid assets with no nomination bypass — requires setting up a real trust or nominee entity now, while alive.' }
];
const emptyForm = () => ({ linkedAssetId: '', type: 'epf', institution: '', nomineeeName: '', referenceNumber: '', trusteeName: '', successorTrustee: '', trustBeneficiaries: '' });
let nominations = $state(load('nominations', []));
let form = $state({ linkedAssetId: '', type: 'epf', institution: '', nomineeeName: '', referenceNumber: '' });
let form = $state(emptyForm());
function addNomination() {
if (!form.linkedAssetId || !form.nomineeeName) return;
if (!form.linkedAssetId) return;
if (form.type === 'trust') {
if (!form.trusteeName) return;
} else if (!form.nomineeeName) {
return;
}
nominations = [...nominations, { ...form, id: crypto.randomUUID() }];
save('nominations', nominations);
form = { linkedAssetId: '', type: 'epf', institution: '', nomineeeName: '', referenceNumber: '' };
form = emptyForm();
}
function exportTrustDeed(n) {
const asset = assets.find(a => a.id === n.linkedAssetId);
const lines = [
'TRUST / NOMINEE HOLDING SETUP — DRAFTING AID',
`Generated: ${new Date().toISOString().slice(0, 10)}`,
`Asset: ${asset ? `${asset.type}${asset.description}` : '(asset removed)'}`,
`Trustee: ${n.trusteeName}`,
`Successor trustee: ${n.successorTrustee || '________________________'}`,
`Beneficiaries: ${n.trustBeneficiaries || '________________________'}`,
'',
'PURPOSE: this asset has no nomination-based probate bypass available (unlike EPF or',
'Takaful). A pre-funded trust or nominee holding structure — set up and title-transferred',
'NOW, while the settlor is alive — is the only way to move it out of the probate-bound',
'estate. Once the trustee holds legal title on the beneficiaries\' behalf, distribution at',
'death is an internal trustee record update, not a Land Office / court filing.',
'',
'ACTION REQUIRED: this document is a drafting aid only. It does not itself create a trust',
'or transfer title. A licensed trust company, Amanah Raya-equivalent body, or lawyer must',
'draft the actual trust deed and complete the title transfer at the Land Office (or',
'equivalent registry) for this to have legal effect and actually bypass probate.',
'',
'DISCLAIMER: Not a fatwa. Not legal advice. Drafting aid only.'
];
const blob = new Blob([lines.join('\n')], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = 'trust-setup-draft.txt'; a.click();
URL.revokeObjectURL(url);
}
function remove(id) {
@@ -63,10 +100,19 @@
{#each CHANNEL_TYPES as c}<option value={c.value}>{c.label}</option>{/each}
</select>
</label>
<label class="field"><span>Institution</span><input type="text" bind:value={form.institution} placeholder="e.g. KWSP, Bank Islam, Takaful Ikhlas" /></label>
<label class="field"><span>Nominee name</span><input type="text" bind:value={form.nomineeeName} /></label>
<label class="field"><span>Reference / policy / account number</span><input type="text" bind:value={form.referenceNumber} /></label>
<button class="btn-primary" onclick={addNomination}>Add nomination</button>
{#if form.type === 'trust'}
<div class="trust-fields">
<p class="trust-note">Land and other illiquid assets have no legal nomination bypass — a trust needs a trustee, not just a "nominee", and title must actually be transferred to them while you're alive for this to work.</p>
<label class="field"><span>Trustee</span><input type="text" bind:value={form.trusteeName} /></label>
<label class="field"><span>Successor trustee</span><input type="text" bind:value={form.successorTrustee} /></label>
<label class="field"><span>Beneficiaries</span><input type="text" bind:value={form.trustBeneficiaries} placeholder="e.g. equal split among children" /></label>
</div>
{:else}
<label class="field"><span>Institution</span><input type="text" bind:value={form.institution} placeholder="e.g. KWSP, Bank Islam, Takaful Ikhlas" /></label>
<label class="field"><span>Nominee name</span><input type="text" bind:value={form.nomineeeName} /></label>
<label class="field"><span>Reference / policy / account number</span><input type="text" bind:value={form.referenceNumber} /></label>
{/if}
<button class="btn-primary" onclick={addNomination}>Add {form.type === 'trust' ? 'trust setup' : 'nomination'}</button>
</div>
{#each nominations as n (n.id)}
@@ -75,10 +121,18 @@
<div class="nomination-row">
<div class="nomination-info">
<strong>{asset ? asset.description : '(asset removed)'}</strong>
<span class="muted">{channelInfo?.label} · {n.institution || '—'}</span>
<span class="muted">Nominee: {n.nomineeeName} · Ref: {n.referenceNumber || '—'}</span>
<span class="muted">{channelInfo?.label}{n.type !== 'trust' ? ` · ${n.institution || '—'}` : ''}</span>
{#if n.type === 'trust'}
<span class="muted">Trustee: {n.trusteeName} · Successor: {n.successorTrustee || '—'}</span>
<span class="muted">Beneficiaries: {n.trustBeneficiaries || '—'}</span>
{:else}
<span class="muted">Nominee: {n.nomineeeName} · Ref: {n.referenceNumber || '—'}</span>
{/if}
</div>
<div class="row-actions">
{#if n.type === 'trust'}<button class="export-btn" onclick={() => exportTrustDeed(n)}>Export</button>{/if}
<button onclick={() => remove(n.id)}>✕</button>
</div>
<button onclick={() => remove(n.id)}>✕</button>
</div>
{:else}
<p class="empty">No nominations logged yet.</p>
@@ -103,4 +157,8 @@
.muted { color: #8A8478; font-size: 11.5px; }
.nomination-row button { background: none; border: none; color: #8A8478; cursor: pointer; }
.empty { font-size: 13px; color: #8A8478; text-align: center; padding: 20px 0; }
.trust-fields { background: rgba(201,168,76,0.05); border-radius: 10px; padding: 12px; margin-bottom: 12px; }
.trust-note { font-size: 11.5px; color: #C9A84C; line-height: 1.5; margin-bottom: 10px; }
.row-actions { display: flex; gap: 8px; align-items: center; }
.export-btn { background: rgba(46,204,113,0.15); color: #2ECC71; border: none; border-radius: 8px; padding: 6px 10px; font-size: 11px; cursor: pointer; font-weight: 600; }
</style>