Skip to content
Draft
361 changes: 361 additions & 0 deletions docs/spec/monorepo-versioning.md

Large diffs are not rendered by default.

81 changes: 81 additions & 0 deletions docs/spec/namespace-manifest-v1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Namespace manifest identity v1

Status: shared codec implemented and tested, 2026-09-06. This is a content
identity contract, not a claim of publication, authorization, leases or FUSE
integration. Both repositories consume the same `namespace-v1.json` fixture;
the committed PowerShell 7 generator independently frames bytes and hashes with
.NET, without calling the Rust implementation.

## Binding

JSON has exactly `mount_path`, `source_snapshot`, `source_subpath` and `policy`.
The first is a canonical RepoPath; source_subpath is a canonical RelativePath
relative to the attested source scope. Their source-side composition must still
fit the 4096-byte absolute-path limit. Tree existence, source membership,
ancestor/descendant binding conflicts and release enforcement are publisher
checks, not proofs supplied by this structural codec.

Canonical bytes are ASCII `mega.namespace-binding.v1` plus NUL, followed by:

1. Mount path: u32 big-endian byte length, then UTF-8 bytes.
2. Full canonical SourceSnapshot bytes from source-snapshot-v1, framed by u32
big-endian byte length. This is not JSON and not merely the source UUID.
3. Source subpath: u32 big-endian byte length, then UTF-8 bytes.
4. Policy u8: 1 = `mutable`, 2 = `immutable_release`.

Policy is explicit and part of identity; it is never guessed from a numeric
directory name. **D2 is confirmed:** an explicitly marked release directory
cannot change content after its first publication; ordinary development
bindings may evolve. A codec that can encode both values does not itself enforce
this rule on writers.

## View

JSON has exactly `schema_version`, `instance_id`, `native`, `bindings_root`,
`overrides_root` and `materialization_policy`. Schema version must be integer 1.
Instance ID is a distinct non-nil canonical UUID type, not a source ID.
The native SourceSnapshot must have root scope `/`; the server must separately
attest that its source is the instance's native backend.

Canonical bytes are ASCII `mega.namespace-view.v1` plus NUL, followed by:

1. Schema version: u16 big-endian, exactly 1.
2. Instance UUID: u32 big-endian byte length, then canonical lowercase UUID text.
3. Full canonical native SourceSnapshot bytes, framed by u32 big-endian length.
4. Bindings root: 32 raw SHA-256 bytes, with no textual prefix.
5. Overrides presence: u8 0 for absent, or u8 1 followed by 32 raw digest bytes.
6. Materialization policy u8: 1 = `git_raw_v1`.

`git_raw_v1` identifies raw Git projection without implicit LFS hydration or
submodule expansion. It is not permission to traverse arbitrary external
symlinks. Overrides are representable in the codec; the reader must explicitly
reject that capability until the override route semantics are implemented.
The absent root is distinct from a present empty-index root.

No timestamp, actor, operation ID, publication sequence, parent view, floating
ref, lease or client generation is hashed into a view. A different commit with
the same tree changes provenance and therefore changes view_id. Re-publishing
identical content can reuse view_id while publication metadata remains separate.

## Strict decoding and cross-repository evidence

Every complete manifest is limited to 16384 bytes. Hash identity is `sha256:`
plus lowercase hex SHA-256 of the entire domain-separated canonical byte
sequence. Binary decoding rejects truncation, length overflow, trailing bytes,
unknown schema/policy/optional tags, invalid UTF-8 or paths and mismatched
domains. JSON rejects unknown fields and passes through the same structural
validation as constructors. Decoding bytes is not digest verification against a
requested ID; the content-store/read boundary must do that separately.

Golden view without overrides:
`sha256:3c8632afb308bf562973b3af517ae5d0a27c05651f3f7511f91e16d7ad8f1231`.

Golden mutable binding:
`sha256:adebe124b05761074c9460ed20426acf3023645e2bfa7e46b12239da68b14a88`.

