Skip to content

Workspace hierarchy delivery

Status: Source candidate, verified in code. The seven container.* operations (storage migration 35, workspace-containers) and the four project.create.* / project.rename.* operations (migration 36, project-metadata-previews) exist in protocol, SDK, Node and CLI. CLI help labels all eleven commands source_implemented_candidate; the registry's agent exposure table (LOCAL_NODE_METHOD_EXPOSURE) marks every mutation human-only. Of these, the Desktop renderer allowlist admits only container.list, container.show and container.index.list (the cockpit loads its Workspace and Project index through container.index.list pages); there is no create or rename UI. Planned: acceptance closure (R0), the naming migration (R1b), repository-free execution (R2), Workspace-owned Sessions (R3) and client create and rename flows (R9), as scheduled in the roadmap. Acceptance stories live in portfolio stories and dogfood suites.

Identity model

  • Workspace (organization) is the container aggregate with container_* IDs (maia.container/v1).
  • Execution Environment keeps the existing execution workspace.* operations and workspace_* IDs, which Sessions, receipts and events reference. It is never renamed in place or rewritten into an organizational Workspace.
  • Browsing Workspaces never calls execution workspace.select and never retargets running Sessions.
  • The later rename (container.* → workspace.*, execution workspace.* → environment.*) follows the naming contract and R1b. It does not repeat migration 35 or rewrite IDs (decision).

Container operations

container.list, container.show, container.index.list, container.create.preview, container.create.confirm, container.rename.preview and container.rename.confirm.

  • Create and rename need a confirmation bound to actor, normalized input, revision, expiry and target. Node owns proofs, validation, compare-and-set, idempotency and receipts.
  • Reads are bounded: limit 1–100, optional afterCursor, optional nextCursor. Duplicate names are allowed; names are not identities. Rename changes only the container label, never a Project or a filesystem path.
  • container.index.list reads every Workspace with its Projects (ID, name, revision and the root path of the Project's first repository, absent for a metadata-only Project) and each Workspace's Project count, in pages of up to 500 rows: a row is a Project, or a Workspace with none. A Workspace a page cuts continues first on the next page, so a client merges entries by container ID. One indexed query serves a page (a range scan of containers and projects_container_sequence, stopped at limit + 1 rows), next to aggregate reads for the cursor snapshot; the cursor is signed per actor and snapshot of Workspaces, Projects and repositories, so any of them changing between pages fails with storage_conflict and the Desktop cockpit restarts its read up to twice (decision).
  • project.register and session.create stay wire-compatible. Registration creates its own container (container_ plus the Project ID suffix) in the same transaction, through the shared Project insertion path.

Preserving migration rules

Migration 35 (services/node/src/storage/migrations/v030-v039/v035.ts) is the reference. Any later hierarchy migration, including R1b's, follows the same rules:

  • One additive, checksummed migration. Migration 35 gave each existing Project one container with a deterministic ID and the Project's name; unrelated Projects are never merged, and an empty database stays empty.
  • Preserve Project, repository, checkout, Execution Environment and Session IDs, historical receipt and event payloads and Git filesystem identities. Do not rebuild execution tables or change runtime kinds.
  • Enforce new relationships with SQLite-safe additive constraints. Migration 35 uses triggers instead of a table rebuild or disabled foreign keys, and a guard table that makes malformed or unresolved historical identities fail closed and roll the migration back.
  • Test on disposable real SQLite: empty initialization, populated upgrade, identity and payload preservation, rollback, checksum mismatch, compatible registration, proof expiry and mismatch, conflicting command reuse, replay, stale rename and cancelled preview.
  • No live profile migration until these tests and independent review pass and a backup and recovery procedure exists.

Project metadata protocol/SDK handoff (candidate)

Implemented in protocol, SDK, Node (migration 36) and CLI (maia project create and maia project rename, each two-phase: --input-stdin previews and --confirm-stdin confirms). Evidence: services/node/src/project-workspace/project-workspace.integration.test.ts, services/node/src/project-workspace/storage/project-metadata-store.test.ts and services/node/src/project-workspace/project-workspace.test.ts. R0 must still close acceptance (current artifact digest, parity and independent review) before release. The operations are human-only; schema registration is not authorization. Schemas: packages/protocol/src/project-workspace/effect-schema.ts.

Inputs

  • Create preview: commandId, existing containerId, positive expectedContainerRevision and name (Project name rules; duplicate labels allowed). The caller never chooses the Project ID.
  • Rename preview: commandId, containerId, projectId, positive Project expectedRevision and name.
  • Confirm: the preview input plus inputDigest (sha256: and 64 lowercase hex digits) and the Node-issued 43-character URL-safe proof.

Results

  • Preview: the normalized input, method, Node-derived actorId, issuedAt strictly before expiresAt, the digest and the proof. Create also returns the Node-reserved projectId.
  • Confirm: actorId, commandId, containerId, inputDigest, delivery (new or duplicate), the unchanged Project shape and a receipt whose actor, command, method, Project target, revision and timestamp match it. Create adds expectedContainerRevision and containerRevision (exactly expected + 1), with the Project at revision 1. Rename adds expectedRevision and advances the Project revision by exactly one. No confirmation returns a proof. The Project record gains no container, repository, directory or readiness field.
  • Stored proofs bind actor, method, normalized input, command, Project, container, revision and the issue and expiry times. Schemas check syntax and relations only; Node authenticates.

One Node writer

  • project.register and metadata create share one transactional Project insertion path (insertProjectRow in services/node/src/project-workspace/storage/store.ts). Metadata create inserts only the Project row and its membership: no repository, checkout, Execution Environment, path, Session or focus change.
  • Create validates the container, compare-and-sets and advances its revision once, inserts the reserved Project and stores the receipt in one transaction. Rename checks membership, compare-and-sets only the Project revision and changes only label, revision and update time.
  • Both confirmations reconcile an existing receipt before rejecting a consumed or expired proof. An exact retry returns the original result, even after later renames; a changed binding fails with storage_conflict. A missing Project or container returns resource_not_found.
  • container.show pages membership; its limit of 100 is a page bound, not a capacity. The reader fetches limit + 1 rows and signs cursors per actor, method, container and snapshot. Tests cover pages beyond 100 Projects and cursor invalidation after insert or rename.
  • A metadata-only Project reads through project.show with no repositories and is not Session-ready. Repository-free execution needs R2's directory identity, authorization and containment contract; a missing repository never authorizes an ambient working directory.

Imported from Maia OS docs at 3dc3e07