Skip to content

Datasets on S3

A dataset on S3 uses the same calls as a dataset on any other storage kind. In the examples, Example Health’s imaging dataset holds files on S3.

Where S3 appears

S3 appears in one place: the dataset’s storageBinding, which an Owner or Admin registers once. Only the Seald Healthcare Cloud holds the binding and its credentials: an AWS role scoped to the bucket and prefix in the binding. A device never holds them. After registration, the application saves, opens, shares and deletes as for any other dataset. It never sees S3 again.

For a save or an open, the SDK:

  1. asks the Seald Healthcare Cloud for the object’s encryption details
  2. asks for temporary access to that one object in the bucket, valid for five minutes
  3. uploads or downloads the object itself, with that access

A share, an unshare, a hold or a retire changes only the Seald Healthcare Cloud. None of them touches an object in the bucket. A bucket that does not answer causes the error storage-unreachable, which is retryable.

Operations

OperationThe applicationThe SDKThe bucket
Registerdatasets.register with the bucket, prefix, region and roleSends the registration. When this device’s person is the first domain owner, creates the domain key and makes it recoverable by break-glass recoveryNothing yet
Listfolders.list(root)Makes one decision, then uses temporary read access to fetch and decrypt the folder indexServes the folder index
Saveobjects.create, or objects.save for the next versionMakes one decision, generates fresh keys, uploads the encrypted object using temporary write access, then updates the folder index and finishes the saveA new object under seald/<locator>/<version>, beside the earlier versions. A new folder index
Openobjects.open({ locator, version? })Makes one decision, uses temporary read access, verifies the whole object, and decrypts it in memory. close() discards the decrypted contentServes the stored object. An earlier version is its own object
Searchfolders.searchOpens every folder index in scope, one decision each, and searches them on the deviceServes the folder indexes. The bucket runs no search
Share, unsharedomains.share, domains.unshareVerifies each device certificate (card). Then gives every device of the person access to the domain key, or removes their access and starts a new epochNothing. No object changes, whatever the key domain’s size
One recorddomains.shareRecordGives each of the person’s devices access to every version of the recordNothing
Sub-domaindomains.create(folder) on an empty folderCreates a domain key for the folder subtree and makes it recoverable by break-glass recoveryA folder index of its own under the folder’s prefix
Retire, restoreobjects.delete, domains.restoreOne decision. The SDK marks or unmarks the entry in the folder indexNothing. The object stays. The Seald Healthcare Cloud refuses every fetch of it
Shreddomains.shredOne decision. The Seald Healthcare Cloud destroys the object’s encryption details, then deletes the object from the bucketThe object is deleted. The bucket’s own copies open nothing
Holdholds.place, holds.liftOne decision. A retire or shred under it is a denyNothing
Offline leaseNothing. Opens work as usualWhen the offline lease is granted, fetches every object it covers and locks them to the deviceServes them when the offline lease is granted. Nothing during an outage
Exportevidence.export({ datasets })Sends the manifest over the packNothing. A pack holds locators, which are the bucket’s keys, never a name
A save that fails after the uploadNothingNothing. No version existsAn object nobody can open. The Seald Healthcare Cloud deletes it 24 hours after the temporary access expires

Register, save, open

The samples use the allowed helper from Access decisions.

const sixYears = 6 * 365 * 24 * 60 * 60;
await allowed(await client.datasets.register({
name: 'imaging', classification: 'phi', storage: { kind: 'object-storage' },
storageBinding: { s3: { bucket: 'example-health-imaging', prefix: 'sealdhealthcare/', region: 'us-east-1', role: 'arn:aws:iam::123456789012:role/sealdhealthcare-cloud' } },
indexFields: ['modality', 'study_date'], segmentFields: [], retentionFloor: sixYears, firstOwner: alice,
}));
// from here on, the dataset's root is a folder like any other
const imaging = (await client.datasets.list()).find((d) => d.name === 'imaging')!;
const listing = await allowed(await client.folders.list(imaging.root));
if (listing) render(listing.entries);
const saved = await client.objects.create(
imaging.root,
{ kind: 'file', name: 'study-4471.dcm', type: 'application/dicom', bytes: fileStream, size },
{ subject: medicalRecordNumber },
);
if ('outcome' in saved) render(await allowed(saved)); // the SDK uploaded the encrypted file with temporary access
const opened = await allowed(await client.objects.open({ locator }));
if (opened?.kind === 'file') renderFile(opened.name, opened.stream()); // the SDK downloaded, verified and decrypted the file in memory
opened?.close();