Both repositories pass five codec tests: independent binary/JSON vectors,
identity changes with provenance/routing/instance/policy, JSON rejection,
all-prefix truncation and malformed tags/lengths, and maximum-length paths.
Mega uses SHA-2 and ScorpioFS uses ring, while the oracle uses .NET SHA-256.
This closes the shared manifest-identity subtask, not the full G01–G06/V01–V18
acceptance suite.
84 changes: 84 additions & 0 deletions docs/spec/source-snapshot-v1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Source snapshot v1 contract

Status: implemented identity/read foundations, 2026-09-06. This contract does not advertise a deployed snapshot capability. Namespace publication, leases, scope attestation coverage and FUSE integration remain separate gates in the versioning specs.

## JSON and validation

The source descriptor has exactly these five fields:

```json
{
"source_id": "11111111-1111-4111-8111-111111111111",
"scope_path": "/project/a",
"object_format": "sha1",
"commit_oid": "1111111111111111111111111111111111111111",
"root_tree_oid": "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
}

```

The hashes above illustrate structure, not a deployed source or a claimed commit/tree relationship.

- source_id is a non-nil, lowercase, hyphenated UUID persisted by Mega. Its server-side mapping includes instance, backend kind and repo ID. Paths are not source IDs; recreating a different logical source must not reuse an ID.
- scope_path is canonical absolute UTF-8. / is the root; other paths cannot end in / or contain empty, dot or parent components or NUL. Limit: 4096 UTF-8 bytes per protocol path, 255 per component. This protocol limit is not a guarantee that any host-local mountpoint prefix fits an OS path limit.
- Names retain case, Unicode composition, plus signs and literal backslashes. No Windows path normalization is applied. Non-UTF-8 names are unsupported in v1.
- object_format is sha1. Object IDs contain exactly 40 lowercase hexadecimal digits. Future algorithms require negotiation, not automatic acceptance.
- Unknown fields, invalid IDs and unknown algorithm tags fail deserialization. Structural validation does not prove scope, object membership or commit.tree: Mega must attest those relationships.

The source_ref selector requires a fully qualified refs/heads/... or refs/tags/... name. The source_commit selector accepts only a commit OID, never an arbitrary tree/tag OID. The compatibility parser in existing Mega browsing APIs still accepts an unqualified tag name; the new typed contract does not.

## Canonical source identity

Do not hash a JSON serialization. Canonical source bytes consist of:

1. The ASCII domain mega.source-snapshot.v1 followed by one NUL byte.
2. The five fields in this order: source_id, scope_path, object_format, commit_oid, root_tree_oid.
3. Each field is encoded as its unsigned 32-bit big-endian UTF-8 byte length followed immediately by those UTF-8 bytes. OIDs are lowercase hex text here, not raw 20-byte values.

source identity = sha256: followed by the lowercase SHA-256 hex digest of those bytes.

The same shared fixture is tested by both implementations. It includes an ASCII scope and a Unicode/plus-sign scope. The first vector's identity is sha256:6e3f8a7e41d3a9759bc05cbc1dab153ad27ba0e0ff494f7692392dbfd5a95451. Fixture bytes/digests were independently computed with .NET; Ceres uses RustCrypto SHA-256 and ScorpioFS uses ring.

This identity includes commit provenance. It is not namespace view_id, publication_seq, a lease, or a projection_key. A same-tree/different-commit pair has different source identities but may still share verified physical objects within an authorized domain.

## Immutable object boundary

ScorpioFS SourceReader owns a fixed SourceSnapshot and only asks ObjectBackend for (source, object kind, OID, root-relative source_path, byte limit). It exposes no mutable ref selector. A different version requires another reader; a caller cannot mutate the descriptor held by an existing reader.

source_path is a membership/authorization context, not a lookup through current routing: the server must walk from the descriptor's fixed root and verify the resulting kind/OID before returning an object. Root uses the empty relative path. This avoids a whole-repository reachability scan per request. Signed object tickets may optimize the same check later; arbitrary caller-supplied OIDs are never sufficient proof.

