mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-10-01 20:33:50 +00:00
feat(specs): OpenObject and OpenSlice 0.1 (#192)
OpenObject is a bucket you can mount: keyed objects as ipfile swarms placed on OpenDisk disks under pay2seed at a stated redundancy (three replicas on three operators in two countries by default), an ipdb index as the bucket's clock, a repair loop, an HTTP API, an S3 mapping and a mount whose consistency is close-to-open by seq. d1sks.com is the reference store. OpenSlice is a container whose compute is rented from one market and whose disk is mounted from another: a host descriptor at /.well-known/openslice.json, one signed slice file (image by digest, OpenCPU/OpenMemory/OpenGPU units, OpenObject mounts, OpenCreds env, ports, placement, lifetime), and a reservation that is a paid2seed lease applied to compute with presence proofs per epoch. slic3s.com is the reference marketplace; c0mpute hosts take the slice role. Both are registered under OpenServer in the catalogs family with landing pages, rows in the OpenSwarm family table, and the spec-discovery contract. llms.txt now cites the specification URL for child specs too, which every block under OpenServer had been missing. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
8e4ea2f997
commit
c7cecb2955
8 changed files with 1019 additions and 3 deletions
272
docs/openobject.md
Normal file
272
docs/openobject.md
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
# OpenObject
|
||||
|
||||
OpenObject is a bucket you can mount. It is one file a store serves about the object storage it runs, one record for a bucket, one record per object, and the rules that turn a pile of rented disks into a place where keyed objects are kept at a stated redundancy, verified every period, repaired when a disk fails, and read back by path from anywhere. The disks are [OpenDisk](/opendisk) disks paid under [OpenSwarm](/openswarm) leases; the bytes on them are `ipfile` ciphertext the owner alone can read; the store is whoever keeps the index and posts the offers. It is maintained by Profullstack, Inc. as part of the LogicSRC open-standards surface, with [d1sks.com](https://d1sks.com) as the reference store, running every bucket at three replicas by default.
|
||||
|
||||
Status: **0.1**. A description of the shape a store, a client and a mount agree on, published so a bucket can move between stores and a disk can hold objects for any of them.
|
||||
|
||||
Slug: `openobject`
|
||||
|
||||
## The problem
|
||||
|
||||
The pieces under this document already exist. `pay2seed` says how one swarm is paid to be held, `paid2seed` says how a holder proves it still holds it, OpenDisk says how a disk is found, OpenFile says how a publisher lists the files it has released. c0mpute hosts files as blake3-addressed objects behind Reed-Solomon 10 data and 4 parity shards, on its own network, through its own routes.
|
||||
|
||||
What none of them says is what an application needs: a bucket with a name, a thousand keys under it that change, a promise that each object is on three different machines in two different countries, a read by key that comes back verified, a list by prefix, a version history, and a mount so a process can open `/data/photos/2026/09/a.jpg` without knowing any of the above. Every store that has built this has built it once, for itself, in a shape only its own client reads. OpenObject is that shape written down, so a bucket at one store is the same bucket at another, and a disk holding objects for one store is holding the same records for all of them.
|
||||
|
||||
## Terms
|
||||
|
||||
- A **store** is the service that keeps a bucket's index, places its objects on disks, watches their proofs and repairs them. It is a gateway, not a holder; it may hold nothing itself. [d1sks.com](https://d1sks.com) is the reference. Its **descriptor** is the file it serves about itself.
|
||||
- A **bucket** is a named set of objects under one key, with one placement policy. The bucket key signs the index.
|
||||
- An **object** is bytes under a **key**, where a key is a UTF-8 path such as `photos/2026/09/a.jpg`. An object has **revisions**; the newest is what a plain read returns.
|
||||
- A **disk** is an OpenDisk disk. A **holder** is a disk holding one object under a `paid2seed` lease.
|
||||
- The **policy** is the redundancy a bucket asks for: `replicas` (every holder has the whole object) or `erasure` (every holder has one shard).
|
||||
- A **mount** is a file-system view of a bucket on a client machine, or inside an [OpenSlice](/openslice) slice.
|
||||
- The **owner** is who holds the bucket key and the content keys, and whose escrow at a hub pays the disks.
|
||||
|
||||
## The store descriptor
|
||||
|
||||
A store serves a JSON document at `/.well-known/openobject.json` on its own origin.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "d1sks.com",
|
||||
"web": "https://d1sks.com",
|
||||
"operator": "https://profullstack.com/.well-known/openprofile.md",
|
||||
"key": "ed25519:7c02e4a19f5d3b8c6e1a0f4d7b2c9e8a5f3d1c0b4e7a6f9d2c5b8e1a4f7d0c3b",
|
||||
"api": "https://d1sks.com/openobject/v1",
|
||||
"hubs": ["https://d1sks.com/api/openswarm", "https://bittorrented.com/api/openswarm"],
|
||||
"disks": "https://d1sks.com/api/disks",
|
||||
"developer": {
|
||||
"cli": {
|
||||
"name": "ip",
|
||||
"install": { "curl": "curl -fsSL https://d1sks.com/install.sh | sh" },
|
||||
"docs": "https://d1sks.com/docs/cli"
|
||||
},
|
||||
"api_docs": "https://d1sks.com/docs/openobject"
|
||||
},
|
||||
"updated": "2026-09-21T06:00:00Z",
|
||||
"policies": [
|
||||
{ "id": "3x", "mode": "replicas", "replicas": 3, "min_operators": 3, "min_countries": 2, "default": true },
|
||||
{ "id": "2x", "mode": "replicas", "replicas": 2, "min_operators": 2 },
|
||||
{ "id": "rs-10-4", "mode": "erasure", "k": 10, "parity": 4, "min_operators": 14 }
|
||||
],
|
||||
"price": {
|
||||
"currency": "USD",
|
||||
"per_gib_month": { "3x": 0.036, "2x": 0.024, "rs-10-4": 0.017 },
|
||||
"per_gib_transfer": 0.005,
|
||||
"per_10k_ops": 0.004,
|
||||
"min_gib": 0,
|
||||
"max_object_gib": 500
|
||||
},
|
||||
"location": { "countries": ["DE", "FI", "US", "PT", "SG"] },
|
||||
"capacity": { "free_gib": 184000, "disks": 412 },
|
||||
"accepts": { "encryption": ["owner", "store"], "versioning": true, "public_reads": true, "mounts": ["fuse", "nfs", "webdav", "s3"] },
|
||||
"proof": { "every_hours": 6, "repair_within_hours": 24 },
|
||||
"record": { "source": "https://d1sks.com/api/openswarm/pay2seed/requesters/ed25519:7c02…0c3b", "buckets": 1930, "objects": 41200000, "lost": 0, "since": "2026-09-13T00:00:00Z" },
|
||||
"s3": "https://s3.d1sks.com"
|
||||
}
|
||||
```
|
||||
|
||||
The smallest valid descriptor is a name, an API and one policy:
|
||||
|
||||
```json
|
||||
{ "name": "spare closet", "api": "https://closet.example/openobject/v1", "policies": [{ "id": "3x", "mode": "replicas", "replicas": 3 }] }
|
||||
```
|
||||
|
||||
The rules, and every one degrades:
|
||||
|
||||
1. **`name`, `api` and one entry in `policies` are the only required keys.** A reader lists what it was given and reports the rest as unstated.
|
||||
2. **`operator`** is who is answerable, as an [OpenProfile.md](/openprofile) URL. **`key`** is the store's OpenSwarm identity, the requester key it posts `pay2seed` offers under and the key a disk sees on every lease. **`hubs`** are where it posts. A store with no key is a store that places nothing and only indexes; `api` says what it does.
|
||||
3. **`disks`** is where the store's pool is listed: a URL answering with the OpenDisk descriptors it places onto, or the descriptors inline. An owner reads the pool before trusting a policy that promises three operators.
|
||||
4. **`developer`** is the block [OpenServer](/docs/openserver) 0.2 defines, unchanged.
|
||||
5. **`policies`** are what the store will run. `mode` is `replicas` or `erasure`. For `replicas`, `replicas` is how many holders each have the whole object. For `erasure`, `k` data shards and `parity` shards, one per holder, and any `k` rebuild the object. `min_operators` and `min_countries` are floors on distinctness across a policy's holders, counted on the disks' `operator` and `location.countries`. One policy is `default: true`; a bucket that names none gets it. **Three replicas on three operators in two countries is the policy this document means by "3x", and it is the reference store's default.**
|
||||
6. **`price`** is per policy for keeping, one number for serving and one for operations. `per_gib_month` for a policy is what the owner pays per GiB of plaintext, so a `3x` price already covers three disks; a store passes the disks' `per_gib_month` through with its margin. `per_10k_ops` prices index operations (PUT, DELETE, LIST); reads of bytes are `per_gib_transfer`. `max_object_gib` caps one object.
|
||||
7. **`location`** and **`capacity`** are the pool summarised: the countries its disks are in and the free GiB across them. `capacity.disks` is the count.
|
||||
8. **`accepts`** is policy. `encryption: owner` means the owner encrypts before upload and the store never has a content key; `store` means the store holds keys for the owner, which is what a public bucket or an S3 client needs. `mounts` lists what the store can serve a bucket as.
|
||||
9. **`proof`** is the cadence the store leases at (`pay2seed` `proof.everyHours`) and the longest it lets an object stay below policy before a replacement holder has been leased.
|
||||
10. **`record`** is the store's standing as a requester at its hubs, `source` the hub's own page; the numbers are copied for convenience and a reader that ranks on them reads the hub. `lost` is objects that went below the read threshold, ever, and a store that has lost one says so.
|
||||
11. **`s3`** is an S3-compatible endpoint when the store serves one, see below.
|
||||
12. **Unknown keys are kept.**
|
||||
|
||||
Serve it as `application/json`. The descriptor is a claim; that it came from the store's own origin is one verification, and the hub's record under `key` is the other.
|
||||
|
||||
## The bucket
|
||||
|
||||
A bucket is a record the store keeps at `<api>/b/<bucket>` and the owner may keep anywhere.
|
||||
|
||||
```json
|
||||
{
|
||||
"openobject": "0.1",
|
||||
"type": "openobject.bucket",
|
||||
"id": "ed25519:3a9f0c2e7d1b5a8c4f6e2d0b9a7c1e3f5d8b0a2c4e6f8d1b3a5c7e9f0d2b4a6c",
|
||||
"name": "photos",
|
||||
"owner": "https://ana.example/.well-known/openprofile.md",
|
||||
"store": "https://d1sks.com/openobject/v1",
|
||||
"policy": { "id": "3x", "mode": "replicas", "replicas": 3, "min_operators": 3, "min_countries": 2 },
|
||||
"encryption": { "by": "owner", "box": "x25519:4c40…18c3" },
|
||||
"versioning": { "keep": 10, "days": 90 },
|
||||
"public": false,
|
||||
"index": "ed25519:3a9f…4a6c/index",
|
||||
"budget": { "hub": "https://d1sks.com/api/openswarm", "payment": { "network": "eip155:8453" } },
|
||||
"created": "2026-09-13T10:00:00Z",
|
||||
"updated": "2026-09-21T06:14:02Z",
|
||||
"sigs": [{ "alg": "ed25519", "key": "ed25519:3a9f…4a6c", "sig": "…" }]
|
||||
}
|
||||
```
|
||||
|
||||
1. **`id`** is the bucket key, an Ed25519 public key. The bucket is whoever holds the private half. Names are per owner and per store; the key is what a disk, a hub and a second store agree on.
|
||||
2. **`policy`** is one of the store's policies, copied in full at creation so the bucket carries its own promise when it moves.
|
||||
3. **`encryption.by`** is `owner` or `store`. With `owner`, each object is an `ipfile` swarm whose per-file key is derived from a bucket master key the owner holds, `keyDerivation: derived` as `ipfile` defines it, and `box` is the bucket's X25519 key that grants are boxed to. With `store`, the store holds the master key and serves plaintext to authorised readers.
|
||||
4. **`versioning`** keeps the last `keep` revisions of a key or those newer than `days`, whichever is more. `{ "keep": 1 }` is a bucket with no history. A revision kept is a revision still leased and paid for.
|
||||
5. **`public: true`** means any reader may GET and LIST without credentials. A public bucket is `encryption: store` or holds plaintext swarms.
|
||||
6. **`index`** is an `ipdb` feed under the bucket key: the append-only log of object records, one entry per revision and per tombstone. A store replicates it; a second store rebuilds the bucket from it; a mount follows it.
|
||||
7. **`budget`** names the hub and network the owner's escrow sits at. The store spends it on leases and repairs under `pay2seed` as the owner's delegate, and the hub's ledger for the requester is the bill.
|
||||
|
||||
## The object
|
||||
|
||||
One record per revision, appended to the index and returned by `HEAD`.
|
||||
|
||||
```json
|
||||
{
|
||||
"openobject": "0.1",
|
||||
"type": "openobject.object",
|
||||
"bucket": "ed25519:3a9f…4a6c",
|
||||
"key": "photos/2026/09/a.jpg",
|
||||
"rev": 3,
|
||||
"seq": 48211,
|
||||
"id": "sha256:d6c3f8285b7871d6a400cba14408288a9acde679f12e1e7dc276f29ca7c493ff",
|
||||
"size": 4194304,
|
||||
"contentType": "image/jpeg",
|
||||
"meta": { "camera": "X100VI" },
|
||||
"swarm": { "manifest": "sha256:9ab1…", "infohashV2": "sha256:4b74…a342", "cipherRoot": "sha256:ba8c…6f7a" },
|
||||
"inline": null,
|
||||
"policy": { "mode": "replicas", "replicas": 3 },
|
||||
"holders": [
|
||||
{ "disk": "ed25519:a41e…7b0a", "lease": "sha256:5c02…", "since": "2026-09-20T18:30:00Z", "provenAt": "2026-09-21T06:00:05Z", "shard": null },
|
||||
{ "disk": "ed25519:9d3c…11ef", "lease": "sha256:71aa…", "since": "2026-09-20T18:30:04Z", "provenAt": "2026-09-21T06:00:09Z", "shard": null },
|
||||
{ "disk": "ed25519:02be…c8d0", "lease": "sha256:c3f0…", "since": "2026-09-20T18:30:07Z", "provenAt": "2026-09-21T05:59:58Z", "shard": null }
|
||||
],
|
||||
"state": "healthy",
|
||||
"tombstone": false,
|
||||
"created": "2026-09-20T18:29:51Z",
|
||||
"sigs": [{ "alg": "ed25519", "key": "ed25519:3a9f…4a6c", "sig": "…" }]
|
||||
}
|
||||
```
|
||||
|
||||
1. **`key`** is the path. `/` separates segments, a key never starts with `/`, and there are no directories: a prefix that other keys share is listed as one, and that is all a directory is.
|
||||
2. **`rev`** counts revisions of this key from 1. **`seq`** is the entry's position in the bucket's index and is the bucket's clock: the record with the higher `seq` is the newer statement about a key, whoever wrote it and whenever it was signed.
|
||||
3. **`id`** is the SHA-256 of the plaintext, the `plainRoot` of the swarm, and the object's ETag. Two revisions with one `id` are one swarm held once, so a rewrite of unchanged bytes costs an index entry and nothing else.
|
||||
4. **`swarm`** names the `ipfile` manifest the bytes live in. An object of `size` under 65,536 bytes may instead be carried in **`inline`**, base64 in the index entry, with no swarm and no holders; the index's own replication is its redundancy. A store may pack many small objects into one swarm and record `swarm.range` for each; the record still carries its own `id`.
|
||||
5. **`holders`** are the disks under lease, one per replica or per shard, `shard` the shard index in erasure mode. `provenAt` is the last proof the store saw, from `paid2seed` receipts.
|
||||
6. **`state`** is `healthy` when every holder the policy asks for is leased and proven within `proof.every_hours`; `degraded` when fewer are but the object can still be read (at least one replica, or at least `k` shards); `lost` below that; `pending` before the first proof. A read of a `degraded` object succeeds; a store reports the state on every `HEAD`.
|
||||
7. **`tombstone: true`** is a deletion: the newest record for a key says there is no object. Earlier revisions stay readable by `rev` until `versioning` lets them go.
|
||||
8. The record is signed by the bucket key when the owner wrote it and by the store key when the store did on the owner's behalf; either is valid, and a reader that finds neither treats the record as hearsay.
|
||||
|
||||
## Placement
|
||||
|
||||
What the policy means, and what the store must do to say `healthy`:
|
||||
|
||||
1. **Distinct disks.** Every holder is a different OpenDisk `key`. Never two replicas on one seeder.
|
||||
2. **Distinct operators**, at least `min_operators`, counted on the disks' `operator`; a disk with no operator counts as its own and no more.
|
||||
3. **Distinct countries**, at least `min_countries`, counted on `location.countries`.
|
||||
4. **Offers, not uploads.** The store posts one `pay2seed` offer per object with `seeders.min` set to `replicas` (or `k + parity`) and `subject` the swarm, `visibility: private` unless the bucket is public, from the owner's escrow. Disks take leases as `paid2seed` says; the store fills from its pool first by posting to the disks' own hubs. Nothing in the swarm side changes.
|
||||
5. **The first copy comes from the client.** The client seeds the swarm, or uploads to the store's gateway which seeds it, until `replicas` holders have fetched and proven. A store that accepts the bytes and answers before that reports `state: pending`, and an honest client keeps its copy until `healthy`.
|
||||
6. **Locality is a preference, not a policy.** A store may place near where reads come from, or near the c0mpute nodes that will read the bucket, but never at the expense of the floors.
|
||||
|
||||
## Repair
|
||||
|
||||
1. A holder whose challenge or probe fails, or whose lease ends without renewal, is `failed` on the record at once.
|
||||
2. The store posts a replacement offer within `proof.repair_within_hours`, sourced from any healthy holder, and appends a record with the new holder when its first proof lands. The object is `degraded` meanwhile, never silently short.
|
||||
3. Repair spends the bucket's budget. A store that cannot repair because the escrow is empty says so on the record (`state: degraded`, `reason: budget`) and to the owner by whatever channel it has; it never lets an object drop below policy without a record saying why.
|
||||
4. In erasure mode, repair rebuilds one shard from any `k` and places it on a new disk; it never re-encodes the whole object.
|
||||
5. An object below the read threshold is `lost`, the store's `record.lost` goes up by one, and the record stays in the index so the loss is on the books.
|
||||
|
||||
## Reading, writing, listing
|
||||
|
||||
The store's API under `api`. Bodies are `application/json` unless bytes.
|
||||
|
||||
| Method and path | Does |
|
||||
|---|---|
|
||||
| `PUT /b/<bucket>` | Create or update a bucket record. Body is the record, signed by the bucket key. |
|
||||
| `GET /b/<bucket>` | The bucket record and its `state` summary: objects, GiB, `healthy`, `degraded` and `lost` counts. |
|
||||
| `GET /b/<bucket>?prefix=&delimiter=/&after=&limit=` | List keys: the newest non-tombstone record per key under `prefix`; with `delimiter`, common prefixes collapse to one entry each. Cursor is `after`. |
|
||||
| `PUT /b/<bucket>/<key>` | Write a revision. Body is the bytes (ciphertext when `encryption.by: owner`, with `X-OpenObject-Manifest` carrying the `ipfile` manifest) or, with `Content-Type: application/vnd.openobject.object+json`, a record for a swarm already seeded. `If-Match: <etag>` makes it conditional on the current `id`; `If-None-Match: *` makes it create-only. Answers `201` with the record and `state`. |
|
||||
| `GET /b/<bucket>/<key>` | The newest revision's bytes, verified against `id` before the last byte is sent. `Range` is honoured. `?rev=` reads an older revision. `ETag` is the `id`. `X-OpenObject-State` is the state. |
|
||||
| `HEAD /b/<bucket>/<key>` | The record, as headers and as `X-OpenObject-Record` JSON. |
|
||||
| `DELETE /b/<bucket>/<key>` | Append a tombstone. `?purge=1` also ends the leases of every revision, which is the only way bytes leave the disks before `versioning` lets them. |
|
||||
| `GET /b/<bucket>/<key>?versions` | Every revision's record, newest first. |
|
||||
| `GET /b/<bucket>.index?after=<seq>` | The index as an `ipdb` feed, for a mount or a second store to follow. |
|
||||
| `POST /b/<bucket>/<key>:repair` | Ask for a repair pass now. |
|
||||
| `GET /b/<bucket>/<key>:holders` | The holders with their OpenDisk descriptors, for a client that wants to fetch from the swarm itself. |
|
||||
|
||||
Authorisation is one of: a request signed by the bucket key (`Authorization: OpenObject <sig>` over method, path, date and body hash); an [OpenAccess](/openaccess) token with scope `openobject:read` or `openobject:write` and the bucket in its grant; nothing, on a `public` bucket's reads. A `409` on `If-Match` carries the current record. `402` on a write means the escrow will not cover the policy, with the shortfall.
|
||||
|
||||
Reads by a client that holds the content key are `ipfile` reads: fetch the ciphertext from any holder or the store's webseed, decrypt, verify `id`. A store reading for an `encryption: store` bucket does the same with its own key. Either way the plaintext hash is checked before it is served, and a mismatch is a `502` and a `failed` mark on the holder it came from.
|
||||
|
||||
## Mounting
|
||||
|
||||
A mount turns a bucket into a directory. It is how an [OpenSlice](/openslice) slice gets its disk and how a laptop gets its photos.
|
||||
|
||||
1. **Keys are paths.** The mount shows `photos/2026/09/a.jpg` at `<mountpoint>/photos/2026/09/a.jpg`. Directories are prefixes and exist while a key is under them. An empty directory is a key ending in `/` with no bytes.
|
||||
2. **Metadata is the index.** The mount follows `<bucket>.index` and answers `stat`, `readdir` and `open` from it, so a list never touches a disk. A mount that has the index from `seq` 48211 sees exactly the bucket at 48211.
|
||||
3. **Data is the swarm.** `read` fetches pieces from holders or the store's gateway, verifies, caches on local disk up to the mount's cache size. A slice on c0mpute reads from a holder on the same network with no internet egress.
|
||||
4. **Writes are write-back.** A file written is a local revision until `close`, then one `PUT`; the object is `pending` until the store says `healthy`, and the mount keeps its copy until then. `fsync` waits for `pending` to clear when the mount is `--sync`; otherwise it returns when the store has the bytes.
|
||||
5. **Consistency is close-to-open by `seq`.** Two mounts writing one key both succeed; the higher `seq` wins and the other becomes a revision. A mount that wants a lock uses `If-Match` and gets a `409`. There is no byte-range locking and no atomic rename across keys; `rename` is a copy of the record under the new key and a tombstone under the old, two index entries, and a reader may see the moment between them.
|
||||
6. **Owners' keys stay with the mount.** With `encryption: owner`, the mount holds the bucket master key and the store never sees plaintext. Inside a slice, the key is granted to the slice for its reservation, as [OpenSlice](/openslice) says.
|
||||
7. **What a mount is not.** Not a block device, not POSIX, not a database. A program that needs `O_APPEND`, `flock`, sparse files or sub-second `mtime` from two writers needs a local disk and should write its results to the bucket when it is done.
|
||||
|
||||
A store lists what it can serve a bucket as in `accepts.mounts`: `fuse` (the reference `ip mount` client), `nfs` and `webdav` (served by the store's gateway for machines that cannot run a client, plaintext only on `encryption: store` buckets or over a per-mount key the gateway holds for the session), and `s3` below.
|
||||
|
||||
## S3
|
||||
|
||||
A store may serve an S3-compatible endpoint at `s3`, because every tool already speaks it. Bucket name is the OpenObject bucket `id` or a name the store maps to one; object key is the key; `ETag` is the hex of `id`; `x-amz-meta-*` is `meta`; `ListObjectsV2` is the list above; `PutObject` with `If-None-Match: *` is create-only; versions are S3 versions with `VersionId` equal to `rev`. Multipart upload becomes one swarm when completed. Credentials are an OpenAccess token as the secret key and its id as the access key, or a store-issued pair. An S3 client never sees `holders` or `state`; `HeadObject` carries `x-amz-meta-openobject-state` so a careful one can. `encryption: owner` buckets are not reachable over S3, because S3 has nowhere to put the key.
|
||||
|
||||
## As an OpenServer offer
|
||||
|
||||
A store is a storage offer, and a directory that reads [OpenServer](/openserver) lists it as one:
|
||||
|
||||
| OpenServer | from OpenObject |
|
||||
|---|---|
|
||||
| `provider.name`, `web`, `operator`, `developer` | the same keys |
|
||||
| `offers[]` | one per policy: `id` the policy id, `name` the store's name and the policy, `kind` `storage`, `model` `p2p`, `premises` `off-prem`, `management` `managed`, `tenancy` `shared` |
|
||||
| `offers[].price` | `{ "amount": price.per_gib_month[policy], "currency", "interval": "month", "unit": "gib" }` |
|
||||
| `offers[].location` | `location`, unchanged |
|
||||
| `offers[].stock` | `in_stock` while `capacity.free_gib` is above zero |
|
||||
| `offers[].updated` | `updated` |
|
||||
|
||||
The disks under a store are listed separately as OpenDisk offers; a directory shows a store as one line and its pool as many.
|
||||
|
||||
## On c0mpute
|
||||
|
||||
c0mpute's storage role already holds blake3-addressed objects as Reed-Solomon 10 data and 4 parity shards and serves them from a gateway. Mapped onto this document: the network is a store, `rs-10-4` is its erasure policy, its gateway serves the API above, each storage worker is an OpenDisk disk, and `ipfile.pin` is the workload a placement becomes. What c0mpute gains is the bucket, the index and the mount, and what a bucket gains from c0mpute is locality: a slice or a transcode job reading a bucket held on the same network pays no internet egress, which is the network's whole argument for hosting files at all. An `object.repair` workload lets any verifier node run the repair pass and be paid per object restored.
|
||||
|
||||
## What is deliberately absent
|
||||
|
||||
**No settlement of its own.** Every byte held is a `pay2seed` offer and a `paid2seed` lease, and every proof and payout is theirs. This document adds the index, the policy and the repair loop, nothing under them.
|
||||
|
||||
**No plaintext at a disk.** With `encryption: owner` a disk holds ciphertext it cannot read and a store holds an index it cannot decrypt. With `encryption: store` the owner has chosen to trust the store, and the disks still see nothing.
|
||||
|
||||
**No central registry.** A store is anyone with an index and a hub account. A bucket moves by handing a second store its record and its index; the disks and the leases do not change.
|
||||
|
||||
**No POSIX.** A mount is a bucket seen as paths. What it promises is in the mounting section, and it promises no more.
|
||||
|
||||
**No consensus.** One bucket key, one index, one clock. Two stores serving one bucket follow the same feed; two owners of one bucket are one owner with two machines.
|
||||
|
||||
## Related standards
|
||||
|
||||
- [OpenSwarm](/openswarm): `ipfile` for the swarms, `pay2seed` and `paid2seed` for holding and proof, `ipdb` for the index, `ippay` for the payee.
|
||||
- [OpenDisk](/opendisk): the disks a store places onto, and their `holding` lists in which every object above appears.
|
||||
- [OpenFile](/openfile): a publisher's released files; a public bucket may be listed there, and an OpenFile entry may point at a bucket key.
|
||||
- [OpenSlice](/openslice): the container that mounts a bucket.
|
||||
- [OpenServer](/openserver): the storage offer a store maps onto, and the units this document borrows.
|
||||
- [OpenAccess](/openaccess): the token a client presents.
|
||||
- [OpenProfile.md](/openprofile): the owner and the operator.
|
||||
|
||||
## Version history
|
||||
|
||||
| Version | Date | Change |
|
||||
|---|---|---|
|
||||
| 0.1 | 2026-09-21 | First publication: the store descriptor, the bucket, the object, placement, repair, the API, mounting, S3, the OpenServer mapping, c0mpute. |
|
||||
|
||||
## License
|
||||
|
||||
The specification text is CC BY 4.0. Serve it, copy it, extend it.
|
||||
280
docs/openslice.md
Normal file
280
docs/openslice.md
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
# OpenSlice
|
||||
|
||||
OpenSlice is a container whose compute is rented from one market and whose disk is mounted from another. A **slice** is a fraction of a machine: some threads, some memory, perhaps a GPU, a little scratch, running one OCI image, with its durable state on an [OpenObject](/openobject) bucket mounted at a path. Because the state is in the bucket, the host is interchangeable: stop the slice here, start it there, the mount reattaches, the address moves with it. The host is a [c0mpute](https://c0mpute.com) node, or any machine that serves the file below; the settlement is OpenSwarm's; the disk is OpenObject's. It is maintained by Profullstack, Inc. as part of the LogicSRC open-standards surface, with [slic3s.com](https://slic3s.com) as the reference marketplace.
|
||||
|
||||
Status: **0.1**. A description of a file a host serves about the slices it will run, the file a slice is, and the reservation between them, published so a slice can move between hosts and a host can be found instead of waited for.
|
||||
|
||||
Slug: `openslice`
|
||||
|
||||
## The problem
|
||||
|
||||
A c0mpute job ends. A buyer posts an offer, a worker bids, runs it, publishes a receipt, and is paid; the work has a completion event and the settlement is built around it. A service has no such event. A web app, a game server, an agent that listens for messages, a database behind them: these run until somebody stops them, and what they need from a machine is a share of it for a while, not a job done.
|
||||
|
||||
c0mpute's hosting draft already found this for static sites and wrote a continuous reservation for it: epochs, presence proofs, slashable collateral. What is missing is the general case, a container, and the two facts that make a container on a peer network tolerable: the host holds nothing the owner cannot get back, because the disk is a bucket kept at three replicas elsewhere, and the host is described in a file, so a buyer picks one by reading rather than by hoping the auction lands somewhere good. The name is the thing sold: a slice of a machine.
|
||||
|
||||
The name is shared. ETSI's OpenSlice is an operations support system for 5G network slicing. This document is about containers, not radio, and says so here so a reader who searched for the other one knows.
|
||||
|
||||
## Terms
|
||||
|
||||
- A **host** is a machine that runs slices: a c0mpute worker with the slice role, or any box serving the descriptor below. Its **descriptor** is the file it serves about itself.
|
||||
- A **slice** is one container instance under one owner: an image, a resource ask, mounts, ports, a lifetime. Its **spec** is the file that says so.
|
||||
- The **owner** is who holds the slice key and pays the reservation.
|
||||
- A **reservation** is the match between a slice and a host at a hub: hours, price, escrow, proofs, payout. It is the `paid2seed` lease's shape applied to compute.
|
||||
- A **mount** is an OpenObject bucket seen as a directory inside the slice.
|
||||
- A **marketplace** reads host descriptors, shows standing from the hubs, and launches slices. [slic3s.com](https://slic3s.com) is the reference.
|
||||
|
||||
## The host descriptor
|
||||
|
||||
A host serves a JSON document at `/.well-known/openslice.json` on its own origin.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "rig-7b, Helsinki",
|
||||
"web": "https://rig-7b.example",
|
||||
"operator": "https://rig-7b.example/.well-known/openprofile.md",
|
||||
"key": "ed25519:b70c4e2a9d1f6c3b8e5a0d7f2c9b4e1a6f3d0c7b2e9a5f8d1c4b7e0a3f6d9c2b",
|
||||
"hubs": ["https://slic3s.com/api/openswarm", "https://c0mpute.com/api/openswarm"],
|
||||
"developer": {
|
||||
"cli": {
|
||||
"name": "c0mpute",
|
||||
"install": { "curl": "curl -fsSL https://c0mpute.com/install.sh | sh" },
|
||||
"docs": "https://c0mpute.com/docs/cli",
|
||||
"repo": "https://github.com/profullstack/c0mpute"
|
||||
}
|
||||
},
|
||||
"updated": "2026-09-21T06:00:00Z",
|
||||
"runtime": { "engine": "crun", "oci": "1.1", "rootless": true, "arch": "x86_64", "kernel": "6.12", "gpu": "nvidia" },
|
||||
"capacity": {
|
||||
"vcpu_total": 32, "vcpu_free": 20,
|
||||
"ram_mb_total": 131072, "ram_mb_free": 81920,
|
||||
"scratch_gb_free": 800,
|
||||
"gpu": [{ "model": "NVIDIA RTX 4090", "vendor": "NVIDIA", "count": 1, "vram_mb": 24576, "free": 1 }],
|
||||
"slices_max": 16, "slices_running": 5
|
||||
},
|
||||
"price": {
|
||||
"currency": "USD",
|
||||
"per_vcpu_hour": 0.006,
|
||||
"per_gib_ram_hour": 0.002,
|
||||
"per_gpu_hour": 0.35,
|
||||
"per_gib_scratch_month": 0.05,
|
||||
"per_gib_transfer": 0.005,
|
||||
"min_hours": 1,
|
||||
"max_hours": 8760
|
||||
},
|
||||
"location": { "regions": ["hel1"], "countries": ["FI"] },
|
||||
"network": { "bandwidth_mbps": 1000, "transfer_gb": 30000, "ipv4": 1, "ipv6": true, "ports": "any", "domains": true },
|
||||
"compute": { "arch": "x86_64", "model": "AMD Ryzen 9 7950X", "cores": 16, "threads_per_core": 2 },
|
||||
"accepts": {
|
||||
"images": ["ghcr.io", "docker.io", "openobject"],
|
||||
"privileged": false,
|
||||
"gpu": true,
|
||||
"egress": true,
|
||||
"max_vcpu": 16,
|
||||
"max_ram_mb": 65536,
|
||||
"max_scratch_gb": 200,
|
||||
"mounts": ["openobject"]
|
||||
},
|
||||
"proof": { "kinds": ["presence", "probe"], "epoch_seconds": 3600, "webhook": "https://rig-7b.example/openslice/hooks" },
|
||||
"record": {
|
||||
"source": "https://slic3s.com/api/openswarm/openslice/hosts/ed25519:b70c…9c2b",
|
||||
"standing": 96,
|
||||
"epochs": 2210,
|
||||
"missed": 4,
|
||||
"evicted": 0,
|
||||
"since": "2026-06-01T00:00:00Z"
|
||||
},
|
||||
"payout": { "payee": "ed25519:b70c…9c2b", "network": "eip155:8453" },
|
||||
"running": "https://rig-7b.example/openslice/running"
|
||||
}
|
||||
```
|
||||
|
||||
The smallest valid descriptor is a name, free threads, free memory and a price:
|
||||
|
||||
```json
|
||||
{ "name": "old laptop, Porto", "capacity": { "vcpu_free": 4, "ram_mb_free": 8192 }, "price": { "currency": "USD", "per_vcpu_hour": 0.004, "per_gib_ram_hour": 0.001 } }
|
||||
```
|
||||
|
||||
The rules, and every one degrades:
|
||||
|
||||
1. **`name`, `capacity.vcpu_free`, `capacity.ram_mb_free`, `price.per_vcpu_hour` and `price.per_gib_ram_hour` are the only required keys.** A reader lists what it was given and reports the rest as unstated.
|
||||
2. **`operator`** is who is answerable, as an [OpenProfile.md](/openprofile) URL. **`key`** is the host's OpenSwarm identity, the key that signs presence proofs and is paid; on c0mpute it is derived from the libp2p key as the c0mpute document says. **`hubs`** are where it takes reservations.
|
||||
3. **`developer`** is the block [OpenServer](/docs/openserver) 0.2 defines, unchanged.
|
||||
4. **`updated`** is when anything changed; `capacity` changes with every slice, so a busy host updates often.
|
||||
5. **`runtime`** is what runs the container: `engine` is `crun`, `runc`, `podman`, `docker`, `firecracker`, `gvisor` or `wasmtime`; `oci` the runtime-spec version; `rootless` whether containers run without root on the host; `arch` as OpenServer spells it; `gpu` the vendor whose devices can be passed in, or absent.
|
||||
6. **`capacity`** is in the units [OpenCPU](/docs/opencpu), [OpenMemory](/docs/openmemory) and [OpenGPU](/docs/opengpu) use: `vcpu` threads, `ram_mb` mebibytes, `vram_mb` mebibytes, `scratch_gb` gigabytes of local disk a slice may use and lose. `*_free` is what a new slice can have now and is what matters. `gpu[].free` counts devices not passed to a running slice.
|
||||
7. **`price`** is per hour for threads, memory and GPU, per month for scratch, per GiB for internet egress. A slice's hourly price is `vcpu * per_vcpu_hour + ram_gib * per_gib_ram_hour + gpus * per_gpu_hour`, pro rata by the second. `min_hours` and `max_hours` bound a reservation.
|
||||
8. **`location`, `network`, `compute`** are as OpenServer defines them. `network.ports` is `any`, a list, or `none` for a host that gives slices no inbound address; `network.domains` is whether the host will answer for a hostname the owner points at it.
|
||||
9. **`accepts`** is policy stated up front. `images` are the registries it will pull from, plus `openobject` when it will take an image from a bucket; `privileged: false` is the only value this version defines; `mounts` lists the mount kinds it can attach, `openobject` being the one this document needs.
|
||||
10. **`proof`** is how the host expects to be checked: `presence` is the host's signed statement per epoch that the slice ran; `probe` is a hub or the owner hitting the slice's health route from outside. `epoch_seconds` is the settlement period.
|
||||
11. **`record`** is the host's history at the hub named in `source`, copied for convenience: epochs proven, `missed`, and `evicted` (slices the host stopped before the owner did). A marketplace ranks on the hub's number, not the file's.
|
||||
12. **`payout`** names the `ippay` payee and network.
|
||||
13. **`running`** is a URL that answers with what the host runs now, or the list inline; shape below.
|
||||
14. **Unknown keys are kept.**
|
||||
|
||||
Serve it as `application/json`. The descriptor is a claim; the origin is one verification, the hub's record under `key` is the other.
|
||||
|
||||
## The slice
|
||||
|
||||
A slice is a record the owner writes and signs, sends to a hub with the reservation, and may keep anywhere.
|
||||
|
||||
```json
|
||||
{
|
||||
"openslice": "0.1",
|
||||
"type": "openslice.slice",
|
||||
"id": "ed25519:5e1d8a0c3f7b2e9d6c4a1f0b8e3d7c2a5f9b0e4d1c6a3f8b7e2d5c0a9f4b1e6d",
|
||||
"name": "shop-api",
|
||||
"owner": "https://ana.example/.well-known/openprofile.md",
|
||||
"image": { "ref": "ghcr.io/ana/shop-api@sha256:1f0e…d9c2", "arch": "x86_64" },
|
||||
"command": ["node", "server.js"],
|
||||
"compute": { "vcpu": 2, "ram_mb": 4096, "gpu": null, "scratch_gb": 10 },
|
||||
"mounts": [
|
||||
{ "bucket": "ed25519:3a9f…4a6c", "prefix": "shop/", "path": "/data", "mode": "rw", "cache_gb": 4, "sync": false },
|
||||
{ "bucket": "ed25519:77c1…0e2b", "prefix": "", "path": "/assets", "mode": "ro", "cache_gb": 2 }
|
||||
],
|
||||
"env": [
|
||||
{ "name": "NODE_ENV", "value": "production" },
|
||||
{ "name": "DATABASE_URL", "vault": "opencreds://ana.example/shop/database-url" }
|
||||
],
|
||||
"ports": [{ "container": 8080, "protocol": "tcp", "public": true, "domain": "api.shop.example" }],
|
||||
"health": { "http": "/healthz", "every_seconds": 30, "grace_seconds": 60 },
|
||||
"placement": { "countries": ["FI", "DE", "NL"], "exclude_hosts": [], "near": "ed25519:3a9f…4a6c", "min_standing": 80 },
|
||||
"lifetime": { "hours": 720, "renew": true, "restart": "always" },
|
||||
"price_cap": { "currency": "USD", "per_hour": 0.05 },
|
||||
"created": "2026-09-21T09:00:00Z",
|
||||
"sigs": [{ "alg": "ed25519", "key": "ed25519:5e1d…1e6d", "sig": "…" }]
|
||||
}
|
||||
```
|
||||
|
||||
1. **`id`** is the slice key. The slice is whoever holds the private half; it signs the spec, the reservation and every change. A slice keeps its `id` across hosts, which is what makes a move a move and not a new slice.
|
||||
2. **`image`** is an OCI image by digest. `ref` is a registry reference, or `openobject://<bucket>/<key>` for an image tarball kept in a bucket, which is how an image outlives the registry it came from. A tag without a digest is refused: two hosts must run the same bytes.
|
||||
3. **`compute`** is the ask, in OpenCPU, OpenMemory and OpenGPU units. `gpu` is `null` or `{ "count", "vram_mb", "vendor" }`. `scratch_gb` is local disk the slice may write and will lose on a move; the root filesystem is an overlay on scratch and counts against it.
|
||||
4. **`mounts`** are OpenObject buckets. `prefix` narrows the mount to keys under it; `path` is where it appears; `mode` `rw` or `ro`; `cache_gb` is the local read cache, out of scratch; `sync` makes `fsync` wait for `healthy`, as the OpenObject mounting section says. The content key for an `encryption: owner` bucket is granted to the slice key, not the host, and the mount runs inside the slice's namespace; the host can read what the slice reads, and this document says so plainly below.
|
||||
5. **`env`** is either a literal `value` or a `vault` reference an [OpenCreds](/opencreds) vault resolves at start. A vault secret never appears in the spec, at a hub, or in the marketplace's copy.
|
||||
6. **`ports`** are what the slice listens on. `public: true` asks the host for an address; `domain` is a hostname the owner will point at the host's address, which the host answers for when `network.domains` is true. A host that cannot give a port refuses the reservation rather than run the slice deaf.
|
||||
7. **`health`** is the route a host and a hub probe. A slice that fails `health` for `grace_seconds` is `unhealthy` on the record, restarted under `lifetime.restart`, and an epoch with no healthy probe is not paid.
|
||||
8. **`placement`** is what the owner will accept: countries, hosts never to use again, `near` a bucket key so the marketplace prefers hosts on the same network as its holders, `min_standing` at the hub.
|
||||
9. **`lifetime`** is the reservation's length, whether the owner's client renews it from escrow while funds last, and the restart policy `always`, `on-failure` or `never`.
|
||||
10. **`price_cap`** is the most the owner will pay per hour. A host whose price for this ask is above it is not offered the reservation.
|
||||
|
||||
## Running one
|
||||
|
||||
An owner that has read a descriptor and wants the host:
|
||||
|
||||
1. **Read** the descriptor for `accepts`, `capacity` and `price`, and `record.source` at the hub for standing. Price the ask; refuse if above `price_cap`.
|
||||
2. **Reserve.** Post an `openslice.reservation` at one of the host's `hubs`: the slice spec, the host `key`, `hours`, the hourly price, `budgetUsd` escrowed for the hours, `proof.epoch_seconds`. The hub holds the money, as `pay2seed` does for disks.
|
||||
3. **Take.** The host takes the reservation on its next poll or at once when the hub pushes to `proof.webhook`, and answers with a lease: the reservation id, its address for each public port, and `startsAt`. A host that has no room by then declines and the hub offers it to the next.
|
||||
4. **Start.** The host pulls the image by digest, resolves `env`, attaches the mounts (the OpenObject client inside the slice's namespace, the grant boxed to the slice key and handed over by the owner's client on the lease), applies `compute` as cgroup limits, passes the GPU if asked, opens the ports, and runs `command`. The slice is `running` when `health` first answers.
|
||||
5. **Prove.** Once per epoch the host signs a presence record: the reservation, the epoch, seconds run, CPU-seconds used, bytes in and out, health probes seen, and the same Merkle root over its request log that c0mpute's hosting draft defines. The hub, the owner, or a c0mpute verifier probes `health` from outside during the epoch. A proven epoch is paid to `payout.payee` from escrow; a missed one is not, and counts against `record.missed`.
|
||||
6. **Renew or end.** The owner's client renews from escrow while `lifetime.renew` and funds allow. At the end, or on `stop`, the host stops the container, the mount flushes write-back to `healthy`, scratch is discarded, and the lease closes with a final proof.
|
||||
|
||||
**Moving.** A move is a stop on one host and a start on another under the same slice `id`: the owner posts a new reservation with the spec unchanged, the new host starts, the mounts reattach to the same buckets at the index's current `seq`, the owner repoints `domain` or the marketplace does, and the old reservation ends. Nothing on the old host is needed. A host that goes away without a final proof is `evicted` on its record, and the owner's client moves the slice the same way; the slice's data was never only there.
|
||||
|
||||
States on the record: `pending`, `pulling`, `running`, `unhealthy`, `stopped`, `moved`, `evicted`.
|
||||
|
||||
## The reservation
|
||||
|
||||
```json
|
||||
{
|
||||
"openslice": "0.1",
|
||||
"type": "openslice.reservation",
|
||||
"hub": "ed25519:c9f1…",
|
||||
"owner": "ed25519:5e1d…1e6d",
|
||||
"host": "ed25519:b70c…9c2b",
|
||||
"slice": "sha256:2b7e…",
|
||||
"hours": 720,
|
||||
"priceUsdPerHour": "0.031000",
|
||||
"budgetUsd": "22.320000",
|
||||
"proof": { "epochSeconds": 3600, "verifiers": ["hub", "owner"] },
|
||||
"startsAt": "2026-09-21T09:05:00.000Z",
|
||||
"expiresAt": "2026-10-21T09:05:00.000Z",
|
||||
"payment": { "network": "eip155:8453", "nonce": "0x4d1a…" },
|
||||
"createdAt": "2026-09-21T09:04:41.000Z",
|
||||
"sigs": [{ "alg": "ed25519", "key": "ed25519:c9f1…", "sig": "…" }]
|
||||
}
|
||||
```
|
||||
|
||||
`slice` is the hash of the signed spec, kept at the hub beside it. `budgetUsd` is at least `hours * priceUsdPerHour`, rounded up. `verifiers` may include `c0mpute`, in which case a verifier node is paid to probe, as the disk protocols allow. Everything else is `pay2seed` and `paid2seed` with the swarm replaced by the container: one lease per reservation, one proof per epoch, GiB-months become slice-hours.
|
||||
|
||||
## Running
|
||||
|
||||
The other half of `record`: what one host runs now, from the host's side, at `running`.
|
||||
|
||||
```json
|
||||
{
|
||||
"updated": "2026-09-21T09:30:00Z",
|
||||
"running": [
|
||||
{ "slice": "ed25519:5e1d…1e6d", "reservation": "sha256:8c0a…", "vcpu": 2, "ram_mb": 4096, "gpu": 0, "since": "2026-09-21T09:05:12Z", "until": "2026-10-21T09:05:00Z", "state": "running", "provenAt": "2026-09-21T09:00:00Z" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
A host lists a slice's key and its resource use, never its image, mounts, env or ports; what a slice does is the owner's to announce.
|
||||
|
||||
## The marketplace
|
||||
|
||||
A marketplace reading descriptors does what an OpenDisk marketplace does: fetches on a schedule because `capacity` moves, dedupes on `key`, reads standing from the hub for anything it ranks on, shows `accepts` beside `price`, marks a host gone rather than deleted, and reports absence as absence. On top of that it:
|
||||
|
||||
1. **Prices an ask** against every host it knows and shows the hourly number before the owner posts.
|
||||
2. **Prefers locality.** With `placement.near`, it ranks hosts on the same network as the bucket's holders first, because a slice reading its own disk should not pay internet egress for it.
|
||||
3. **Launches.** One act: read the host, post the reservation, hand over the mount grants, show the address when the lease lands.
|
||||
4. **Moves.** On `evicted`, on a failed health probe past grace, or on the owner's click, it reserves elsewhere within `placement` and repoints `domain`.
|
||||
5. **Gives a name** when the owner has none: `<slice>.slic3s.app` or its own, pointed at whichever host runs the slice now.
|
||||
|
||||
[slic3s.com](https://slic3s.com) is the reference: every host descriptor it has read, standing from the hubs each names, and a launcher that takes a slice spec.
|
||||
|
||||
## As an OpenServer offer
|
||||
|
||||
A host is a hosting offer, and a directory that reads [OpenServer](/openserver) lists it without a second parser:
|
||||
|
||||
| OpenServer | from OpenSlice |
|
||||
|---|---|
|
||||
| `provider.name`, `web`, `operator`, `country`, `developer` | `name`, `web`, `operator`, `location.countries[0]`, `developer` |
|
||||
| `offers[].id` | `key`, or the descriptor's origin when there is no key |
|
||||
| `offers[].kind` | `cloud` |
|
||||
| `offers[].premises`, `management`, `tenancy` | `off-prem`, `unmanaged`, `shared` |
|
||||
| `offers[].model` | `p2p` when the operator is a person or a peer, `centralized` when it is a provider; the host may state `model` itself and that wins |
|
||||
| `offers[].compute` | `{ "vcpu": capacity.vcpu_free, "ram_mb": capacity.ram_mb_free, "arch": runtime.arch }` plus `compute` unchanged |
|
||||
| `offers[].gpu` | `capacity.gpu[0]` as an OpenGPU block, `count` its `free` |
|
||||
| `offers[].location`, `network` | the same keys, unchanged |
|
||||
| `offers[].price` | `{ "amount": per_vcpu_hour, "currency", "interval": "hour", "unit": "vcpu" }`, `unit` kept as the provider's own key; the memory and GPU prices ride along under `price` unchanged |
|
||||
| `offers[].stock` | `in_stock` while `vcpu_free` and `ram_mb_free` are above zero and `slices_running` is below `slices_max` |
|
||||
| `offers[].updated` | `updated` |
|
||||
|
||||
A provider that sells VPS plans and rents slices serves both files. A peer with one rig serves only this one, and is listed in both places.
|
||||
|
||||
## On c0mpute
|
||||
|
||||
A c0mpute node with the slice role adds `c0mpute:role:slice` and `c0mpute:openslice:host` to its capability tags, serves the descriptor from its gateway, and takes reservations as a continuous contract rather than a job: the hosting draft's epoch attestation with the site replaced by the container. `c0mpute slice up slice.json` reads the spec, prices it against the hosts the node knows, reserves, and prints the address; `c0mpute slice ls`, `logs`, `stop` and `move` do what they say. A slice on a c0mpute host reading an OpenObject bucket whose holders are c0mpute storage workers pays nothing for the bytes, which is the case the two documents were written for: compute from c0mpute.com, disk from d1sks.com, the pair from slic3s.com.
|
||||
|
||||
## What is deliberately absent
|
||||
|
||||
**No orchestration.** One slice is one container on one host. There are no pods, no services, no autoscaling, no scheduler beyond the marketplace's ranking. Two slices that need each other find each other by `domain`.
|
||||
|
||||
**No confidential compute.** A host runs the container and can read what the container reads, including a mounted bucket's plaintext while it is mounted. What this document promises is the opposite direction: nothing on the host is needed to get the slice back. An owner who cannot trust any host with a secret keeps it out of the slice.
|
||||
|
||||
**No persistent local disk.** Scratch is gone on a move. State that must survive is in a mount.
|
||||
|
||||
**No network between slices.** Each slice has its own address on its own host. A private network across hosts is a different specification.
|
||||
|
||||
**No image registry.** A slice names an image by digest at a registry it trusts, or keeps it in a bucket.
|
||||
|
||||
**No settlement of its own.** The reservation is a lease, the epoch is a period, the presence record is a proof, and payout is `ippay`. The disk protocols were written first and this document applies them.
|
||||
|
||||
## Related standards
|
||||
|
||||
- [OpenObject](/openobject): the bucket a slice mounts.
|
||||
- [OpenDisk](/opendisk): the same shape for a disk, and the marketplace rules this document copies.
|
||||
- [OpenSwarm](/openswarm): `pay2seed` and `paid2seed` for the reservation's shape, `ippay` for the payee, and the c0mpute document for node identity.
|
||||
- [OpenServer](/openserver), [OpenCPU](/docs/opencpu), [OpenMemory](/docs/openmemory), [OpenGPU](/docs/opengpu): the hosting offer a host maps onto and the units this document borrows.
|
||||
- [OpenCreds](/opencreds): where `env` secrets live.
|
||||
- [OpenProfile.md](/openprofile): the owner and the operator.
|
||||
|
||||
## Version history
|
||||
|
||||
| Version | Date | Change |
|
||||
|---|---|---|
|
||||
| 0.1 | 2026-09-21 | First publication: the host descriptor, the slice, running one, the reservation, running, the marketplace, the OpenServer mapping, c0mpute. |
|
||||
|
||||
## License
|
||||
|
||||
The specification text is CC BY 4.0. Serve it, copy it, extend it.
|
||||
|
|
@ -44,6 +44,8 @@ a different product. The member protocols keep their `ip` names.
|
|||
| `paid2stream` | Server protocol for paid live streams: relay leases per hour, presence proofs, gateways serving standard HLS, M3U and EPG | [`paid2stream.md`](./openswarm/paid2stream.md) |
|
||||
| OpenFile | The web door onto an `ipfile` swarm: `/.well-known/openfile.json` lists a publisher's files with fetch routes, verification, consent, price and holders | [`openfile.md`](./openfile.md) |
|
||||
| OpenDisk | The web door onto a `paid2seed` seeder: `/.well-known/opendisk.json` lists free GiB, price, policy, proof cadence and hub standing, so a disk is found instead of waited for | [`opendisk.md`](./opendisk.md) |
|
||||
| OpenObject | A bucket you can mount: keyed objects as `ipfile` swarms placed on OpenDisk disks under `pay2seed` at a stated redundancy, an `ipdb` index, repair, an API and a mount | [`openobject.md`](./openobject.md) |
|
||||
| OpenSlice | A container whose compute is a host and whose disk is an OpenObject mount: `/.well-known/openslice.json`, the slice file, and a reservation that is a `paid2seed` lease applied to compute | [`openslice.md`](./openslice.md) |
|
||||
|
||||
Supporting documents:
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue