English · 中文
Mega2 is the second-generation Mega engine built for Agents. Its core capabilities are the Monorepo engine and optional Agent Session Capture. The recommended Agent workflow combines Mega2 with ScorpioFS for local filesystem mounts and Libra for version-control workflows. This guide covers everyday Git, HTTP API, large-file, optional-service, and CLI operations. For deployment instructions, see the Deployment Guide; the commented config.toml is the authoritative list of configuration keys.
Scope: the open-source edition supports only trunk / storage-only mode. It has no Web UI or Change List (CL). Use Libra's
libra mega2 browserfor interactive browsing and directory or tag operations (see §6).
Repository behavior depends on the path you use:
- Monorepo paths (the root path and paths outside
import_dir) expose only the public branchmain. Git-client tag writes and pushes to other branches are rejected. Create, list, and delete tags through the HTTP API (§4.2) or Libra'slibra mega2 browsercommand (§6). - ImportRepo paths under
[monorepo].import_dir(default/third-party) follow ordinary Git semantics: they allow multiple branches and Git-client tag operations, accept later pushes, and can be removed (§2.6). Use them when migrating or hosting third-party repositories. - An old ImportRepo's
refs/libra/tracesis hidden from ordinary code refs and is never selected as the codeHEAD; a repository with only that legacy ref has an unborn codeHEAD. If a code ref descends from a legacy traces commit, ordinary reads isolate that ref while clean refs remain discoverable; an explicit request for its commit returns 409trace_history_in_code. A maintainer with code push rights can explicitly delete or reset the polluted ref through receive-pack. Only callers that pass the push permission check see that ref in receive discovery; standard Git clients cannot discover the hidden legacy traces ref's old value for updates. Ordinary code clone/fetch builds the requested closure in the current authorized code view, accounting for legal haves and negotiated shallow/filter options. Traces-only objects are not included merely because they share storage; content shared with code may remain in its legitimate closure. The traces protocol defines the exact object and tag boundaries. The legacy view returns 413response_budgetwhen its published code-ref/tag, commit, or authorized-object limits are exceeded, and 503query_timeoutwhen the read budget expires or a database read fails. For ImportRepos without a legacy traces ref, ordinary code advertisement uses a limited ref read in place of the previous read, while fetch authorisation adds a ref read. At 4098 refs or more, a named lookup and a full ref read may also be needed. Those read views have a 10-second budget and return 503query_timeouton timeout or database read failure. A database read failure in receive advertisement also returns 503. A missing ancestor or non-commit branch tip rejects the repository with 409object_integrity; repair the data before retrying. - The shipped storage-only service has no Web UI or Change List (CL). Its OpenAPI document omits CL, issue, reviewer, and user routes.
- On Monorepo paths, Git pushes and product API writes share the same write queue and path-tip authority; edit/save inside a live ImportRepo writes that repository directly, see §4.1. The architecture guide describes the write path.
Git smart HTTP (info/refs, git-upload-pack, git-receive-pack) is mounted under the repository path and supports sub-path clones. See the Deployment Guide for local Compose endpoints:
git clone http://127.0.0.1:9000/project
git fetch && git reset --hard origin/main # client alignment after a trunk pushPush behavior in trunk mode (Monorepo paths; for ImportRepos see §2.6):
- Push to
refs/heads/main; pushes to other branches and Git-client tag writes are rejected on Monorepo paths. - A single-commit push lands unchanged. A push containing multiple commits is squashed into one commit on
main. Afterward, rungit fetch && git reset --hard origin/main; otherwise, the next push may be rejected as non-fast-forward. - Push to a repository subpath. Pushing from the root path
/is unsupported in storage-only mode.
The protocol endpoints and write surfaces are listed in the Architecture Guide.
Storage-only does not expose SSH receive-pack for ordinary code repositories (git.ssh_receive_pack=false remains mandatory; omitting it refuses startup). Ordinary code SSH supports clone, fetch, and pull. With traces enabled, a TraceRepo has a limited command-admission exception only after credential binding, owner, and project-write checks, as described in §5.3; SSH receive-pack and upload-pack transfer scoped traces over the same authorized data model. See the Deployment Guide for ordinary-code authentication modes.
SSH public-key authentication checks that the declared algorithm matches the actual signature algorithm. For example, a request declaring rsa-sha2-512 with a SHA-1 signature is rejected; matching RSA-SHA2-512 authentication remains supported. Existing standard OpenSSH key storage, reload, and public-key fingerprint interfaces remain unchanged. This dependency update does not enable SSH agent service or forwarding. See the protocol document for upstream security fixes and key compatibility.
Ordinary-code write surfaces (Git receive-pack, LFS batch/lock writes, product API writes) share git.push_auth:
token: HTTP Basic takes only the password (= the token secret); the username is ignored.pathsprefixes authorize on component boundaries.none: credential-less writes, only for controlled networks (loopback / Unix socket / intranet front); the HTTP ImportRepo removal endpoint is the exception and answers 403 to every valid request undernone(see §2.6).
See the Deployment Guide for token setup, credential injection, and security guidance; this guide does not reproduce token values. Traces separately checks a verified credential, principal binding, and target authorization; push_auth=none does not remove those checks.
[monorepo].object_format defaults to sha1, which works with standard Git clients. The sha256 and blake3 formats are Libra extensions and require the Libra client; standard Git interoperability is not supported for those formats. Configuration options are listed in config.toml.
This section is the single authoritative description of creating a new path in the monorepo; the Quick Start, the README and the initialization manual link here.
Allowed roots. The first-level directories listed in [monorepo].root_dirs (values in config/config.toml, shape rules in the Configuration Guide) are the allow-list for creating paths: a new path can only be created under one of them; .view and .filter are exact-case reserved names and cannot be first-level directories. Paths under import_dir (default /third-party) are ImportRepos (see §1); a Git push creates them directly and this section does not apply; §2.6 covers their updates and removal. Adding a first-level root requires a config change and a restart; the new root is then still missing from the root tree, and only a push that adds commits on top of existing history can create it, because provisioning and product writes never land at /.
First push to a new path. If the path already exists (it was pushed, provisioned or written through the API), push as described in §2.1. If it does not exist yet, trunk mode handles the push as follows; both path-policy rejections in the table stay the same on a verbatim retry:
| Case | Result | Next step |
|---|---|---|
| The path is not under any allowed root | Rejected with MONO_PATH_NOT_ALLOWED, listing the allowed roots |
Use a path under one of the roots; for a new root see "Allowed roots" above |
The path is under an allowed root and the pushed history starts from nothing (e.g. the first commit after git init), or is another path's history pushed unchanged |
Rejected with MONO_PATH_UNINITIALIZED, naming the provisioning command |
Provision the path as below, clone it, commit on top and push; for a new root that is not in the root tree yet see "Allowed roots" above |
| The path is under an allowed root and the push adds at least one commit on top of a commit the server already has (e.g. committing in a clone of another path) | Accepted and created | After the push, run git fetch && git reset --hard origin/main to align |
In the second and third cases the new path advertises no refs, so Git sends the whole source history: if it carries more than [monorepo].max_push_commits (default 250) commits, the push is rejected with that limit (no path-policy code; a verbatim retry may show a different text). Provision the path instead under an existing root; a root added after initialization cannot be provisioned, so clone a path with a short history, commit on top and push that to the new root.
Provisioning a path. Provisioning only creates the directory (one .gitkeep commit) and is safe to repeat:
- CLI:
mega2 path provision --server <URL> <PATH>. It reads the push token only from the environment variableMEGA2_TOKEN(omit it underpush_auth=none) and reads no config file. On success it printscreated <path> (<commit>)oralready exists <path>and exits 0; when the server refuses, it writes the error text to stderr and exits 1. - HTTP:
POST /api/v1/path/provisionwith body{"path": "<PATH>"}; authentication, status codes and the response shape are in the runtime OpenAPI (§4.4) and in the contract pagerefactoring/directory-entry-api.md(Chinese).
After provisioning, git clone <URL><PATH>, commit on top and push. Product writes (§4.1) under an allowed root that is already in the root tree create the path themselves and need no provisioning.
Error codes. Path-policy error texts start with a stable code, so clients can branch on the code before the colon; the exact text format, HTTP status and where each code occurs are in the error model (Chinese). The four codes and what to do:
MONO_PATH_NOT_ALLOWED: use a path under an allowed root (provisioning, and product writes where no ImportRepo is live, also return it for paths underimport_dir).MONO_PATH_UNINITIALIZED: provision the path first.MONO_PATH_INVALID: use a canonical path (absolute, no./.., repeated or trailing/); a path beginning with.viewor.filteralso receives this code from provisioning, product writes, and trunk pushes that create the path.MONO_PATH_CONFLICT: a component of the path is a file; choose another path (returned only by provisioning, through the API or the CLI).
This section is the authoritative description of how ImportRepos are updated, removed and retained, including the operator CLI mega2 import-repo remove. ImportRepos live under [monorepo].import_dir (default /third-party); the initialization manual explains how import_dir classifies paths.
Creation and later pushes. A push to a path under import_dir that does not exist yet creates an ImportRepo and mounts it in the Monorepo root tree. The path must lie strictly below import_dir: the server rejects a push to import_dir itself with a 400 IMPORT_REPO_PATH_INVALID, which the Git client shows only as The requested URL returned error: 400. From then on the ImportRepo is an ordinary Git repository that you can keep updating: creating, updating and deleting branches and tags, and --force non-fast-forward updates, are all accepted. Later pushes only update refs and add no commit to the Monorepo root tree, so the squash and the git fetch && git reset --hard origin/main alignment step of §2.1 do not apply to ImportRepos.
Concurrency and failure granularity. Every ref update in a push is conditional on the value the server advertised: if another push or API write changed the ref after the advertisement, that update is rejected with IMPORT_REPO_STALE_REF (more often, the Git client rejects the push with fetch first before sending it): get the ImportRepo's latest refs (for example git fetch mega2), merge or rebase, and push again, or use --force when you mean to overwrite; a conflicting tag has to be resolved on its own. The product API's edit/save writes the default branch directly without this check and can overwrite a concurrent push (§4.1). Within one push, tags are written one by one and succeed or fail independently; all branches are written as one batch, and if the batch fails every branch reports the same reason and none of them moves. The push report always starts with unpack ok; the server does not support git push --atomic. The full text, HTTP status and occurrences of each error code are in the "ImportRepoError" section of the error model (Chinese).
Nested ImportRepos. Pushing a parent path first and then a child path below it works, and the parent can still be updated afterwards (except when the parent is a legacy mount from before the upgrade with no mount-provenance record, see "Compatibility" below). In the reverse order, when a child already exists, the parent's branches are rejected with IMPORT_REPO_PATH_OCCUPIED. Children must be removed before their parent; otherwise removal is rejected with IMPORT_REPO_HAS_CHILDREN.
Removal. Removal detaches the ImportRepo from the service: it deletes the repository record and its refs, removes the mount this repository owns from the Monorepo root tree (with a root commit Remove ImportRepo <P>; mount content that is not its own stays, see "Compatibility" below), and then sweeps the repository's commit, tree, blob and tag metadata in batches. After the detach, clone and fetch of the path return 404. For a push still in progress when the detach commits, the refs it has yet to write are rejected with IMPORT_REPO_REMOVED and its branches do not land (tags are written one by one; a tag written before the detach belongs to the old repository and is swept with it); a push that starts after the detach — even while the sweep is unfinished — imports the path as a new repository. The detach transaction also deactivates an active trace target whose path and backing import repository match this ImportRepo, and records a binding generation one higher than before. A new ImportRepo at the same path gets a new target UUID. Resuming an older cleanup does not change that new target. Product create, delete, and move under an import directory still return 409 and do not create a target. There are two entry points that use the same cleanup mechanism:
- HTTP
POST /api/v1/import-repo/remove(§4.6): requiresgit.push_auth = "token"and a token that covers the path (§2.3); deployments withpush_auth = "none"get 403 for every valid removal request (a malformed body or path is answered first with 400 / 422). Request fields, outcomes (removed/pending/absent), the continuation protocol and error codes are in the "ImportRepo 叶子清理" section of the contract pagerefactoring/directory-entry-api.md(Chinese). - The operator CLI
mega2 import-repo remove: runs on the server with the service's own config, for deployments such aspush_auth = "none"that cannot use HTTP removal; being able to read the service's config and the database credentials in it is the authorization.
For both entry points absent means the path has no unfinished cleanup left, and so does the CLI's removed (a run drains the path's other cleanups before it finishes); the HTTP removed only guarantees that the cleanup the request refers to is complete, and older unfinished cleanups of the same path may remain for later requests (see the contract page).
Operator CLI. On a Compose stack, run it inside the mega2 container (the image sets MEGA_CONFIG; use the same -f / -p as when the stack was started). On a bare-metal deployment, use the service's config file, --profile / MEGA_PROFILE and MEGA_* environment variables (for example a database URL injected through the environment), and put --config before import-repo:
docker compose -f <compose file> [-p <project>] exec -T -e MEGA_LOG__PRINT_STD=false mega2 mega2 import-repo remove --path /third-party/<repo> --yes
mega2 --config /etc/mega2/config.toml import-repo remove --path /third-party/<repo> --yes- Prerequisites: version 0.40.11 or later; the service is running (it reclaims write-queue rows an interrupted run leaves behind); the same image or binary as the service — a binary whose schema does not match the database is refused, and the command never migrates the database. Only storage-only deployments are accepted (
git.push_authistokenornone). The config must be named with--configorMEGA_CONFIG; no default config is generated. For this run,redis.urland the S3 credentials must be literal values (vault://references are refused; override them withMEGA_REDIS__URL,MEGA_OBJECT_STORAGE__S3__ACCESS_KEY_IDandMEGA_OBJECT_STORAGE__S3__SECRET_ACCESS_KEY); the command never opens the vault. Do not setMEGA_ID_GENERATOR_WORKER_IDfor this run: with it set, the CLI no longer claims a worker lease from Redis and may use the same worker id as the service. Without--yes, nothing is done. - Output: the last line of stdout is the outcome line, with control characters in the path escaped; when logs go to stdout (
MEGA_LOG__PRINT_STD=true), the lines before it are logs. With exit codes 1 and 2 there is no outcome line and stderr gives the reason; with exit codes 3 and 130, stdout has the outcome line and stderr explains it. - Ctrl-C: the 130 rows below apply when the CLI runs in the foreground directly. Under
docker compose exec -T, Ctrl-C only ends the Compose client (which exits 130 with no outcome line) while the command in the container keeps running to the end: do not rerun right away; confirm it has finished as described below, then check the ledger.
| Outcome line / situation | Exit code | Meaning and next step |
|---|---|---|
removed <P> (repo_id=R, cleanup_id=C) |
0 | Detached and fully swept. When the path has no live repository and only unfinished cleanups remain, R and C can refer to an earlier removed repository |
absent <P> |
0 | This path has neither a live repository nor an unfinished cleanup. --path matches the stored repository path exactly as given: it does not strip .git (a Git URL does) and does not correct spelling, so before running, look it up with the psql form under "Checking the ledger" below, SELECT id, repo_path FROM git_repo WHERE repo_path = '<P>', and confirm the path and repository id |
pending <P> (repo_id=R, cleanup_id=C) |
3 | Detached, sweep not finished (--max-rounds reached, 1000 rounds by default, or a failure after the detach); continue with --cleanup-id C |
interrupted <P> (repo_id=R, cleanup_id=C) |
130 | Interrupted with Ctrl-C after the detach committed; continue with --cleanup-id C |
interrupted <P> |
130 | Interrupted with Ctrl-C before a cleanup was recorded; the repository is unchanged, rerun the same command; if stderr says the ledger could not be read, see below |
stderr gives the error (for example <CODE>: …, import-repo remove: …, Other error: …, Database error: …) |
1 | No cleanup was recorded by this run: act on the error code (for example, remove the children first for IMPORT_REPO_HAS_CHILDREN) or fix the prerequisite and rerun; if the ledger could not be read, see below |
Usage error such as a missing --yes |
2 | Nothing was done |
-
Continuing:
--cleanup-id <C>(with the same--path) only continues sweeping that ledger row and never detaches a live repository, so it is safe even after the path was pushed again; rerunning without--cleanup-iddetaches whatever repository is live at that moment, including one imported in the meantime. Once the primary cleanup is complete, a run also sweeps the path's other unfinished cleanups from the ledger (with--cleanup-id, possibly newer rows too), again without detaching any repository; a run that endspendingmay leave those rows untouched. -
Ledger could not be read: after a failure or an interrupt, the CLI reads the cleanup ledger to print a continuation handle; if stderr says the ledger could not be read (exit 1, or 130 with an outcome line without ids), the detach may have committed, so check the ledger as described next before continuing or rerunning.
-
Checking the ledger: if the process was killed (SIGTERM, SIGHUP and SIGKILL are not handled), crashed, or got Ctrl-C under
exec -T, first confirm the command has finished (on a Compose stack,docker compose -f <compose file> [-p <project>] top mega2shows whethermega2 import-repo removeis still running in the container), then query the path's cleanup ledger (the evaluation stack's database user and name are bothmega2; other deployments use the database from the service's config):docker compose -f <compose file> [-p <project>] exec -T postgres psql -U mega2 -d mega2 -c "SELECT id, repo_id, state FROM import_repo_cleanups WHERE path = '/third-party/<repo>' ORDER BY id"
Continue each row whose
stateisdetachedwith--cleanup-id <id>; nodetachedrow means no cleanup is unfinished. If the path still clones, it may be the original repository (the detach never committed) or a repository imported again in the meantime; check first (for example, its latest commits) before deciding to rerun, since a rerun detaches whichever repository is live.
Retention. Removal deletes metadata: the repository record and refs, the mount it owns in the Monorepo root tree, and the repository's commit, tree, blob and tag rows (the latter are swept in rounds, so a large repository can take several). Blob bytes in object storage are kept; the server has no garbage collection. The cleanup ledger import_repo_cleanups and the audit log (kind = import_repo.remove) are kept permanently and record the requester: the token name for HTTP, operator-cli for the CLI. Removal cannot be undone; pushing the same path again imports it as a new repository (a new repo_id). Known limitations are listed in the "ImportRepo 叶子清理" section of the contract page (Chinese).
Compatibility. Existing ImportRepos accept later pushes without any data migration: a legacy mount whose mount point holds only .gitkeep and whose repository has at least one branch gets its mount-provenance record filled in by the next push that creates or updates a branch. Two other legacy shapes reject pushes that create or update a branch with IMPORT_REPO_PATH_OCCUPIED (tag pushes and branch-delete-only pushes are not affected):
- A parent mounted before the upgrade that already has a nested child ImportRepo, in this order: (1) back up (
git clone --mirror) and remove the children; (2) push a branch create or update to the parent, which records its mount (with nothing new to push, push a temporary branch and then delete it); (3) only then push the children back. Pushing a child back before step 2 gets the parent rejected again. - A legacy mount without any branch: removal deletes only its records and leaves the placeholder directory in the root tree, so the path still cannot be imported afterwards; use another path. Until a legacy mount has its provenance record, do not delete all of its branches with a delete-only push, or it ends up in this shape.
The cleanup ledger table is created by the automatic database migration on upgrade; from 0.40.4, upgrades also normalize existing ImportRepo alias paths, see section 9.2 of deploy-trunk.md (Chinese).
For enablement and registration, see the [views] entry in the Configuration Guide and the views operations section of the Deployment Guide. HTTP and SSH can clone and fetch /.filter/<filter_id>.git, /.view/<name>.git, and /.view/<name>@<version>.git. The unversioned named URL is for human browsing; agents and automation must use @version. Views are not a read security boundary; read authorization is the same as for /<path>.git.
View history is projected from the repository beginning. Commits have different hashes from /<path>.git even when file contents match, so do not mix these URLs in one remote. Creating a new version replaces the history served by the unversioned named URL. Existing clones may see a non-fast-forward fetch and should be cloned again.
View URLs are read-only: HTTP push returns 403; SSH push returns one line, ERR view URLs are read-only. Over HTTP, --depth with v0 or v2, and --filter with v2, return 400 and git reports RPC failed; HTTP 400. V0 does not advertise filter, so git ignores --filter and performs a full clone. SSH rejects depth and v2 filter too, but currently writes plain error: … text, so git may only report a protocol error.
When a view is not ready, HTTP returns 503 with Retry-After; SSH returns ERR view <filter_id> unavailable: <reason> and exits with code 75, while git stderr contains remote error: view <filter_id> unavailable. Retry later.
- Git LFS (standard): endpoints
/info/lfsand/api/v1/lfswork with standardgit-lfsclients. LFS writes use the samegit.push_authsetting as Git receive-pack; see the Deployment Guide for authentication modes.
The base URL is the HTTP service listen address. Write endpoints authenticate via git.push_auth: missing/bad credentials get 401, path-scope violations get 403 (same as the Git write surface).
POST /api/v1/create-entry, POST /api/v1/delete-entry, POST /api/v1/move-entry, and POST /api/v1/edit/save write files and directories. On Monorepo paths, the path tip advances through the same write queue used by git push. Product writes inside a live ImportRepo are handled by that repository: edit/save writes the default branch directly, without the write queue and without the ref check a push makes; create, delete, and move are not supported and return 409. A successful response carries cl_link: null (no CL is created), and a same-stack git clone or git pull can read the committed content immediately. Index-backed browse results may take a short time to catch up after a write. The runtime OpenAPI document describes request fields and response schemas.
Monorepo paths forbid Git-client tag pushes, so tags are managed through this entry point (ImportRepos can also push tags directly from Git clients, see §2.6):
| Operation | HTTP |
|---|---|
| Create | POST /api/v1/tags |
| List | GET /api/v1/tags/list (GET-only; POST returns 405) |
| Get | GET /api/v1/tags/{name} |
| Delete | DELETE /api/v1/tags/{name} |
Create and delete requests authenticate through git.push_auth; list and get requests do not require authorization. The runtime OpenAPI document describes request fields and response schemas.
GET /api/v1/status, GET /api/v1/file/blob/{object_id}, GET /api/v1/file/tree, and the blob / tree / blame preview read paths remain available; read authorization follows git.anonymous_access. The full path list is the runtime OpenAPI (see §4.4); this guide does not duplicate the endpoint table.
- Machine-readable contract:
GET /api/openapi.json(under storage-only it truthfully omits CL / issue / user routes). - Interactive docs:
/swagger-ui.
Error types and HTTP status mapping are centralized in crate::common::errors.
POST /api/v1/import-repo/remove detaches and sweeps one ImportRepo under import_dir; it is mounted only in the storage-only morphology. It requires git.push_auth = "token" and a token that covers the path; with push_auth = "none" it answers 403 to every valid request, so use the operator CLI mega2 import-repo remove instead. Removal semantics, retention and continuation are in §2.6; the request and response contract is in the "ImportRepo 叶子清理" section of refactoring/directory-entry-api.md (Chinese).
Both mounted surfaces require storage-only mode and their own config switch. If either condition is missing, the route is not mounted and returns 404; enabling either switch outside storage-only mode prevents startup. Traces HTTP Git is controlled separately by [traces].enabled; configure the deployment identity, credential bindings and target permissions before enabling it.
- OCI Distribution
/v2: mounted when storage-only and[oci].enabled=true; serves as a container registry (docker loginreuses push tokens — there is no separate token service). See the Deployment Guide for enablement and usage. - Agent Session Capture
/api/v1/agent-capture: an optional API for ingesting and querying Agent sessions, events, checkpoints, transcripts, and file operations. It is mounted only in storage-only mode when[agent_capture].enabled=trueand at least one[[agent_capture.ingest_tokens]]is configured. It uses separate ingest tokens and is independent of Git push; a Git push does not create a session capture record. See the configuration and API reference (Chinese). - Traces archive:
[traces].enabledstaysfalsein the sample profiles. Turning it on requires an operator UUID inserver_id; mega2 does not mint that id at startup. This switch controls scoped traces entry points: HTTP Git is described in §5.2 and SSH identity, discovery, and Git transfer in §5.3. HTTP traces failures use stable codes: missing authentication is 401unauthenticated; an unknown or unauthorized scope is 404unknown_scope; a bad parameter or cursor is 400invalid_parameterorinvalid_cursor;unsupported_schema,invalid_capture,object_integrity, andtrace_history_in_codeare 409;response_budgetis 413;query_timeoutandsearch_unavailableare 503. The error text does not include an object key or transcript body. A named maintenance operator moves an existing target withmega2 --config <existing-config> traces rebind-target --target <UUID> --path <canonical-path>(MEGA_CONFIGmay name the config instead). The path must already be canonical and must already be a directory. A missing target, a non-canonical path, or a missing directory does not write. The command does not generate a default config, and a config found only in the working directory is refused. HTTP publication rechecks, in that same database transaction, that the credential is still active, the target is still active on the authorized path, the binding generation is unchanged, and the caller still has write permission on the current path. Retiring the credential or rebinding the target while the pack is unpacked rejects the publication and leaves the tip, membership, and journal unchanged. A missing credential isunauthenticated. Every other failed recheck isunknown_scope. A Mono account write check reads the process's current Cedar snapshot during that transaction. A snapshot copied before unpack does not grant the publication, and a missing or dirty snapshot is rejected.offdoes not grant that write.shadowfollows the current Cedar decision and does not turn a code-push shadow allow into a traces write. See the Configuration Guide. When a published checkpoint manifest declares resources, mega2 checks each part's path, OID, Git type, andbyte_lenin the declared order, plus the total resource length and content hash. Any mismatch marks that checkpoint invalid and does not delete the archived objects. Metadata schema 2, manifest schema 1, and lifecycle schema 2 are the recognized capture schemas. Any other schema stays archived and is marked unsupported.created_atis Unix seconds, not milliseconds.Libra-Parent-Commitis stored only as a code-link string and is not a Git parent. For a controlled GIN recovery, runmega2 --config <existing-config> traces search-index --action inspect --descriptor-file <path>and then--action repair --execution-timeout 1800withMEGA2_TRACES_PG_ADMIN_URLpointing at the target database. The timeout must be between 60 and 7200 seconds. Repair builds a missing index or rebuilds an exact own invalid index; it never drops an unknown or mismatched object. Capture filters matchagentbymetadata.agent_kindverbatim andcode_commitby the code-link string.sinceanduntilare RFC3339 timestamps normalized to UTC with[since, until)semantics; a reversed interval is rejected. A session is included when any of its checkpoints matches, and sessions are ordered by the newest matching checkpoint first. The capability limitsmax_object_bytes,max_metadata_bytes,max_manifest_bytes,max_pack_bytes,max_decoded_bytes,max_objects,max_new_commits, andmax_delta_depthare projected only from the fixedTracesProtocolLimitsconstants, not from operator configuration. The same DTO also carriesmax_tree_depth,ingest_timeout_seconds,metadata_limit_default,search_limit_default,text_bytes_default,metadata_limit_max,search_limit_max, andtext_bytes_max, likewise projected only from those constants. Query and scan fieldsquery_bytes_min,query_bytes_max,response_bytes_max,candidate_fragments_max,scan_bytes_max, andquery_timeout_msare projected only from those constants too.GET /api/v1/traces/capabilitiesuses the same credentials as traces Git over HTTP and returnslibra-traces/v1, the configuredserver_id,http/ssh,sha1/sha256, and those same 22 limits. When the process index is ready,search_readyandbounded_scan_supportedare true and there is no unavailable reason; when it is unavailable, both are false and the reason is the stable index reason.GET /api/v1/traces/targets/resolvereturns an existing readable target and does not create one. The credential needs an active traces binding; an unknown path, an unreadable path, or an unbound credential is 404. An authenticated request with a missing or non-canonicalpathis 400invalid_parameter.GET /api/v1/traces/targets/{target_id}/streamslists only that target.source,owner, andepochare exact filters. The default limit is 50 and the maximum is 200. Each item reportsaccepted_tip,indexed_tip, andstatus(empty,indexed, orindex_pending). An unknown or unreadable target is 404. The traces read routes are registered byapi_router::merge_tracesand mounted on both trunk and review only while[traces].enabledis true. While disabled they are absent, including from the runtime OpenAPI document. Read projection state reportsaccepted_seq,accepted_watermark,indexed_seq,indexed_watermark,index_state,unsupported,invalid,coverage_complete, and validatedcompleteness; an empty result while indexing is behind never reportscomplete, and a redacted-only transcript is never markedmissing_raw.GET /api/v1/traces/streams/{stream_id}/sessionsandGET /api/v1/traces/streams/{stream_id}/sessions/{session_id}expose the sessions of one stream, use the same read decision as traces Git, and keep the fixed list budget: the default limit is 50 and the maximum is 200.GET /api/v1/traces/streams/{stream_id}/sessions/{session_id}/checkpointsand its checkpoint detail route expose the checkpoints of one session with the same read decision and the same fixed list budget.GET /api/v1/traces/streams/{stream_id}/sessions/{session_id}/checkpoints/{checkpoint_id}/transcriptreads the transcript text of one checkpoint within the fixed byte budget, truncating on UTF-8 character boundaries and returning a continuation cursor. A keyword search matches literally: the query is compared byte for byte after ASCII letters are folded,%,_and\are never treated as wildcards, and a query longer than 256 UTF-8 bytes is rejected. The capabilities response also reports the search readiness:search_readyandbounded_scan_supportedare true when the process index is ready, andcjk_index_supportedis measured from the deployed locale. A paged query pins the accepted and indexed watermarks when it starts, and its cursor keeps that snapshot, so a later page reads the same results even when a new push arrives. The coverage verdict is taken at the pinned snapshot: it reports separately whether the scan read the whole scope and whether the index caught up, so a later push does not change it. The read surfaces share one budget: search returns 20 results by default and at most 100, a body is 16 KiB by default and at most 128 KiB, and a whole JSON response is at most 1 MiB. An over-budget response is rejected whole, so the JSON a client reads is always valid.GET /api/v1/traces/streams/{stream_id}/search?q=...runs the keyword search: each hit carries one representative piece of evidence with an occurrence count, and the page reports whether it covered the whole scope.
When [traces].enabled=false, HTTP GET/POST requests entering the root .traces reserved namespace return HTTP 404 with the JSON error message traces locator not found before application authentication, request decompression, or reading the request body. This covers discovery, upload-pack, receive-pack, and aliases that enter the namespace after one percent-decoding pass or dot-segment folding. The guard neither checks scope existence nor falls back to ordinary Native/Import repositories. Ordinary code paths that do not enter the reserved root keep their existing dispatch. The rule also checks the original URL retained by the server before LFS rewriting: even if an existing code repository has the literal root name %2etraces, its /%2etraces/… URL returns 404 while traces is disabled because one decoding pass enters the reserved namespace. When traces is enabled, this disabled guard preserves that literal root’s existing dispatch. After the existing connection authentication, SSH checks the literal repository path in exec. With traces disabled, upload-pack/receive-pack paths entering the root .traces namespace return ERR traces locator not found, exit status 1, and close the channel before protocol advertisement, stdin consumption, or ordinary repository dispatch. SSH paths do not undergo HTTP percent decoding; ordinary nested paths, similar root names, and literal %2etraces keep their existing dispatch. Ordinary SSH authentication and storage-only write restrictions retain their existing semantics. Enabled HTTP Git uses the authentication and scope boundaries below; SSH identity, discovery, and Git transfer are described in §5.3. See the traces protocol.
HTTP traces supports standard Git v0 push and v0/v2 ref discovery and fetch. A valid credential must pass authentication, principal binding and target authorization before any advertisement or request-body read. Missing or invalid credentials receive 401 with WWW-Authenticate; authenticated unauthorized and unknown scopes both receive 404. Discovery for an empty scope creates no database records. SHA-1 and SHA-256 use the scope’s actual object format, and fetch preserves the original object bytes. The traces protocol defines identities and locator syntax; see §5.3 for the current candidate's libra-traces-* extension advertisements. The SSH object data surface remains a separate delivery.
Run maintenance commands on the server with an explicitly named existing configuration; MEGA_CONFIG can also name it. The operator needs access to that configuration's database. Credentials must already exist: pass a name or record ID, never a token or private key. Replace the angle-bracket placeholders below with actual values:
mega2 --config config/config.toml traces credentials bind \
--principal '<principal-uuid>' --namespace static_push_token \
--credential '<configured-token-name>'
mega2 --config config/config.toml traces credentials bind \
--principal '<principal-uuid>' --namespace mono_account \
--credential 'ssh-key:<ssh-key-record-id>'
mega2 --config config/config.toml traces credentials rotate \
--binding '<binding-uuid>' --namespace mono_account \
--credential 'access-token:<record-id>'
mega2 --config config/config.toml traces credentials retire \
--binding '<binding-uuid>'Keep the binding UUID from successful output for later rotation or retirement. The command requires the database schema to match the current binary and never runs migrations. The Traces protocol, §2.6, defines credential keys, principals, idempotency, conflicts and retirement; these maintenance commands do not change [traces].enabled. The SSH-key example uses the actual ID of an existing stored Mono SSH public-key record. The SSH login, account display name, and fingerprint are not that record ID and do not determine the principal. Never use a token secret or private key as the credential key.
HTTP upload-pack accepts only uncompressed (identity) or gzip request bodies; both the compressed bytes and decoded commands are limited to 64 KiB. Receive-pack accepts only uncompressed bodies. Other encodings or malformed gzip return 400, and exceeding the request budget returns 413. Mono account reads and writes both require valid Cedar authorization; cedar.enforcement=off does not grant traces access.
Traces v1 accepts only PACKs without deltas. Set git config pack.window 0 in the pushing repository, keep objects loose and avoid gc/repack before pushing; --no-thin alone does not prevent internal deltas. A delta request is rejected as a whole without publishing a partial ref update. Each push may update only refs/libra/traces; deletion and other refs are rejected. The first successful push creates the scope.
Fetch the traces ref explicitly, for example git -c protocol.version=2 fetch <traces-url> refs/libra/traces:refs/libra/traces; set the version to 0 to check v0. Ordinary clone selects heads by default and does not establish whether a traces ref exists. Supply the Basic password or Bearer token through credential management; keep real tokens out of URLs and logs.
Receive and fetch have independent capacity limits. A 503 query_timeout indicates retryable overload or temporary unavailability; back off before retrying and inspect server capacity. A disconnected transfer or failed PACK checksum is not a successful fetch. See the Deployment Guide for provisioning and occupancy recovery.
SSH traces supports scoped Git receive-pack and upload-pack after credential binding and authorization. Use the reserved SSH login traces, the same locator and refs/libra/traces ref described in §5.2, and a verified key bound to the intended principal. For example, use git push --no-thin <ssh-traces-url> HEAD:refs/libra/traces and git -c protocol.version=2 fetch <ssh-traces-url> refs/libra/traces:refs/libra/traces; the no-delta PACK rule in §5.2 applies to SSH too. A successful handshake or advertisement alone does not prove an object transfer.
Authorized HTTP/SSH discovery advertises libra-traces-v1 and libra-traces-server=<server_id>. Only an existing scope advertises libra-traces-scope=<stream UUID>; a valid empty scope omits it without inventing a UUID. The server ID comes from persistent deployment configuration. The object-format and 40/64-digit zero OID width of an empty advertisement follow the SHA-1/SHA-256 format selected by authorized metadata. Protocol v2 discovery is upload-pack/read only. An authorized existing scope advertises its actual traces tip; an empty scope does not invent one. An advertised scope UUID alone does not prove a PACK transfer completed. Ordinary code discovery does not advertise these traces-specific capabilities.
- The reserved SSH login
tracesrejects none-auth. Other logins allowed to read ordinary code anonymously still cannot access traces anonymously. The principal comes from a verified credential and explicit binding, not the login or a same-named Mono account. - Bind a static push token as
static_push_token / <configured-token-name>and a Mono SSH public key asmono_account / ssh-key:<actual-record-id>. Public-key signature authentication must complete; merely offering a key does not establish an admission identity. An ambiguous fingerprint source, deleted key, or replacement of the source at the same ID rejects further traces commands using the old identity. - The connection credential is verified and its current source confirmed valid before any scope or target lookup. Every traces command requires an active binding and the corresponding target permission before admission or advertisement: upload-pack requires target project-read permission and permits shared reads by nonowners with that permission; receive-pack on an existing scope requires both scope ownership and target project-write permission. For existing-scope receive, ownership alone is insufficient, as is write permission without ownership. A valid empty scope follows Create write authorization without fabricating an existing owner. A static token must cover the target path. A Mono SSH key without the scoped mapping below uses the current valid Cedar decision for that command:
offdoes not grant access, andshadowfollows the real Cedar decision rather than ordinary code's shadow allow. - Only a real traces locator that passes these checks receives the limited storage-only TraceRepo receive-pack command-admission exception. Ordinary code and other views do not receive it. The mandatory
git.ssh_receive_pack=falsesetting and ordinary-code write denial remain, as do the disabled reserved-root rejection and no-fallback boundary.
In a storage-only deployment with git.push_auth="token", an operator can explicitly scope a saved SSH key using an existing static token. For an actual SSH key record ID 123, configure a static token named trace-ssh-key:123, with a protected token secret and allowed paths such as paths = ["/project"]. Use traces credentials bind to bind both mono_account / ssh-key:123 and static_push_token / trace-ssh-key:123 to the same principal UUID. Both bindings must remain active, and the signature-verified key may read or write traces only within the token's paths. Missing, retired, or differently owned bindings reject access without falling back to Mono Cedar; writing an existing scope still requires its owner. The SSH key client does not send the token secret, but that static token remains usable through the existing HTTP and SSH password authentication, so protect it. Omitted or empty paths retain the static-token whole-repository scope; list paths explicitly to restrict access. Configuration changes require a restart, and rotation to a different key record ID requires a new token name and bindings. Without this configured name, the existing Mono SSH key Cedar behavior is unchanged; ordinary code SSH does not use the mapping.
HTTP and SSH in the same service process share one receive slot and one separate fetch slot. A disconnect does not establish whether a database commit already in progress completed; check the resulting ref before deciding what to do. The ingest deadline does not guarantee a bound on COMMIT or rollback cleanup.
SSH reports rejection through authentication failure or controlled channel error, exit, and closure. An unknown scope and an authenticated but unauthorized request produce the same controlled ERR without disclosing scope existence. The HTTP status codes in §5 are not SSH handshake statuses. The Traces protocol, §2.6, defines credential maintenance and identity sources.
The open-source edition has no Web UI. Libra's libra mega2 browser command provides basic remote directory browsing and supported directory and tag operations. These operations do not replace Git client clone, fetch, or push:
libra mega2 browserThe TUI browses remote directories one level at a time and supports creating, deleting, moving, and renaming directories. Its Tag panel lists, creates, and deletes root tags; creating or deleting requires write credentials. It reads remote directory and tag data from Mega2 and does not replace a Git client's clone, fetch, or push. The sha256 / blake3 object formats are likewise only available with the Libra client (see §2.4).
Global flags: --config <PATH> (env MEGA_CONFIG) and --profile <NAME> (env MEGA_PROFILE, loading the sibling config.<profile>.toml). See the Configuration Guide for load order, environment overrides (MEGA_<SECTION>__<KEY>), unknown-field rejection, and hot reload. For a new database, mega2 service init --yes creates the initial Monorepo from the configured root_dirs and exits without starting listeners; the commented config.toml shows the directory defaults.
| Command | Purpose |
|---|---|
mega2 service init --yes |
Initialize an empty Monorepo and exit (no listeners started); the fixed bootstrap action for an empty volume |
mega2 service http --host 0.0.0.0 -p 8000 |
Start the HTTP surface (Git smart HTTP + LFS + /api/v1 + optional OCI / Agent Capture); matches the Dockerfile default CMD |
mega2 service ssh [--ssh-port 2222] |
Start the SSH surface (ordinary code is read-only under storage-only; scoped traces Git and identity are described in §5.3); standalone requires cedar.enforcement=off, otherwise use multi |
mega2 service multi http ssh |
Start HTTP + SSH in one process (shared authorization snapshot) |
mega2 path provision --server <URL> <PATH> |
Provision a monorepo path (before its first push, see §2.5); the token comes only from the environment variable MEGA2_TOKEN, no config file is read |
mega2 import-repo remove --path <P> --yes [--cleanup-id <ID>] [--max-rounds <N>] |
Remove an ImportRepo on the server (storage-only; put --config before import-repo or set MEGA_CONFIG); outcome lines and exit codes in §2.6 |
mega2 --config <existing> traces rebind-target --target <UUID> --path <canonical-path> |
Move an existing trace target onto a directory that is already canonical (§5). No default config is generated |
mega2 --config <existing> traces rebuild-index --stream <UUID> --index-version <N> |
Rebuild the selected stream's indexed prefix into a new index version |
mega2 --config <existing> traces credentials bind --principal <UUID> --namespace <NAMESPACE> --credential <KEY> |
Explicitly bind an existing credential (§5.1) |
mega2 --config <existing> traces credentials rotate --binding <UUID> --namespace <NAMESPACE> --credential <KEY> |
Rotate an existing binding's credential (§5.1) |
mega2 --config <existing> traces credentials retire --binding <UUID> |
Retire a binding while keeping its history (§5.1) |
mega2 config validate [--resolve-secrets] [--show-sources] |
Validate configuration before startup; add --deny-warnings in automation |
mega2 config init [-o PATH] [--force] |
Generate a safe starter configuration file |
mega2 config secret ref/set/check/rotate |
Manage vault-backed config secrets (SecretRef) |
mega2 config vault backup/restore/rekey/reset |
Vault core-key operations (the last three are destructive and require --force) |
mega2 debug storage-smoke [--key ...] |
Hidden command: object-storage read / write / delete smoke; absent from top-level help |
The authz-audit subcommand audits authorization in review mode. Under storage-only, cedar.enforcement is always off, so this command is not part of day-to-day use. The Architecture Guide summarizes the authorization modes.
traces rebuild-index requires an existing config named with --config or MEGA_CONFIG. It fixes and reports the accepted_seq and indexed_seq watermarks, then replays only that stream's contiguous indexed prefix. Each sequence commits independently, so rerunning the same command after an interruption is safe. It does not change raw objects, the accepted tip, or journal state.
The full flag list lives in --help and src/commands/. See the Deployment Guide for Compose setup and the Contributing Guide for the development workflow and required checks.
| Topic | Document |
|---|---|
| Guides (quick start / recipes / config / deployment / architecture / contributing) | quick-start.md · recipes.md · configuration.md · deployment.md · architecture.md · contributing.md |
| Repository paths, branches, tags, push behavior, and the ImportRepo lifecycle | Sections 1–2 of this guide |
| Trunk / storage-only deployment and the Compose stack | deployment.md |
| Config keys and environment variables | config.toml (commented sample), configuration.md |
| Git protocol surfaces and write path | architecture.md |
| Directory, file, and tag operations | Sections 4.1–4.2 of this guide |
| OCI registry and Agent Capture routes | architecture.md and deployment.md |
| Error handling implementation | crate::common::errors |
| Contribution workflow and required checks | contributing.md |