Backends must check current authorization and retention even on a global CAS hit, enforce limits during download, and return raw object payloads. The client verifies SHA-1 over Git's type + space + decimal length + NUL + payload. A file beginning with Git-like header bytes retains those bytes.

Tree traversal is relative to the source root. Prefix neighbors such as /project/ab do not match scope /project/a. A scope commit's tree is already rooted at the scope; the prefix is never applied twice. Tree names, entry modes, symlink targets, missing paths and failed object fetches remain distinct.

The initial reader is a bounded whole-object implementation: default limits are 16 MiB/tree and 64 MiB/blob. It returns an explicit size-limit error, never empty bytes, on oversized objects. This is not the final streaming/CAS/FUSE adapter or a claim that stat is metadata-only. Namespace routing, chunked large-object reads and controlled workspace generation changes are not completed by these tests.

## Mega source observations and scope proofs

SourceCatalog registers stable backend IDs and resolves typed selectors. A new import observation uses its registered root and a repo-scoped commit/tag resolution. A new native observation requires an exact scoped ref whose stored root agrees with commit.tree; it records native_ref_observed, not a claim that an older writer emitted a creation proof. Native projection derives a child by walking an already attested fixed root and preserves the base commit provenance.

Explicit native commits without a proof for the requested scope return SCOPE_UNKNOWN. A recorded descriptor can be resolved after refs or registry entries are removed; a reused path assigned to a different repo ID receives a different source ID. No current registry lookup is used to read an already attested source.

The catalog checks descriptor attestation and walks root-relative paths to bind object kind/OID to source membership. It strictly decodes UTF-8 trees and checks SHA-1 independent of git-internal's thread-local algorithm. It is an internal metadata service, not an authorization or retention grant. Public HTTP reads must add those checks, and no snapshot endpoint or capability is enabled by the catalog alone. Observing individual sources is not an atomic multi-source namespace publication. Existing commit metadata is trusted ingestion state; this foundation does not claim raw commit/tag payload re-verification or complete proof capture by every writer.

## Source object HTTP binding (client adapter implemented)

GET api/v1/sources/{source_id}/trees/{oid} or blobs/{oid}, relative to the configured server base URL, carries exactly five percent-encoded query fields: object_format, scope_path, commit_oid, root_tree_oid and source_path. The path supplies source_id and the expected object kind/OID; together these reconstruct the attested descriptor and fixed-root membership request. There is no ref/latest query.

Authorization: Bearer carries a current Mono access token; X-Mega-Snapshot-Lease carries the retention lease identifier. Neither is a query parameter or Debug field. The server must validate both and must not let the lease substitute for source/scope authorization. HTTP access logging should redact these headers and avoid logging private query paths.

A successful full object response is HTTP 200, Content-Type application/octet-stream, with raw bytes. The client does not follow redirects, accept partial/204 responses as full objects, or treat JSON/HTML login/error pages as objects. SourceReader verifies the returned Git hash. The adapter checks Content-Length and also bounds collection of streamed chunks when length is absent. 401/403 become Forbidden, 410 becomes Expired; other failures, including object/source 404, remain Unavailable rather than being misreported as an absent directory entry. A missing entry discovered in a verified tree is a separate PathNotFound result.

The adapter requires HTTPS except for loopback HTTP test/development servers, rejects base URLs containing userinfo/query/fragment, sets connect/request deadlines, and retains no reqwest URL-bearing error text. It does not acquire or renew leases, negotiate capabilities, authorize requests on behalf of Mega, or connect existing mounts automatically. Local Axum transport tests are not a deployed Mega end-to-end test. The server routes remain an implementation gate under the confirmed default-off policy.

## Verification

Run the relevant repository command:

```sh
cargo test -p ceres --lib snapshot --locked
cargo test --lib snapshot --locked

```

The ScorpioFS reader fixtures obtain their OIDs from git hash-object --stdin (without -w). Git must be installed, but these tests need no network, FUSE mount, mutable global Git configuration or existing repository objects.
Loading