Business continuity
What you keep if this site is down for a day or gone for good. Your files are already yours; this page documents the paid applications' own formats, so you can read them without OBSCURA.
Your files stay with you
Nothing you open or make in OBSCURA OS is kept by this site: the tools run in your browser and save to your own device. If the site went down tomorrow, or stopped for good, no file of yours would go with it, because none of them is here. There is nothing to export from us and nothing to get back.
What you do need to think about is the work kept inside your browser rather than in a file: drafts the Workspace keeps as you type, documents the applications keep on the device with their versions, and projects kept in the browser's own storage. A browser short of disk space can clear a site's storage, and clearing the site's data deletes it. Save to a file, or to a folder on your disk, for anything you mean to keep.
The free tools
The Workspace and the single tools are ordinary web pages. Once opened they work with no connection, and installed as an app they open offline too. They save Word, Excel, PowerPoint, PDF, Markdown, CSV and the other formats listed on file formats, which other programs open.
The paid applications
The Sovereign Suite, the Photo Editor, the Engineering Suite, PDF Studio, Case and Umbra are served by this site only to a paid key, so they are the part that depends on the service. This is what happens to them:
- If the site is unreachable for a while, an application this browser has already opened keeps working from the copy it holds, offline, until the end of the period the key was paid for. A key cannot renew while the site is down.
- If the site stopped for good, the applications would stop when their keys ran out, at the end of the period last paid for. The files they made would not: the notes below say what is in each and how to read it without OBSCURA.
- Each application also saves in formats that do not need it: PDF, DXF, STEP, STL, PSD, TIFF, PNG and the rest. For anything you need to open in ten years, keep one of those beside the application's own file.
The formats, read from the code
Each note names the file in the code that writes the format, and the build checks the note against it: if the writer changes what it puts in the file, the build stops until the note says so. The checks also run the writers that run outside a browser and read back what they write.
OBSCURA Case: .obscase
- What it is
- A zip.
- Standard inside
- Zip; JSON and JSON Lines (one JSON record a line); plain UTF-8 text; each original file exactly as it was loaded; SHA-256.
- Its own
- The names of the fields in the JSON, which are Case's. Every case file carries a README.txt that explains them.
| Inside | What it holds |
|---|---|
README.txt | What every entry is, in plain words. |
case.json | The case: its name, matter, custodians, tags, privilege names and domains, chronology and exhibits. |
documents.jsonl | One record a document: its control number, family, custodian, metadata and hashes. |
coding.jsonl | The tags and note each coded document was given, by whom and when. |
batches.json, redactions.jsonl | The review batches, and each redaction as boxes on a page or ranges of text. |
audit.jsonl | The audit trail, one entry a line, each holding the SHA-256 of the one before it. |
text/ | The text taken from each document, named by control number. |
natives/ | Each original file once, named by its SHA-256. |
manifest.json, manifest.sha256 | Every entry with its size and SHA-256, and the manifest's own SHA-256. |
Reading it without OBSCURA. Unzip it. sha256sum -c manifest.sha256 checks the manifest, and each entry's hash in manifest.json checks the rest. The originals in natives/ open in whatever opened them before. A production Case makes for the other side is also a zip, with standard Concordance (.dat) and Opticon (.opt) load files, TIFF or JPEG pages, text, the originals, and its own manifest.
Written by pro-code/case-share.js; the build checks this note against it.
The Photo Editor: .obphoto
- What it is
- A zip, with a different name.
- Standard inside
- Zip; JSON; every layer's pixels as a PNG, at full size; lookup tables as the .cube text they came in as.
- Its own
- How the layers are put together: blend modes (named as the browser's canvas names them, such as
source-overandmultiply), adjustment layers, effects and editable text are described in document.json in the editor's own terms. A program that does not know them gets every layer's pixels, and has to stack them itself.
| Inside | What it holds |
|---|---|
document.json | The picture's size and resolution, and each layer bottom to top: its name, position, opacity, blend mode, mask, and the file holding its pixels. |
layers/1.png, layers/2.png, … | Each layer's pixels, in the order document.json lists them; 1 is the bottom. |
masks/, smart/, frames/ | A layer's mask, a smart object's source, a frame's picture, numbered like the layer they belong to. |
patterns/, alphas/, luts/, selection.png | Patterns, saved selections and lookup tables the picture uses, and the selection at the time it was saved. |
Reading it without OBSCURA. Rename it to .zip, or open it with any zip program. Each layer is an ordinary PNG; stacked bottom to top at the position document.json gives, with its opacity, they make the picture. To keep a picture any program opens, export a PNG, JPEG, WebP, TIFF or PSD from the editor as well.
Written by pro-code/photo-editor-file.js; the build checks this note against it.
Foundry (the Engineering Suite's 2D drawings): .foundry
- What it is
- One JSON text file.
- Standard inside
- JSON in UTF-8. Pictures traced over are inside it as
data:URLs. - Its own
- The drawing's shapes are Foundry's own records: lines, polylines, arcs, circles, splines, text, dimensions, hatches, blocks, sheets and viewports, with points as numbers in the drawing's units. Nothing but Foundry draws them from this file.
| Inside | What it holds |
|---|---|
format | Always obscura-foundry. |
drawing | The drawing: its name, units, precision, layers (name, colour, line type and weight), and ents, every shape with its type, layer and points; its blocks and sheets. |
assets | The pictures the drawing uses, by id. |
Reading it without OBSCURA. Any text editor or JSON reader opens it. To keep a drawing any CAD program opens, export DXF (AutoCAD's exchange format, version 2000) from Foundry as well, or PDF and SVG for a picture of it.
Written by pro-code/foundry-io.js; the build checks this note against it.
The Engineering Suite (3D parts and assemblies): .json (a project file)
- What it is
- One JSON text file.
- Standard inside
- JSON in UTF-8.
- Its own
- The part is kept as its history: sketches, and the features made from them (extrusions, revolves, holes, fillets and the rest), with their numbers and the variables they use. It is the suite's own parametric description; no other CAD program rebuilds a solid from it.
| Inside | What it holds |
|---|---|
format, version, saved | obscura-engineering, 1, and when it was saved. |
doc | The part or assembly: its name, material, variables and features, the history in order; an assembly also holds the parts in it. |
Reading it without OBSCURA. Any JSON reader opens it. To keep the shape itself readable by any CAD program, export STEP (a faceted solid), 3MF, STL, OBJ or glTF from the suite as well; a drawing of the part exports as DXF or PDF.
Written by pro-code/eng-io.js; the build checks this note against it.
OBSCURA Roots: .oroots
- What it is
- A zip archive.
- Standard inside
- The tree exports as GEDCOM 5.5.1 or 7.0, the genealogy standard every family history program and site reads, and as GEDZIP (GEDCOM 7.0 with its pictures in one zip). Pictures and documents inside the project are kept byte for byte, in their own formats.
- Its own
- tree.json, the whole tree as Roots holds it: the GEDCOM it was read from, with every tag Roots does not show kept on its record, and what GEDCOM has no place for: DNA matches, the research log and to-do lists, and faces marked in pictures.
| Inside | What it holds |
|---|---|
manifest.json | oroots, its version, when it was saved and how many people are in it. |
tree.json | The tree: people, families, sources, repositories, notes and places, in JSON. |
media/ | Each picture or document, under its record's ID, as it was added. |
Reading it without OBSCURA. Any zip tool opens it and any JSON reader reads tree.json. To keep the tree readable by every genealogy program, export GEDCOM from Roots as well.
Written by pro-code/roots-store.js; the build checks this note against it.
OBSCURA Vector: .ovector
- What it is
- A zip, with the mimetype first and uncompressed, as an EPUB or an OpenDocument file has it.
- Standard inside
- Zip, JSON in UTF-8, every picture as the PNG or JPEG it was, and preview.svg: the first artboard as an ordinary SVG file that any browser or drawing program opens.
- Its own
- The shape of the JSON: artboards, layers, and each object with its geometry and style. Paths are lists of anchor points with their two handles; rectangles, ellipses, polygons and stars keep their sizes and a matrix; text keeps its face, size and spacing; parts of an imported SVG that Vector does not edit are kept as the SVG they were.
| Inside | What it holds |
|---|---|
mimetype | application/x-obscura-vector. |
document.json | The document: format is obscura-vector, then artboards, layers with everything in them, the swatches, guides and grid. |
media/ | Each picture placed in the document, named by its id in document.json. |
preview.svg | The first artboard, written as Vector exports SVG. |
Reading it without OBSCURA. Unzip it and open preview.svg in any browser, or read document.json in any JSON reader. To keep every artboard in a standard file, export SVG for each from Vector, and PDF for print.
Written by pro-code/vector-out.js; the build checks this note against it.
OBSCURA Erase: .oerase
- What it is
- The line
OBSCURA-ERASE, a four-byte length and a JSON header, then a zip sealed with AES-256-GCM under a key made from the file’s own passphrase with Argon2id (or PBKDF2-SHA-256 at 600,000 rounds where Argon2 cannot run); the header holds the parameters. - Standard inside
- AES-256-GCM, Argon2id, zip and JSON in UTF-8; each request’s letters as PDF; each file that came back as the bytes it arrived as.
- Its own
- The shape of the JSON: the person’s details, and each request with its organisation, law, right, the text it was asked with, and its timeline of dated events.
| Inside | What it holds |
|---|---|
erase.json | format is obscura-erase-data; index holds me, requests and settings. |
letters/ | One PDF a request: the request letter and the follow-ups its timeline called for when it was exported. |
files/ | What each organisation sent back, in a folder named by the file’s id. |
Reading it without OBSCURA. With the passphrase, the header gives the key derivation’s parameters and the rest is one AES-GCM message: any tool with Argon2id and AES-GCM opens it, and the zip inside opens anywhere. The PDFs read in any PDF reader.
Written by pro-code/erase-store.js; the build checks this note against it.
OBSCURA Umbra: a folder
- What it is
- Not a file of its own: a project is a folder of ordinary files.
- Standard inside
- Everything. A folder you opened is read and written in place, so the files on your disk are the project. A project kept in the browser exports as an ordinary zip, with its git history (a normal .git folder) if you ask.
- Its own
- Nothing.
| Inside | What it holds |
|---|---|
The project's files | As you wrote them. |
.git | The version history, in git's own format, which any git reads. |
Reading it without OBSCURA. Open the folder, or unzip the export, in any editor. A project kept only in the browser is lost if the browser's data is cleared, so export a zip now and then.
Written by pro-code/umbra-fs.js; the build checks this note against it.
OBSCURA Books: .obooks
- What it is
- A zip.
- Standard inside
- Zip; JSON; the receipts and bills as the pictures or PDFs they were; SHA-256.
- Its own
- The shape of the JSON: the company, its chart, contacts, documents, bank lines and journals, as Books keeps them.
| Inside | What it holds |
|---|---|
company.json | obscura-books, version 1: the company's details and VAT scheme, accounts (the chart), contacts, docs (invoices, credit notes, quotes and bills, line by line), bank (each statement line and how it was explained), journals, rules, reconciliations, posted VAT returns and closed years. Every amount is a whole number of pence (or the currency's smallest unit). |
attachments/ | Each receipt or supplier's PDF, under its id and its own name. |
manifest.json | obscura-books-file: when it was written, and the path, size and SHA-256 of every other part, checked when Books opens the file. |
Reading it without OBSCURA. Unzip it. company.json opens in any JSON reader or spreadsheet's import; the attachments are ordinary files. Books also exports every journal line, document and bank line as CSV for an accountant's own software.
Written by pro-code/books-store.js; the build checks this note against it.
OBSCURA Keep: .okeep
- What it is
- One file: the line
OBSCURA-KEEP, a JSON header, then each sealed part as a name and its bytes. A package left for one person is the same, with a format ofobscura-keep-package. - Standard inside
- JSON; base64; Argon2id (or PBKDF2-SHA-256, as the header says); AES-256-GCM; HKDF-SHA-256.
- Its own
- The way the parts are strung together, and the names of the fields inside the index, which are Keep's. Every part is sealed, so nothing in it reads without the passphrase.
| Inside | What it holds |
|---|---|
The header | The Keep's id; kdf, the passphrase's salt and Argon2id parameters; wraps.pass, the 32-byte vault key sealed under the key the passphrase gives (and wraps.recovery, under the recovery key's). |
index.bin | Every record, login, code and note, the letter and the settings, as JSON, sealed: a version byte (1), a 12-byte IV, then AES-256-GCM ciphertext and tag. The key is HKDF-SHA-256 of the vault key, salt obscura-keep| and the id, info obscura-keep v1 data; the additional data names the part. |
b-<id>.bin, t-<id>.bin | Each document's bytes, and its thumbnail, sealed the same way. |
Reading it without OBSCURA. The Python below, with the cryptography package (version 45 or later), opens one with its passphrase and writes the index and every document into a folder. A package somebody left you also opens at /keep-open, with no account.
import base64, getpass, json, os, struct, sys
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
data, out = open(sys.argv[1], "rb").read(), sys.argv[2]
assert data.startswith(b"OBSCURA-KEEP\n"), "not a Keep file"
at = 13
n, = struct.unpack(">I", data[at:at + 4]); at += 4
header = json.loads(data[at:at + n]); at += n
parts = {}
while True:
nl, = struct.unpack(">H", data[at:at + 2]); at += 2
if not nl: break
name = data[at:at + nl].decode(); at += nl
ln, = struct.unpack(">I", data[at:at + 4]); at += 4
parts[name] = data[at:at + ln]; at += ln
kid, k = header["id"], header["kdf"]
salt = base64.b64decode(k["salt"])
secret = (sys.argv[3] if len(sys.argv) > 3 else getpass.getpass("Passphrase: ")).encode()
if k["alg"] == "argon2id":
kek = Argon2id(salt=salt, length=32, iterations=k["t"], lanes=k["p"], memory_cost=k["m"]).derive(secret)
else:
kek = PBKDF2HMAC(hashes.SHA256(), 32, salt, k["i"]).derive(secret)
def unseal(key, sealed, aad):
assert sealed[0] == 1
return AESGCM(key).decrypt(sealed[1:13], sealed[13:], aad.encode())
vault = unseal(kek, base64.b64decode(header["wraps"]["pass"]), "obscura-keep|v1|wrap|pass|" + kid)
key = HKDF(hashes.SHA256(), 32, ("obscura-keep|" + kid).encode(), b"obscura-keep v1 data").derive(vault)
index = json.loads(unseal(key, parts["index.bin"], "obscura-keep|v1|index|%s|index" % kid))
os.makedirs(out, exist_ok=True)
json.dump(index, open(os.path.join(out, "index.json"), "w"), indent=1, ensure_ascii=False)
for item in index["items"].values():
for f in item.get("files", []):
body = unseal(key, parts["b-%s.bin" % f["id"]], "obscura-keep|v1|blob|%s|%s" % (kid, f["id"]))
open(os.path.join(out, f["id"] + " " + f["name"].replace("/", "-")), "wb").write(body)
print(len(index["items"]), "items written to", out)
Written by pro-code/keep-store.js; the build checks this note against it.
OBSCURA Research: .oresearch (a project file)
- What it is
- A zip archive.
- Standard inside
- Zip, holding JSON in UTF-8, the pictures as PNG, JPEG, GIF or WebP files and the recordings as the audio and video files they were. The data also save as SPSS .sav, Stata .dta, CSV and Excel .xlsx, the output as Word and PDF, the codebook as CSV and Word, transcripts as WebVTT, and the coding as a REFI-QDA project, all standard files.
- Its own
- The project's description in
project.json: the data set by column with each variable's label, value labels, missing values and measure; the sources' text; the codes, the coded passages (a code, a source and the characters from and to), memos, annotations and cases; the output viewer's tables; and the syntax log.
| Inside | What it holds |
|---|---|
project.json | format (obscura-research), version 1, the name, data, qual, output and log. |
images/ | Each picture a coded source holds, as it was imported. |
media/ | Each recording a coded source holds, as it was imported. |
Reading it without OBSCURA. Unzip it and open project.json in any JSON reader; the coded passages are character offsets into each source's text. To take the data elsewhere, save them as .sav, CSV or Excel from the File menu.
Written by pro-code/research-project.js; the build checks this note against it.
OBSCURA Write: .owrite
- What it is
- A zip, with the mimetype first and uncompressed, as an EPUB or an OpenDocument file has it.
- Standard inside
- Zip, JSON in UTF-8, and the pictures and PDFs in the research as the files they were.
- Its own
- The shape of the JSON: the binder as items with their children, and the bible, labels, targets, writing history, comments and submissions. The text of each document is a small subset of HTML (paragraphs, emphasis, footnotes and notes as spans); a screenplay's scene marks each paragraph with its element.
| Inside | What it holds |
|---|---|
mimetype | application/x-obscura-write. |
project.json | The project: format is obscura-write, items the binder, each with its title, synopsis, status, labels, target, story date, snapshots and body. |
media/ | Each picture or PDF added to the research, named by its id in project.json. |
Reading it without OBSCURA. Unzip it and read project.json in any JSON reader; every document's text is there as HTML a browser shows. To have the writing itself in a standard file, compile to Word, EPUB, Markdown or plain text, or a screenplay to Final Draft or Fountain.
Written by pro-code/write-out.js; the build checks this note against it.
OBSCURA Authorship: .oauthor (a record of how a work was made)
- What it is
- A zip of ordinary files.
- Standard inside
- Zip, JSON lines, JSON, Markdown, HTML, PDF, Word (.docx), PNG, RFC 3161 timestamp tokens (DER) and an ECDSA P-256 signature. The work itself is in its own Markdown, and as HTML, PDF and Word beside it.
- Its own
- The record (chain.jsonl): blocks of events chained by SHA-256, laid out in full at the top of oauthor.js, which is free code anyone can read and run.
| Inside | What it holds |
|---|---|
chain.jsonl, reveal.json | The record, and the words of pastes, links and footnotes kept beside it under salted SHA-256s. |
timestamps/, manifest.json, manifest.sig, device.json | The timestamp tokens, every file's SHA-256, and the device key's signature over that list with its public half. |
document/, summary.pdf | The work, and the figures with a timeline. |
Reading it without OBSCURA. Unzip it with anything. /verify-authorship checks it, free; the work in document/ opens in any word processor or browser.
Written by pro-code/authorship-export.js; the build checks this note against it.
OBSCURA Sound: .osound
- What it is
- A zip, with the mimetype first and uncompressed, as an EPUB or an OpenDocument file has it.
- Standard inside
- Zip, JSON in UTF-8, and the audio as the files it came in as (an MP3 stays an MP3, a FLAC a FLAC), or as 32-bit float WAV for audio made in Sound: a recording, or the result of an effect.
- Its own
- The shape of the JSON: the tracks with their gain, pan, mute and solo; each clip's place on the timeline in samples, the stretch of its file it plays, its gain and its fades; the markers; and a noise profile if one was learned.
| Inside | What it holds |
|---|---|
mimetype | application/x-obscura-sound. |
project.json | The project: format is obscura-sound, rate the sample rate every position is counted in, tracks with their clips, markers in seconds, and sources, each naming its file under audio/. |
audio/ | Each recording, named by its id in project.json. |
Reading it without OBSCURA. Unzip it: every recording in audio/ plays in any audio player or editor. To have the mix itself in a standard file, export it to WAV, which any editor opens; project.json says where each clip goes if the edit is to be rebuilt elsewhere.
Written by pro-code/sound-store.js; the build checks this note against it.
PDF Studio: standard files only
- What it is
- PDF Studio has no file format of its own.
- Standard inside
- It writes PDF; comments as XFDF; form data as FDF, XFDF or JSON; measurements as CSV; Word, Excel and PowerPoint files; certificates as PKCS#12 (.p12).
- Its own
- Nothing.
Reading it without OBSCURA. Whatever reads PDF and those formats: Acrobat, a browser, LibreOffice, Microsoft Office.
Written by pro-code/pdfs-comments-ui.js; the build checks this note against it.
The Sovereign Suite: .sovereign (a backup)
- What it is
- Sealed one of two ways: with a passphrase, as a small JSON envelope, or to age public keys, as a standard age file.
- Standard inside
- JSON; base64; PBKDF2-SHA256 and AES-256-GCM; age (age-encryption.org/v1).
- Its own
- The names of the registers inside, which are the Suite's. Each record carries its SHA-256.
| Inside | What it holds |
|---|---|
format, method, kdf, cipher | sovereign-backup, passphrase, and how it was sealed, in words. |
sealed | Base64 of 16 bytes of salt, 12 of IV, and the AES-256-GCM ciphertext. The key is PBKDF2-SHA256 of the passphrase with that salt, 310,000 iterations, 32 bytes. |
Inside, once opened | JSON: sovereign-backup-payload, the workspace's name, when it was made, every record by name (items), and each record's SHA-256 (sha256). |
Reading it without OBSCURA. One sealed to age keys opens with the age program: age -d -i your-key.txt backup.sovereign. One sealed with a passphrase opens with this Python, which needs the cryptography package, and checks every record's hash as it goes:
import base64, hashlib, json, sys
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
envelope = json.load(open(sys.argv[1]))
assert envelope["format"] == "sovereign-backup" and envelope["method"] == "passphrase"
blob = base64.b64decode(envelope["sealed"])
salt, iv, sealed = blob[:16], blob[16:28], blob[28:]
key = hashlib.pbkdf2_hmac("sha256", sys.argv[2].encode(), salt, 310000, 32)
payload = json.loads(AESGCM(key).decrypt(iv, sealed, None))
for name, value in payload["items"].items():
assert hashlib.sha256(value.encode()).hexdigest() == payload["sha256"][name]
json.dump(payload, sys.stdout, indent=1)
Written by pro-code/sovereign-svc-data.js; the build checks this note against it.
What is not offered
- No escrow of the paid applications' code. There is no copy held by a third party, to be released if the business stops, and no offline package of the paid code a customer can keep past the date of their key.
- No written disaster recovery plan beyond this page and incident response. The site is static files and a few small routes on Cloudflare, rebuilt from the repository on every change; the records the server keeps are listed on Trust, and there is no separate backup of them beyond what Cloudflare keeps. A Team or Business admin can download the record of seat changes at any time from the seats page.
- No service level agreement. Status shows what the scheduled checks have recorded.
Questions about any of this: [email protected].