///
// CFC Phase 3 demo: per-row, DATA-DERIVED SQLite labels.
//
// Each email row's confidentiality is computed from the row's own columns —
// the sender, every recipient on a messy RFC-5322 "To" line (regex split), and
// the db owner — and authored-by integrity is minted only when the row's own
// auth column shows dmarc=pass. The labels are enforced fail-closed:
//
// - the inbox query re-derives each row's label (alias-proof, by TRUE column
// origin) and attaches it to that row's entity doc;
// - the "skim" query declares an output ceiling (maxConfidentiality) and
// drops rows it does not admit (onExceed:"skip" — a declared, observable
// existence release);
// - COUNT(*) refuses outright: an aggregate's contributors cannot be
// re-labeled per row, and a skipped row cannot be un-counted;
// - every db.exec INSERT runs the same rule as a write gate (the computed
// row label is recorded as the write's CFC policy input before commit).
//
// Spec: docs/specs/sqlite-builtin/06-cfc.md ("Per-row labels, derived from
// row data")
import {
cfSqlite,
computed,
Default,
handler,
NAME,
pattern,
PerSession,
sqliteDatabase,
type SqliteDb,
Stream,
UI,
type VNode,
Writable,
} from "commonfabric";
interface MailRow {
id: number;
from_addr: string;
to_addrs: string;
auth: string;
body: string;
}
/** The inbox projection: `from_addr` is aliased to `sender`, so the returned
* rows carry `sender` and NOT `from_addr`. */
interface InboxRow {
id: number;
sender: string;
to_addrs: string;
auth: string;
body: string;
}
interface MailboxInput {
draftFrom: PerSession>>;
draftTo: PerSession>>;
draftBody: PerSession>>;
}
export interface MailboxOutput {
[NAME]: string;
[UI]: VNode;
seed: Stream;
}
const seedMail = handler((_, { db }) => {
db.exec(
"INSERT INTO emails (from_addr, to_addrs, auth, body) VALUES (?, ?, ?, ?)",
[
"Alice Example ",
"bob@example.com",
"spf=pass dmarc=pass dkim=pass",
"Lunch tomorrow?",
],
);
db.exec(
"INSERT INTO emails (from_addr, to_addrs, auth, body) VALUES (?, ?, ?, ?)",
[
"carol@c.example",
'"Dave D" , erin@e.example',
"",
"Quarterly numbers attached.",
],
);
});
const sendMail = handler<
void,
{
db: SqliteDb;
draftFrom: Writable;
draftTo: Writable;
draftBody: Writable;
}
>((_, { db, draftFrom, draftTo, draftBody }) => {
// The write gate evaluates the per-row rule against these bound values: a
// draft without a sender address fails closed (the rule's min:1 anchor).
db.exec(
"INSERT INTO emails (from_addr, to_addrs, auth, body) VALUES (?, ?, ?, ?)",
[draftFrom.get(), draftTo.get(), "", draftBody.get()],
);
draftBody.set("");
});
export default pattern(
({ draftFrom, draftTo, draftBody }) => {
const { table, all, principal, match, whenMatches, authoredBy, dbOwner } =
cfSqlite;
// Pull address tokens out of a messy "Name , addr" recipient line.
const ADDR = /[^\s<>,;"]+@[^\s<>,;"]+/g;
const emails = table(
{
id: "integer primary key",
from_addr: "text",
to_addrs: "text",
auth: "text",
body: "text",
},
(f) => ({
// Row confidentiality = sender ∧ every recipient ∧ the mailbox owner.
// (Conjunctive `all()` — `any()` (one OR-clause) unlocks per-user
// views once the clause-aware label profile lands; it errors today
// rather than silently meaning the wrong thing.)
confidentiality: all(
principal("mailto", match(f.from_addr, ADDR, { min: 1 })),
principal("mailto", match(f.to_addrs, ADDR)),
dbOwner(),
),
// Provenance from row data is forgeable by the row's writer, so this
// mints a self-describing claimed-authored-by atom (not the trusted
// AuthoredBy family), gated on the row's own auth evidence.
integrity: whenMatches(
f.auth,
/dmarc=pass/,
authoredBy(principal("mailto", match(f.from_addr, ADDR, { min: 1 }))),
),
}),
);
const db = sqliteDatabase({ tables: { emails } });
// The full inbox. `from_addr AS sender` also demonstrates that the rule's
// inputs resolve by TRUE column origin, never by output name.
const inbox = db.query(
"SELECT id, from_addr AS sender, to_addrs, auth, body FROM emails " +
"ORDER BY id",
{ reactOn: db },
);
// A skim view with a DECLARED output ceiling: only rows whose computed
// label fits {alice, bob, owner} survive; the rest are skipped (declared
// existence release). Carol's mail to Dave+Erin is dropped here.
const aliceBobSlice = db.query(
"SELECT id, from_addr, to_addrs, auth, body FROM emails ORDER BY id",
{
reactOn: db,
maxConfidentiality: [
"did:mailto:alice@a.example",
"did:mailto:bob@example.com",
{ __ctDbOwner: true },
],
onExceed: "skip",
},
);
// Aggregates on a rule-bearing table FAIL CLOSED: the count derives from
// every row, including rows a ceiling would not admit.
const mailCount = db.query<{ n: number }>(
"SELECT COUNT(*) AS n FROM emails",
{ reactOn: db },
);
const inboxRows = computed(() => inbox.result ?? []);
const sliceRows = computed(() => aliceBobSlice.result ?? []);
const countError = computed(() => String(mailCount.error ?? ""));
const seed = seedMail({ db });
return {
[NAME]: "Per-Row Labeled Mailbox (CFC Phase 3)",
[UI]: (
Inbox (all rows)
Every row carries its own data-derived label: sender ∧
recipients ∧ owner, re-derived from the row at read time.
Seed sample mail
{inboxRows.map((row) => (
#{row.id} from {row.sender} → {row.to_addrs}
{row.body}
{row.auth
? "auth: " + row.auth +
" ⟹ claimed-authored-by minted"
: "no auth evidence ⟹ no authorship claim"}
))}
Alice+Bob slice (declared ceiling, onExceed:"skip")
This query declares maxConfidentiality [alice, bob, owner].
Rows with other participants are dropped — a declared,
observable existence release.
{sliceRows.map((row) => (
#{row.id} from {row.from_addr}: {row.body}
))}
COUNT(*) fails closed
An aggregate's contributors cannot be re-labeled per row, so
the query refuses rather than under-labels:
{countError}
Compose (write gate)
The INSERT evaluates the same rule: no sender address ⟹ the
write is rejected (min:1 anchor, fail closed).
Send
),
seed,
};
},
);