Share and unshare

A share changes only the Seald Healthcare Cloud’s record of who can access the domain key. No object in the bucket changes. A share costs the same for ten studies or ten million.

await allowed(await client.domains.share(imaging.root, { person: alice })); // gives every device of alice access, for every epoch
await allowed(await client.domains.share(imaging.root, { group: 'radiologists' })); // a standing share. Member devices give later joiners access.
await allowed(await client.domains.unshare(imaging.root, { person: alice })); // removes access at once and starts a new epoch. The person keeps earlier studies.
await allowed(await client.domains.shareRecord(locator, bob)); // gives access to one study, version by version, with no listing
await allowed(await client.domains.create(folder, { members: [{ group: 'oncology' }] })); // an empty folder becomes a key domain of its own

Every save is a new object beside the old one. Seald Healthcare keeps the versions itself. The bucket’s own versioning is separate from Seald Healthcare’s. An older stored object put back in the bucket fails the folder index’s freshness check.

const next = await client.objects.save(
locator,
{ kind: 'file', name: 'study-4471.dcm', type: 'application/dicom', bytes: fileStream, size },
{ baseVersion: 1 },
);
const earlier = await allowed(await client.objects.open({ locator, version: 1 })); // its own object, open to whoever could open it
const found = await client.folders.search({ indexFields: { modality: 'MR' }, savedFrom: from }, { datasets: ['imaging'] });
render(found.entries); // locators only. The bucket runs no search.

Delete, restore, shred, hold

await allowed(await client.objects.delete({ locator, version: 1 })); // retires version 1. The Seald Healthcare Cloud refuses later fetches. The object stays in the bucket.
const bin = await client.domains.recycleBin(imaging.root);
await allowed(await client.domains.restore(bin[0]));
await allowed(await client.domains.shred(bin.filter((item) => !item.held))); // destroys the encryption details, then deletes the object from the bucket
await allowed(await client.holds.place({ dataset: 'imaging' }, 'Audit', 'AUD-2026-03')); // refuses every delete in the dataset until two Owners lift the hold

Offline and evidence

client.on('reachable', ({ reachable }) =>
banner(reachable ? undefined : 'Offline: the leased worklist opens, nothing else'), // the SDK fetched the covered objects when the offline lease started
);
const pack = await allowed(await client.evidence.export({ datasets: ['imaging'] }, { from, to })); // every access event on the dataset, by locator

What the bucket holds

This is what anyone who browses the bucket sees, including the storage provider.

KeyHoldsNever
seald/<locator>/<version>The encrypted object. A small unencrypted part identifies which key it needs. The name, type and times are inside the encrypted part, not visible in the bucketA name, a key, a hash of the content, the customer’s own id
The same, for a folder indexThe key domain’s listing, a stored object like any other. The SDK rewrites it on every saveA plaintext listing
The bucket’s own versions and copiesStored objects with no key anywhere. Once a shred destroys the encryption details, they stay unreadable for goodA way back after a shred

The customer’s bucket settings

SettingWith Seald Healthcare
Replication, the customer’s own backupsFine. Every copy is ciphertext. A copy without its encryption details opens nothing
Object versioningSeparate from Seald Healthcare’s versioning. Seald Healthcare keeps every version as its own object. A rollback fails the freshness check
A lifecycle rule that deletes objectsA loss Seald Healthcare cannot undo, never a disclosure
AccessThe role the Seald Healthcare Cloud assumes, for the prefix alone. Nothing else in Seald Healthcare reaches the bucket. A person who browses it sees opaque keys

Next