///
// CFC Phase 3 demo: per-row rules COMPOSE with per-column (Phase 2) labels.
//
// A patient-records table mixes both label sources on one row entity:
// - the per-row rule derives WHO the row concerns from the row itself
// (patient ∧ clinic owner — every field of the row inherits it), and
// - the ssn column additionally carries a static per-column "pii" label.
//
// So reading r.ssn observes {pii ∧ patient ∧ owner} while r.diagnosis
// observes {patient ∧ owner} — and a declared output ceiling that admits the
// patient and the owner (but not "pii") accepts a diagnosis projection while
// REFUSING an ssn projection of the very same rows.
//
// Spec: docs/specs/sqlite-builtin/06-cfc.md ("Read — re-derive per row,
// attach, ceiling")
import {
cfSqlite,
computed,
handler,
NAME,
pattern,
sqliteDatabase,
type SqliteDb,
Stream,
UI,
type VNode,
} from "commonfabric";
interface DiagnosisRow {
id: number;
patient_email: string;
diagnosis: string;
}
export interface RecordsOutput {
[NAME]: string;
[UI]: VNode;
seed: Stream;
}
const seedRecords = handler((_, { db }) => {
db.exec(
"INSERT INTO records (patient_email, ssn, diagnosis) VALUES (?, ?, ?)",
["ada@a.example", "111-22-3333", "sprained wrist"],
);
db.exec(
"INSERT INTO records (patient_email, ssn, diagnosis) VALUES (?, ?, ?)",
["grace@g.example", "444-55-6666", "common cold"],
);
});
export default pattern, RecordsOutput>(() => {
const { table, all, principal, match, dbOwner } = cfSqlite;
const ADDR = /[^\s<>,;"]+@[^\s<>,;"]+/g;
const records = table(
{
id: "integer primary key",
patient_email: "text",
// Per-COLUMN (Phase 2) static label: ssn is pii wherever it flows.
ssn: { type: "string", ifc: { confidentiality: ["pii"] } },
diagnosis: "text",
},
// Per-ROW (Phase 3) rule: the whole row is confidential to the patient it
// concerns (derived from the row's own data) and the clinic owner.
(f) => ({
confidentiality: all(
principal("mailto", match(f.patient_email, ADDR, { min: 1 })),
dbOwner(),
),
}),
);
const db = sqliteDatabase({ tables: { records } });
const ceiling = [
"did:mailto:ada@a.example",
"did:mailto:grace@g.example",
{ __ctDbOwner: true },
];
// Diagnosis projection: per-row label only ⟹ fits the ceiling.
const diagnoses = db.query(
"SELECT id, patient_email, diagnosis FROM records ORDER BY id",
{ reactOn: db, maxConfidentiality: ceiling, onExceed: "fail" },
);
// SSN projection of the SAME rows: the per-column "pii" label rides every
// row and the ceiling does not admit it ⟹ the query REFUSES (fail closed).
const ssns = db.query<{ id: number; patient_email: string; ssn: string }>(
"SELECT id, patient_email, ssn FROM records ORDER BY id",
{ reactOn: db, maxConfidentiality: ceiling, onExceed: "fail" },
);
const diagnosisRows = computed(() => diagnoses.result ?? []);
const diagnosisError = computed(() => String(diagnoses.error ?? ""));
const ssnError = computed(() => String(ssns.error ?? ""));
const seed = seedRecords({ db });
return {
[NAME]: "Per-Row × Per-Column Labels (CFC Phase 3)",
[UI]: (
Diagnoses (per-row label fits the ceiling)
Each row's label is derived from its own patient_email (∧ the
clinic owner). The declared ceiling admits the patients and the
owner, so this projection flows.
Seed sample records
{diagnosisRows.map((row) => (
#{row.id} {row.patient_email}: {row.diagnosis}
))}
{diagnosisError}
SSNs (per-column "pii" exceeds the ceiling — refused)
The very same rows, projected with the ssn column: Phase 2's
static "pii" label rides every row, the ceiling does not admit
it, and onExceed:"fail" refuses the whole query — the two label
sources compose on one row entity.
{ssnError}
),
seed,
};
});