Published alongside IDOP 1.0. Defines the containers that hold and move IDOP documents.
IDOP Workspace Containers 1.0
This document is normative. RFC 2119 terms apply. It specifies the containers
around an IDOP package — workspaces, folders, projects, the .idopbundle
transport, and the Portable Items contract. It changes nothing about the
IDOP 1.0 package profile (or the pre-release drafts), and a Reader that implements none of
this remains conformant to them.
1. Model
| Concept | Is | May contain |
|---|---|---|
| Workspace | a top-level root | folders, projects, IDOP documents, regular files |
| Folder | an organisational container | folders, projects, IDOP documents, regular files |
| Project | a curated container of cooperating documents | IDOP documents, regular files, project metadata and non-secret shared configuration |
| IDOP document | an existing .idop package |
as its profile defines |
| Regular file | passive supporting content | bytes and safe metadata |
| IDOP Item | an application-defined portable object inside one document | see §5 |
A workspace is a root, not a decorative folder name. A project is a leaf container in this version: it MUST NOT contain a workspace, another project, or a general directory hierarchy, and a Reader MUST refuse a project marker whose member path names another marker file.
An IDOP Item tree is application-defined and is separate from the container tree. An item is not a filesystem node.
Every entity MUST have a stable, collision-resistant identity appropriate to its scope. Display names and paths are not identities.
2. Local representation
A marked workspace is a directory containing idop.workspace.json at its
root. A project is a directory containing idop.project.json at its root.
A directory with no marker that the user explicitly selects is a temporary workspace root for that session. The Reader MUST NOT modify the directory to make it one, and MUST NOT search parent directories for a marker: the selected directory is the boundary.
Markers MUST validate against IDOP-WORKSPACE-SCHEMA.json and
IDOP-PROJECT-SCHEMA.json. Unknown fields and duplicate keys are errors.
id MUST be a UUID; createdAt, when present, MUST be an RFC 3339 UTC
timestamp.
A marker MAY declare non-secret configuration and credentialAliases. A marker
MUST NOT contain a credential value, and a Reader MUST refuse a configuration key
whose name indicates secret material (SECRET, TOKEN, PASSWORD, PASSWD,
APIKEY, API_KEY, PRIVATE_KEY, CREDENTIAL, or any key ending _KEY) with
IDOP-WS-SECRET-001.
A project marker MAY set discovery. directory (the default) enumerates the
project directory; explicit uses members and nothing else, and MUST be
accompanied by a members list. Member paths are project-relative and MUST obey
the shared path grammar (§3).
Marker files are the mechanism, not content. A Reader SHOULD NOT present them as ordinary resources in a navigation tree.
Identifiers and versions
| Marker | format |
formatVersion |
|---|---|---|
idop.workspace.json |
https://idoplabs.com/ns/idop/workspace |
1.0 |
idop.project.json |
https://idoplabs.com/ns/idop/project |
1.0 |
idop.bundle.json |
https://idoplabs.com/ns/idop/bundle |
1.0 |
A Reader MAY also read the pre-release markers (urn:idop:workspace,
urn:idop:project, urn:idop:bundle with 0.1-draft) but MUST NOT accept a
1.0 identifier with a draft version or the reverse. A Producer writes 1.0.
A 1.0 credential alias is { "alias", "origins", "description"? }: it names the
HTTPS origins the aliased credential is meant for and never a service. (Draft
markers named a provider from a closed list; 1.0 carries no trade names.)
3. Paths
Container-relative paths follow the same grammar as package entry paths:
relative, UTF-8, /-separated, Unicode NFC, at most 240 bytes and 16 segments.
Absolute, UNC, drive and backslash paths, empty/./.. segments, NUL and
control characters, trailing dots or spaces, :, and Windows device names are
all forbidden. A Reader MUST refuse a path outside the selected root.
4. .idopbundle transport
A bundle is a hardened ZIP with this layout:
mimetype application/vnd.idop.bundle+zip first entry, Stored, exact
idop.bundle.json the bundle manifest
content/** the transported directory, byte for byte
The manifest MUST validate against IDOP-BUNDLE-SCHEMA.json. format is
https://idoplabs.com/ns/idop/bundle; formatVersion is 1.0. scopeType declares what is
transported: workspace, folder or project. A Reader meeting an unknown
scopeType MUST refuse the bundle and SHOULD name the scope it did not
understand.
The media type is submitted for registration with IANA (vendor tree), together
with the package type application/vnd.idop+zip.
entries MUST list every content/** entry with its byte length and its
lowercase hex SHA-256. The agreement MUST be checked in both directions:
- an entry the manifest names but the archive lacks is
IDOP-BUNDLE-INTEGRITY-001; - a digest or length mismatch is
IDOP-BUNDLE-INTEGRITY-002; - a duplicate manifest entry is
IDOP-BUNDLE-INTEGRITY-003; - an archive entry the manifest does not declare is
IDOP-BUNDLE-INTEGRITY-004.
The fourth is not optional. An undeclared extra entry is how content rides along unnoticed.
content/** MUST be verbatim. A file whose type the Reader does not understand
MUST survive a round trip byte for byte.
A bundle MUST NOT contain another bundle (IDOP-BUNDLE-PATH-003). Import
expansion depth is exactly one.
Bundle reading MUST apply the same ZIP hardening as package reading: two
independent structural views, the path grammar of §3, entry-count, per-entry,
aggregate and compression-ratio limits, special-file rejection, and duplicate and
Unicode/case-collision detection. The media type MUST be checked as soon as the
first entry is readable, so a package offered as a bundle is refused for what it
is (IDOP-PROFILE-002) rather than for a path rule.
Writing MUST be deterministic: canonical entry order, fixed ZIP timestamp and
permissions, Stored mimetype, and a caller-supplied identity and createdAt.
Producing a bundle twice from the same content MUST give identical bytes.
Limits
| Limit | Value |
|---|---|
| compressed bundle | 256 MiB |
| total uncompressed content | 512 MiB |
| entries | 4096 |
| single entry | 64 MiB |
| bundle or marker manifest | 512 KiB |
5. Portable Items contract, version 1
An application opts in by declaring extensions["idop.portable-items"] in its
package manifest. The declaration MUST validate against the declaration
definition of IDOP-PORTABLE-ITEMS-SCHEMA.json:
{ "contractVersion": "1",
"index": "items/index.json",
"itemPrefix": "items/data/",
"sharedDependencies": ["shared/glossary.json"] }
index and sharedDependencies are storage-relative paths; itemPrefix is a
storage-relative prefix ending in /. Neither the index nor a shared dependency
may live inside itemPrefix.
The contract requires the 1.0 profile (or the 0.2-draft). A 0.1-draft package declaring it
MUST be refused with IDOP-ITEMS-PROFILE-001.
The application maintains an index at index, validating against the index
definition. Each entry carries itemId, kind, title, and optionally
summary, parentId and dependencies.
A Reader MUST refuse an index with:
| Condition | Code |
|---|---|
duplicate itemId |
IDOP-ITEMS-DUPLICATE-001 |
parentId naming an item that is not present |
IDOP-ITEMS-PARENT-001 |
| a cycle in the parent chain, self-parenting included | IDOP-ITEMS-CYCLE-001 |
a dependency outside sharedDependencies |
IDOP-ITEMS-DEPENDENCY-001 |
| a dependency the package does not carry | IDOP-ITEMS-DEPENDENCY-002 |
| a dependency path that is not a valid storage path | IDOP-ITEMS-DECL-003 |
| more items than the limit (512) | IDOP-ITEMS-LIMIT-001 |
A refused index MUST disable the feature for that document. It MUST NOT prevent the document from opening: an index is the application’s business.
Derivation
The Reader — not the document — constructs the derived package. Listing and derivation MUST read the index and item state from the validated package and the storage overlay. Document code MUST NOT be consulted, so there is no window between the decision and the data.
A derived package MUST contain:
- every entry outside
storage/**, byte for byte; storage/<itemPrefix><itemId>/**for the selected item and its descendants;- each declared dependency those items reference;
- a rewritten index describing exactly the items that travelled, in the source
index’s order, with the selected item’s
parentIdremoved.
It MUST NOT contain any other item’s state, any credential, any Reader setting, any permission grant, or any project-level data.
It MUST receive a new document.id and a new revisionId, with
parentRevisionId null — it is a new document, not a revision. Its code/**
MUST be unchanged, so a permission grant bound to the code digest keeps its
meaning.
It MUST record provenance under extensions["idop.derived-from"]:
{ "contractVersion": "1", "sourceDocumentId": "…", "sourceRevisionId": "…",
"itemId": "…", "derivedAt": "…", "verified": false }
Provenance is not a signature. verified is false and a Reader MUST NOT
treat provenance as evidence of authorship.
The result MUST pass the full package validator before it is offered to a user, and the source document MUST be unchanged.
An export exceeding 512 storage entries or 8 MiB of storage is
IDOP-ITEMS-LIMIT-002. An unknown item is IDOP-ITEMS-NOT-FOUND-001. Asking a
non-participating document is IDOP-ITEMS-UNSUPPORTED-002.
6. Projects and cooperation
A Reader MAY enumerate a project’s resources and open the documents in it. A document MUST NOT be able to read another document’s package, storage, DOM, credentials or execution context.
Project membership MUST NOT transfer credential permission. A permission grant stays bound to application identity, code digest and declared capabilities.
Non-secret project configuration MUST live in the project marker and be resolved through the precedence in §7. It MUST NOT be merged into global Reader settings.
There is no project.* Runtime API in this version. The following names are
reserved and MUST NOT be implemented incompatibly:
project.documents.list, project.navigation.open,
project.sharedStorage.read, project.sharedStorage.write,
project.events.publish, project.events.subscribe.
A Reader MUST reject an unknown Runtime API method with IDOP-RUNTIME-METHOD.
A project MUST NOT contain scripts that execute with Reader trust.
7. Configuration precedence
Non-secret configuration resolves most-specific first:
- ephemeral session override
- document configuration
- project configuration
- workspace configuration
- account or device default
A Reader SHOULD surface the effective value and the scope it came from. Configuration MUST NOT carry secrets; a credential value is Reader state, and a container may reference an alias only.
8. Sessions and runtimes
A Reader MAY hold several validated document sessions open at once. In this version at most one untrusted runtime MAY execute at a time.
Switching away MUST tear down or suspend the runtime while retaining the validated package state and the unsaved overlay. Switching back MUST reconstruct the runtime deterministically from that state.
Each runtime MUST carry an unforgeable routing identity, and a Reader MUST drop messages from a session that is not the active one.
Dirty state, save target, revision identity, source errors and runtime messages MUST be per document. Closing a container with unsaved work MUST NOT discard it silently.
9. Regular files
A regular file is passive. A Reader MUST NOT execute it, and MUST NOT render
active content (HTML, XHTML, SVG, scripts, WebAssembly) in its own trusted
origin. Preview is an explicit allowlist; everything outside it MUST offer a
stated refusal and a download. A download of an unrecognised or active type
SHOULD use application/octet-stream.
Unknown regular files MUST be preserved through a bundle round trip.