docs(org): pre-stage A5 living-docs for merged core+server+CLI-admin (item-CRUD/extension TODO)
Pre-stages the A5 living-docs sweep for the already-merged A (relicario-core org module) + C (relicario-server pre-receive hook) + CLI admin/rotate/status-audit work, so the final A5 sweep (after Dev-B B9-B14 merges) is fast. Adds org sections to docs/FORMATS.md (org repo wire formats + wrapped-key blob layout), docs/CRYPTO.md (ECIES X25519 wrap/unwrap, no-Argon2id contrast, rotate re-encryption), docs/SECURITY.md (signature-verifying hook, owner-only elevation, audit vocabulary, honest limitations), DESIGN.md (org-master-key secrets row + server org mode + deps), core/cli ARCHITECTURE.md (org module + org_session), and an Unreleased CHANGELOG entry. B item-CRUD (org add/get/list/edit/rm/restore/purge + main.rs wiring) and extension parity are left as explicit TODO. STATUS/ROADMAP mark-shipped and extension/ARCHITECTURE are deferred to the full A5 (track not yet landed; Dev-D deferred). All cited code constants pinned with file:line per living-docs discipline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJo44YM3UbBjro2fG6NrKy
This commit is contained in:
@@ -71,6 +71,60 @@ An empty array (`[]`) puts the pre-receive hook in bootstrap mode (all pushes ac
|
||||
|
||||
Commits by `public_key` at or after `revoked_at` (Unix seconds) are rejected by the pre-receive hook. Commits before `revoked_at` remain valid (they were authorized at the time).
|
||||
|
||||
## Org vault repo formats
|
||||
|
||||
The org vault is a **separate git repository** alongside the personal vault. It is not nested inside `.relicario/`. Its layout:
|
||||
|
||||
```
|
||||
org.json # OrgMeta (schema_version, org_id, display_name, created_at)
|
||||
members.json # PUBLIC/unencrypted member directory
|
||||
collections.json # collection definitions
|
||||
keys/<member-id>.enc # org master key wrapped to that member's device key
|
||||
manifest.enc # OrgManifest (schema_version 1, per-member-filtered)
|
||||
items/<collection-slug>/<item-id>.enc # collection-scoped item blobs
|
||||
```
|
||||
|
||||
### `org.json` — OrgMeta
|
||||
|
||||
Unencrypted JSON (`OrgMeta`, `org.rs:164`). `schema_version: 1` (`org.rs:174`). Fields: `schema_version`, `org_id`, `display_name`, `created_at` (Unix seconds).
|
||||
|
||||
### `members.json` — OrgMembers
|
||||
|
||||
Unencrypted JSON array of `OrgMember` records (`org.rs:72`); container type `OrgMembers` carries `schema_version: 1` (`org.rs:93`). Per-member fields: `member_id` (16 lowercase hex chars), `display_name`, `role` (one of `owner | admin | member`), `ed25519_pubkey` (OpenSSH wire string), `collections` (array of granted slug strings), `added_at`, `added_by`. Roles are not secrets — authorization to read this file is not required to verify signatures.
|
||||
|
||||
### `collections.json` — OrgCollections
|
||||
|
||||
Unencrypted JSON; `schema_version: 1` (`org.rs:138`). Contains a list of `CollectionDef` records (`org.rs:123`). Validation (`org.rs:145`) rejects slugs that are empty, contain `/`, or equal `.`.
|
||||
|
||||
### `keys/<member-id>.enc` — wrapped org master key
|
||||
|
||||
Binary blob; NOT a standard `.enc` blob. Layout (`org.rs:264`):
|
||||
|
||||
```
|
||||
┌──────────────────────────┬─────────┬────────┬──────────────────────┐
|
||||
│ ephemeral_x25519_pubkey │ version │ nonce │ ciphertext + tag │
|
||||
│ 32 bytes │ 1 byte │24 bytes│ N + 16 bytes │
|
||||
└──────────────────────────┴─────────┴────────┴──────────────────────┘
|
||||
```
|
||||
|
||||
- The wrapping key is `SHA-256(dh_shared || ephemeral_pubkey || recipient_pubkey)` (`org.rs:278–281`), held in `Zeroizing<Vec<u8>>`.
|
||||
- The inner AEAD (`version || nonce || ciphertext+tag`) is produced by `crate::crypto::encrypt` — the same XChaCha20-Poly1305 framing used for personal `.enc` blobs (see **Encrypted blob** above). `VERSION_BYTE = 0x02` applies here too.
|
||||
- The X25519 private scalar is derived from the device ed25519 seed via `SHA-512(seed)[..32]` with RFC 7748 clamping (`org.rs:242`). Argon2id is **not** involved — the wrapping key is derived entirely from the X25519 DH exchange.
|
||||
|
||||
### `manifest.enc` — OrgManifest
|
||||
|
||||
Encrypted with the org master key using `crypto::encrypt` (standard `.enc` framing). Decrypts to `OrgManifest` JSON (`org.rs:199`); `schema_version: 1` (`org.rs:206`). Each `OrgManifestEntry` (`org.rs:185`) carries: `id`, `type`, `title`, `tags`, `modified`, `trashed_at`, and a `collection` slug field. The `collection` field distinguishes this type from `ManifestEntry` in the personal vault.
|
||||
|
||||
Contrast with the personal vault manifest: `Manifest` uses `MANIFEST_SCHEMA_VERSION = 2` (`manifest.rs:12`) and `ManifestEntry` has no `collection` field. The two types are distinct and do not share a schema.
|
||||
|
||||
### `items/<collection-slug>/<item-id>.enc`
|
||||
|
||||
Standard `.enc` blob (see **Encrypted blob** above), encrypted under the org master key. The blob itself does **not** name its collection — the directory path segment carries the slug. This allows the pre-receive hook (`relicario-server`) to authorize a write by path segment without decrypting the blob.
|
||||
|
||||
**TODO (pending Dev-B B9–B14):** CLI commands for creating, reading, editing, and deleting org items (`org add` / `get` / `list` / `edit` / `rm` / `restore` / `purge`) are not yet wired in `main.rs`.
|
||||
|
||||
**TODO (extension follow-up):** extension UI for browsing and editing org vault items.
|
||||
|
||||
## Item IDs and Field IDs
|
||||
|
||||
| Kind | Length | Entropy | Source |
|
||||
|
||||
Reference in New Issue
Block a user