Cloud Standard slots
The standard module turns the Cloud Standard slots
into a typed codec. CloudStandard encodes and seals each slot; CloudReader
reads and decrypts them (see Reading);
PrismManager orchestrates the high-level prism lifecycle.
Slot identifiers
SLOT_NAMES = {
key: "dataprism.key", encryption: "dataprism.encryption",
metadata: "dataprism.metadata", providers: "dataprism.providers",
}
SLOT_IDS = { key, encryption, metadata, providers } // hash of each name
dataId(name: string): DataId // hash of the name
fileDataId(filename: string): DataId // legacy filename hash; not scoped addressing
isReservedSlot(id: DataId): boolean // is it one of the dataprism.* slots?CloudStandard
A codec bound to a DataPrismKey. new CloudStandard(key)
retains legacy filename addressing. Pass a separate fileDataIdScopeKey to
enable scoped file identifiers.
| Slot | build | read | Encrypted? |
|---|---|---|---|
dataprism.key | keySlot() | readKey(payload) | clear |
dataprism.encryption | encryptionSlot() | readEncryption(payload) | clear |
dataprism.metadata | metadataSlot(meta) | readMetadata(payload) | sealed |
dataprism.providers | providersSlot(providers) | readProviders(payload) | sealed |
| file | fileSlot(index) | readFileIndex(payload) | sealed |
Each build method returns a SlotWrite = { dataId, payload }, ready to hand
to the execution layer. The clear slots are JSON as hex; the
sealed slots go through key.sealJson, except V3 file indexes, which seal
canonical JSON bytes. The read* methods throw a clear
"slot empty / does not exist" error on an empty payload instead of a cryptic
crypto failure.
readFileIndex and CloudReader.fileIndex accept legacy, V2 and V3 indexes.
The older readFile and CloudReader.file methods retain their legacy/V2
return types and reject V3 with guidance to use the union reader.
Other members:
initialSlots({ metadata?, providers? }) → SlotWrite[]: the provisioning slots. Always includeskeyplusencryption; addsmetadata/providersonly if provided.fileId(filename) → DataId: applies this standard's legacy or scoped profile.reader(source, cloudId) → CloudReader: build a decrypting reader.
const std = new CloudStandard(key);
const slots = std.initialSlots({ metadata, providers });
await (
await client.execution.batchWriteData({
cloudId,
dataIds: slots.map((s) => s.dataId),
payloads: slots.map((s) => s.payload),
})
).wait();Slot schemas
The file types below show the legacy index layout. V2 and V3 carry versioned chunk descriptors; use the union reader above when reading across versions.
interface MetadataSlot {
name: string;
description: string;
addresses: Record<Address, string>; // owner / writer addresses → readable name
fileDataIdProfile?: "dataprism.file-data-id.hmac-sha256.v1";
}
type ProviderId =
| "aws_s3"
| "gcp_storage"
| "azure_blob"
| "cloudflare_r2"
| "scaleway_object_storage";
interface ProvidersSlot {
aws_s3?: { service: "signed_url"; signerUrl; region; bucket };
gcp_storage?: { service: "signed_url"; signerUrl; projectId; bucket };
azure_blob?: { service: "signed_url"; signerUrl; accountName; container };
cloudflare_r2?: {
service: "signed_url";
signerUrl;
endpoint;
bucket;
region;
};
scaleway_object_storage?: {
service: "signed_url";
signerUrl;
endpoint;
region;
bucket;
};
}
interface Quorum {
dataShards: number;
parityShards: number;
}
interface ChunkDesc {
hash: Hex;
len: number;
shards: Hex[];
} // shards = content hashes
interface BlobRef {
level: number;
size: number;
chunks: ChunkDesc[];
}
interface FileIndex {
name: string;
size: number;
chunkSize: number;
quorum: Quorum;
providers: string[]; // ordered provider-ID list, frozen at upload
root: BlobRef; // compact Merkle root (≈ a few hundred bytes)
meta?: Record<string, unknown>;
}
interface SlotWrite {
dataId: Hex;
payload: Hex;
}Scoped file identifiers
Without fileDataIdScopeKey, CloudStandard uses the legacy
keccak256(UTF8(filename)) lookup rule and writes metadata without a
fileDataIdProfile. Supplying scoped-profile metadata without the addressing
key is rejected, as are unknown profile values.
const scoped = new CloudStandard(key, { fileDataIdScopeKey: addressingKey });
const dataId = scoped.fileId(filename);
const metadataSlot = scoped.metadataSlot(metadata);addressingKey must be 32 bytes and kept by the customer. In this mode,
metadataSlot() records dataprism.file-data-id.hmac-sha256.v1 automatically.
File writes, reads, repairs and name-based deletes use the keyed identifier:
domain-separated HMAC-SHA-256 over the canonical JSON of the NFC-normalized
filename. Names remain case-sensitive. Use standard.fileId(filename), not the
standalone legacy fileDataId(filename) helper, for these lookups.
Keep the addressing key stable across file-encryption key rotations.
TenantScope.bindPrism retains a separate addressing key in a customer-owned
binding store, and TenantPrism.standard(key) supplies it to CloudStandard.
The Prism opening key is still supplied separately. See
Three separate keys.
Scoped addressing does not itself turn on V3 writes or migrate existing
entries. V3 uploads require an explicit
publicIndex option; constructors without the addressing option and
legacy/V2 reads remain supported.
See File storage & resilience for how
FileIndex / BlobRef / ChunkDesc describe a file.
PrismManager
High-level lifecycle orchestration, on top of the execution layer plus the
standard. Build via client.prism(standard, cloudId?) or
new PrismManager({ standard, execution, source?, cloudId? }).
Creation
create(args: CreatePrismArgs): Promise<CreatePrismOutcome>CreatePrismArgs = { fundAmount, salt, metadata?, providers?, relayerFee?, timestamp? }.
The key slot written during creation always contains the public key and an empty
private member. The removed includePrivate property is rejected at runtime
for JavaScript callers as well as absent from the TypeScript interface.
create provisions the prism and writes all four reserved slots (key,
encryption, metadata, providers, with empty defaults if not supplied) in
a single atomic operation: one signature, one confirmation. The fundAmount
must cover the creation fee plus the per-entry data fees. It returns
{ cloudId, create, init }.
const manager = client.prism(new CloudStandard(key));
const { cloudId } = await manager.create({
fundAmount,
salt,
metadata,
providers,
});Dedicated slot editors
These write reserved slots intentionally. Full replacements use modifyData;
read-modify-write helpers use modifyDataIfCurrent with the observed evidence
lineage and version:
| Method | Effect |
|---|---|
setMetadata(metadata, opts?) | replace dataprism.metadata |
setProviders(providers, opts?) | replace all of dataprism.providers |
setProvider(id, config, opts?) | upsert one provider (reads current, merges, preserves others) |
removeProvider(id, options) | remove one provider after a matching safety preflight |
setProvider / removeProvider require a source with getDataEvidence
(the NetworkReader provides it). readMetadata() / readProviders() require
a source with getData.
Provider removal is intentionally fail-closed. Build a preflight from the complete known file inventory and the caller-approved minimum margin, repair every affected index, then remove the registry entry:
const preflight = preflightProviderRemoval("aws_s3", knownFiles, {
configuredProviders: await manager.readProviders(),
inventoryComplete: true,
minimumToleratedProviderLoss: approvedMinimum,
});
await manager.removeProvider("aws_s3", { preflight });An incomplete inventory or any file that still maps a shard position to the
provider blocks removal. A caller may pass
riskAcceptance: { reason: "..." }, but that acceptance is explicit and is
never selected by the SDK. A configuration change after the scan invalidates
the preflight. The submitted removal is also bound to that exact registry
snapshot on-chain; a concurrent update causes the transaction to revert rather
than overwriting the newer configuration.
await manager.setProvider("aws_s3", {
service: "signed_url",
signerUrl: "https://storage.example.com/aws/sign",
region,
bucket,
});
await manager.setMetadata({ name: "Acme", description: "v2", addresses });Guarded generic writes
writeData(dataId, payload, opts?) // throws if dataId is reserved
modifyData(dataId, payload, opts?) // throws if dataId is reserved
deleteData(dataId, opts?) // throws if dataId is reserved
batchWriteData(dataIds, payloads, opts?) // throws if ANY dataId is reserved
