Available

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 parentId removed.

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:

  1. ephemeral session override
  2. document configuration
  3. project configuration
  4. workspace configuration
  5. 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.