napplet.
← All NAPs

NAP-GIT / PR #137

Runtime-Mediated Git Repositories

Open PR

Full specification from the fetched upstream revision ↗. The document below is rendered from its original Markdown. Follow the latest source on GitHub ↗.

NAP-GIT

Runtime-Mediated Git Repositories

draft

NAP ID: NAP-GIT Domain: git Web binding (NIP-5D): window.napplet.git; domain presence signals availability. This is projection guidance.

Description

NAP-GIT provides repository access through Git objects, references, and explicit remote operations. The napplet designs browsing and editing workflows. The shell mediates repository selection and authority. The runtime executes Git operations, holds credentials, signs when required, and selects storage and network backends. The same contract supports native Git libraries or executables and ngit, which combines Git object storage with Nostr repository state and collaboration.

Boundary

A Git object is immutable content identified by its hash: a blob (file bytes), tree (directory entries), commit (a tree and its history), or annotated tag. A reference is a mutable name pointing to an object. These concepts are common to both implementations; the runtime's implementation choice is not a projection.

Concern Owner
Repository access, object verification, storage, retention Runtime
Local reference transactions, remote synchronization Runtime
Credentials, signer selection, Nostr event construction and verification Runtime, under shell mediation
Editors, comparisons, history presentation, conflict-resolution choices Napplet
Repository, account, publication destinations, permission decisions User, through the shell
Host paths, processes, sockets, Git configuration and command execution Runtime internals

Creating a commit, moving a local reference, updating a remote reference, and submitting a proposal are separate effects. The runtime MUST NOT substitute one for another. In particular, ngit's proposal-producing branch conventions MUST NOT turn push into propose.

This draft exposes committed objects. Working directories, staging indexes, checkout, merge/rebase execution, arbitrary commands, issues, reviews, repository announcement editing, and forge administration are outside this surface. Napplets can compare trees and construct an edited commit without host filesystem access. Filesystem and generic Nostr access remain independently governed by their owning domains; no other NAP domain is required by this contract.

API Surface

Every runtime exposing git MUST implement open, close, refs, get, and read. Additional operations are reported per repository in operations. An absent operation MUST return unsupported when its defined request is received. An advertised operation can still be denied by current policy.

Operation Parameters Result Wire
open optional source (text) GitRepository git.open / git.open.result
close repository (text) null git.close / git.close.result
refs repository (text), optional cursor (text), optional limit (integer) GitRefPage git.refs / git.refs.result
get repository (text), oid (text), optional cursor (text), optional limit (integer) GitObject git.get / git.get.result
read repository (text), oid (text), offset (integer), length (integer) GitBytes git.read / git.read.result
createCommit repository (text), request (GitCommitRequest) GitCreatedCommit git.createCommit / git.createCommit.result
updateRef repository (text), request (GitRefUpdate) GitRefUpdate git.updateRef / git.updateRef.result
fetch repository (text), remote (text), ref (text) GitFetchedRef git.fetch / git.fetch.result
push repository (text), request (GitPushRequest) GitPublication git.push / git.push.result
propose repository (text), request (GitProposalRequest) GitPublication git.propose / git.propose.result

Common values

Value Definition
Object ID (oid) Full lowercase hexadecimal Git object ID: 40 characters for sha1, 64 for sha256, matching the opened repository's objectFormat. Abbreviations, revision expressions, and all-zero IDs are invalid. A Git ID is distinct from a Nostr event ID.
Reference name (ref) UTF-8 text containing a full refs/heads/… or refs/tags/… name accepted by Git's reference-name rules. No refspecs, wildcards, revision expressions, or symbolic references. Branch mutations require refs/heads/….
Bytes Text containing canonical padded standard base64 under RFC 4648. Empty bytes are "". Whitespace, base64url, missing required padding, and nonzero pad bits are invalid.
Path Bytes encoding a repository-relative path. / byte separates segments. Empty, absolute, trailing-slash, empty-segment, NUL, . and .. paths are invalid. No Unicode normalization, case folding, URL decoding, or host path interpretation.
Name Bytes encoding one nonempty tree-entry name; / and NUL are invalid. Names need not be UTF-8.
Cursor Nonempty opaque text scoped to the endpoint, repository handle, operation, and original arguments. Not authority.

All integers MUST be exact and within -9007199254740991 through 9007199254740991; byte counts and offsets MUST be nonnegative. List limits MUST be positive. Omitted optional fields use the defaults stated below. null is valid only where explicitly allowed. Empty lists mean no entries.

Repository and reference schemas

GitRepository:

Field Required Type Notes
repository yes text Nonempty opaque handle for this authenticated endpoint.
objectFormat yes "sha1" or "sha256" Applies to every Git object ID on this handle.
operations yes list of text Supported operation names from the API table. No duplicates.
remotes yes list of GitRemote Only remotes the caller may discover. Empty for local-only access.

GitRemote:

Field Required Type Notes
remote yes text Nonempty opaque identifier scoped to this repository handle.
operations yes list of text Supported subset of "fetch", "push", "propose"; not a grant.

GitRef:

Field Required Type Notes
ref yes text Full reference name.
oid yes text Direct target; an annotated tag points to its tag object, not its peeled commit.

GitRefPage:

Field Required Type Notes
entries yes list of GitRef Local references, sorted by unsigned UTF-8 bytes of ref.
next yes text or null Cursor for the next page; null means complete.

GitRefUpdate:

Field Required Type Notes
ref yes text Local branch reference.
expected yes text or null Current direct object ID; null requires absence.
oid yes text or null New commit ID; null deletes. Both expected and oid cannot be null.

GitFetchedRef:

Field Required Type Notes
ref yes text Requested remote reference.
oid yes text Observed direct target, now available locally.

Object schemas

GitObject:

Field Required Type Notes
oid yes text Requested object ID.
kind yes "blob", "tree", "commit", or "tag" Selects the fields below.
size yes integer Complete uncompressed Git object body size in bytes.
entries conditional list of GitTreeEntry Required only for a tree; one page of immediate children.
next conditional text or null Required only for a tree.
commit conditional GitCommit Required only for a commit.
target conditional text Required only for a tag; target object ID.

Fields for other kinds MUST be absent. Blobs contain no byte payload in get; use read. An annotated tag's content and signature remain available through read; target does not assert signature validity.

GitTreeEntry:

Field Required Type Notes
name yes text Name bytes.
mode yes "040000", "100644", "100755", "120000", or "160000" Tree, regular blob, executable blob, symlink blob, or gitlink (submodule commit).
oid yes text Child object ID. A gitlink's object can be absent from this repository.

GitCommit:

Field Required Type Notes
tree yes text Root tree object ID.
parents yes list of text Parent commit IDs in stored order; empty for a root commit.
author yes text Raw author-header value, encoded as bytes. Attribution, not authenticated identity.
committer yes text Raw committer-header value, encoded as bytes.
message yes text Exact message bytes. No character encoding is assumed.

GitBytes:

Field Required Type Notes
data yes text Requested bytes from the uncompressed object body, without Git's object header.
offset yes integer Requested offset.
size yes integer Complete body size.
eof yes boolean True when offset plus decoded data length reaches or exceeds size.

read works for all four object kinds. Its raw bytes preserve headers, signatures, non-UTF-8 messages, and fields omitted from the structured view. The runtime MUST verify the object's Git hash, including its type/size header, before returning any part of it. Replacement objects and grafted history MUST NOT alter the requested object's view.

Commit schemas

GitCommitRequest:

Field Required Type Notes
parent yes text or null Parent commit, or null for a root commit with an initially empty tree.
changes yes list of GitChange Nonempty, distinct paths; applied to the parent's tree.
message yes text Nonempty UTF-8 commit message; NUL is invalid.

GitChange:

Field Required Type Notes
path yes text Path bytes.
action yes "put" or "delete" Delete removes one existing non-tree entry.
mode conditional "100644" or "100755" Required only for put.
data conditional text File bytes; required only for put, including empty files.

GitCreatedCommit:

Field Required Type Notes
oid yes text Created commit object ID.

Publication schemas

GitPushRequest:

Field Required Type Notes
remote yes text Remote identifier from GitRepository.remotes.
ref yes text Exact destination branch.
oid yes text Locally available commit to publish.

GitProposalRequest:

Field Required Type Notes
remote yes text Remote identifier supporting propose.
target yes text Intended destination branch.
base yes text Locally available commit; ancestor of head.
head yes text Locally available commit, distinct from base.
title yes text Nonempty plain text.
description yes text Plain text; may be empty.

GitPublication:

Field Required Type Notes
outcome yes "complete", "failed", "partial", or "unknown" Derived from receipts under the rules below.
receipts yes list of GitReceipt Nonempty; every required publication effect is represented.

GitReceipt:

Field Required Type Notes
effect yes "objects", "ref", or "proposal" Object availability, reference publication, or proposal publication.
destination yes text Nonempty opaque destination identifier, stable within this repository handle.
status yes "accepted", "rejected", or "unknown" Positive acknowledgement, known no effect at this destination, or uncertain effect.
oid yes text Requested push commit or proposal head.
ref conditional text Required only for ref effects; exact destination branch.
locator no text Credential-free absolute URI identifying the published artifact, when available. Informational; conveys no authority.

Operation Semantics

Repository access and lifetime

open without source asks the shell to let the user choose a repository or create an empty runtime-managed repository. With source, it requests access to that repository location. The shell MAY offer a choice, require consent, use an existing authorized store, or deny the request. Local host paths and file: URLs are invalid. Supported remote URL schemes are runtime policy; unsupported ones return unsupported. nostr: locations use the upstream NIP-34 clone URL format.

Success means the runtime established repository access and can service core reads. It does not promise that every remote object is cached. open MAY fetch under shell policy, but MUST NOT publish or alter an existing repository's refs. The runtime MUST NOT equate matching URLs, root commits, or object hashes with identical repository authority. The returned remote identifiers bind the approved logical repository and its authority, not an arbitrary Git server named by data.

Handles, remotes, and cursors MUST be scoped to the authenticated endpoint. Copying a handle to another endpoint grants nothing. Policy MAY revoke access. Unknown, foreign, closed, or revoked handles return invalid-handle without revealing whether another endpoint owns them.

close prevents new operations on the handle and waits for already-started operations to settle before returning null. Repeated close on the caller's already-closed handle succeeds. Close releases access and cursors; it does not delete repositories, undo commits, or retract publications. Endpoint teardown invalidates all handles and cursors. The runtime MUST stop work that has not crossed its effect boundary and MUST NOT start further publication steps under the revoked endpoint. Already-issued effects may finish; the runtime MUST retain their outcome for shell inspection and MUST NOT send late results to another endpoint. Policy revocation follows the same effect rule.

Objects read, created, or fetched through a handle MUST remain available for that handle's lifetime unless access is revoked or storage fails. References may be shared with other authorized callers; the handle is not a private clone.

Reads and pagination

refs lists authorized local branch and tag references. It performs no remote refresh. Remote state is obtained with fetch. A first page captures one local reference snapshot; subsequent pages MUST continue that snapshot, even if refs move. An empty repository returns entries: [], next: null. The runtime MUST NOT normalize or lossily decode reference names. A visible reference whose name cannot be represented as UTF-8 makes listing fail with unsupported rather than silently hiding that reference.

get returns a verified object's structured view. Tree entries are sorted lexicographically by unsigned name bytes. cursor and limit are valid only for trees. For both paginated operations, omission of limit selects a runtime default. The runtime MAY cap the page size but MUST use next rather than silently truncate. Continuations MUST repeat the original arguments, including the same limit or its omission. Invalid or expired cursors return invalid-cursor; the caller restarts from the first page. An incomplete page MUST advance.

read returns exactly min(length, max(0, size - offset)) bytes. An offset at or beyond the end returns empty bytes and eof: true. Zero length is valid. The runtime MUST reject an excessive requested length with too-large, rather than shorten a successful read. It MAY obtain authorized missing objects from approved backends; inability to obtain an object returns not-found or unavailable. Symlinks and gitlinks are data; the runtime MUST NOT follow them to host files or submodule repositories.

Local mutations

createCommit constructs blobs, trees, and one commit. Parent ancestry MUST be preserved exactly. It does not update a reference, staging index, working directory, or remote. The shell selects the author, committer, timestamps, and any Git signing identity under user policy. No napplet field selects a signer. The returned object's stored headers are inspectable with get and read. The message MUST be stored as its exact UTF-8 bytes, without normalization or line-ending changes. Git signatures and Nostr event signatures are separate mechanisms.

The runtime MUST validate all changes before constructing the commit. Changes MUST NOT overlap as ancestor/descendant paths. put replaces or creates only a regular file; replacing a tree, symlink, or gitlink returns conflict. Missing parent directories are created. Traversing a non-tree returns conflict. Deleting a missing entry returns not-found; deleting the last entry removes empty ancestor trees. Unchanged entries and their modes MUST be preserved. The runtime MUST reject .git path segments, ASCII case-insensitively, and backslash bytes in changes. These restrictions do not hide existing tree names from reads. Invalid input or failure MUST NOT update refs; unreachable objects left by failed creation MAY be garbage-collected.

updateRef is a local compare-and-swap: the runtime MUST atomically compare the current direct target with expected and apply oid, or return conflict without changing that reference. It MUST validate a non-null target as a local commit. Symbolic destinations MUST be rejected. Rewrites and deletions require explicit shell policy. The runtime MUST NOT change a branch attached to a working directory; it MUST return conflict. Success returns the applied request record. Runtime-managed bare repositories avoid this restriction. This transaction makes no statement about remote state.

Remote effects

fetch resolves one named remote reference according to that remote's authority, obtains its target and reachable Git objects, and returns the observed target. Success requires complete reachable history and trees, excluding gitlink targets. A runtime limited to incomplete history returns unavailable. A missing ref returns not-found. Fetch MUST NOT move local branches, tags, or working directories. Internal caches and tracking metadata MAY change. A concurrent remote change does not invalidate the returned observation.

push requests publication of exactly oid to the destination branch, with its reachable objects. Only branch creation and fast-forward updates are in this draft. A fast-forward retains the previous commit as an ancestor of the new commit. The runtime MUST check the authorized remote state before publication and apply any stronger checks the backend supports. A known non-fast-forward returns conflict. Force, deletion, mirror refspecs, and automatic merge are excluded. A backend that cannot perform the requested reference update MUST return unsupported; it MUST NOT silently submit a proposal.

Remote publication is not the local updateRef transaction. Neither a prior fetch nor a successful push guarantees exclusive ownership or global compare-and-swap on a distributed remote. The runtime MUST NOT claim such a guarantee. It MUST preserve the exact requested ref and object ID when signing or sending a publication. Competing signed state can later supersede it. When a backend represents several refs in one state document, the runtime MUST preserve unrelated refs from the resolved state. This preserves the observed state; it cannot prevent an unseen concurrent update from being superseded.

propose submits the commits after base through head for the named target branch, with the supplied title and description. The runtime MUST verify ancestry and preserve that range. It MUST NOT merge or update the target branch. The runtime owns representation as patches, a remote branch plus pull request, or Nostr proposal events. If it cannot preserve the requested range or metadata, it returns unsupported. Native Git alone need not support proposals. This operation creates a new proposal; revisions and lifecycle changes are outside this draft. A proposal receipt SHOULD supply its address as locator.

Before push or propose, the shell MUST authorize the complete publication plan: destinations, disclosed history, signer/account, and each required effect. The runtime MUST NOT add destinations after authorization without renewed mediation. Supporting object uploads count as publication even if a later reference or proposal step fails. For patches carrying all required content, a separate object-upload effect is unnecessary.

After publication begins, the runtime MUST return GitPublication, including on timeout or partial failure, rather than hide receipts in an ordinary error. Every required effect/destination pair MUST have one receipt. Unattempted steps are rejected; acceptance requires positive acknowledgement or verified preexisting satisfaction. Outcome is derived in this order:

Condition outcome
Any receipt is unknown "unknown"
All receipts are accepted "complete"
All receipts are rejected "failed"
Otherwise "partial"

An accepted objects receipt means the required objects are available at that destination. An accepted ref receipt means the selected ref authority accepted the exact update; for signed distributed state, selected relay acceptance is the evidence. An accepted proposal receipt means the proposal was accepted by its selected service or relay. push requires at least one ref receipt and no proposal receipts. propose requires at least one proposal receipt and MUST NOT include a ref receipt for the target branch. Supporting proposal branches MAY have their own ref receipts. complete does not promise global consensus, permanent availability, replication to unselected recipients, or merge.

Native Git and ngit Mapping

This table is implementation guidance, not a claim of NAP support. Upstream formats remain owned by Git and NIP-34.

Contract Native Git implementation ngit implementation
open Approved local object store or managed clone Resolve approved Nostr repository identity; manage a Git object store
get, read, createCommit Git object database The same Git object model; Nostr event IDs never replace Git IDs
refs, updateRef Local refs and atomic local transactions Local refs and the same local transactions; no implicit event publication
fetch Resolve remote refs and fetch objects Resolve authorized signed state and obtain objects from approved Git servers
push Publish objects and update exact remote branch Publish objects where needed and authorized signed branch state
propose Optional forge or patch-delivery integration Construct and publish the appropriate Nostr proposal representation

ngit separates signed repository state from Git object hosting, as described in its architecture guide. The runtime MUST verify signatures and the selected repository's maintainer authority before using signed remote state. An announcement or server's possession of objects alone does not authorize its refs. The runtime MUST follow its selected backend's state-selection rules; unresolved conflicting authority returns conflict. It MUST NOT substitute a reachable object's host as the state authority when signed state is unavailable. Before publishing signed branch state, the runtime MUST verify that the selected signer has maintainer authority for the opened repository. It MUST return denied before publication if that authority is absent.

ngit's proposal branch behavior belongs behind propose. An implementation may use a remote helper internally, but MUST prevent helper naming rules, implicit tracking configuration, or automatic fallbacks from changing the requested effect. Separate receipts expose Git server uploads and relay publication; one successful step does not establish the other. No generic relay or signer surface is exposed to the napplet by git.

Wire Protocol

The following language-neutral messages carry this contract. The web projection maps them to the NIP-5D envelope. ? marks optional fields; it is not part of a field name.

Type Direction Payload fields
git.open napplet -> runtime id, source?
git.open.result runtime -> napplet id, result?, error?
git.close napplet -> runtime id, repository
git.close.result runtime -> napplet id, result?, error?
git.refs napplet -> runtime id, repository, cursor?, limit?
git.refs.result runtime -> napplet id, result?, error?
git.get napplet -> runtime id, repository, oid, cursor?, limit?
git.get.result runtime -> napplet id, result?, error?
git.read napplet -> runtime id, repository, oid, offset, length
git.read.result runtime -> napplet id, result?, error?
git.createCommit napplet -> runtime id, repository, request
git.createCommit.result runtime -> napplet id, result?, error?
git.updateRef napplet -> runtime id, repository, request
git.updateRef.result runtime -> napplet id, result?, error?
git.fetch napplet -> runtime id, repository, remote, ref
git.fetch.result runtime -> napplet id, result?, error?
git.push napplet -> runtime id, repository, request
git.push.result runtime -> napplet id, result?, error?
git.propose napplet -> runtime id, repository, request
git.propose.result runtime -> napplet id, result?, error?

id is nonempty text, unique among in-flight requests from that endpoint. The runtime MUST return one corresponding result with the same id for every recognized request with a valid id from a live authenticated endpoint. Results MUST be routed only to that endpoint. There are no unsolicited messages or wire subscriptions in this draft. Unrecognized message types and messages from unauthenticated endpoints follow the projection's rules.

A successful envelope MUST contain result of the operation's declared type and omit error. A failed envelope MUST contain error and omit result. A publication result can report an unsuccessful effect through outcome; successful delivery of the result does not establish successful publication. Unknown request fields MUST be rejected with invalid-request before effects, so misspelled authority or precondition fields cannot silently disappear. Receivers MAY ignore unknown result fields.

Error Handling

GitError (error):

Field Required Type Notes
code yes text One value from the table below.
message no text Safe human-readable detail. Not for programmatic branching.
Code Meaning
invalid-request Missing, malformed, unexpected, or contradictory fields; invalid path, ref, object ID, base64, or range.
invalid-handle Repository access is absent or ended.
invalid-cursor Cursor is absent from its scope, expired, or used with different arguments.
not-found Authorized object, ref, remote, or entry is absent or hidden.
denied Shell policy or user choice refused the request.
cancelled User cancelled selection, or work stopped before its observable mutation/publication.
unsupported Operation, source scheme, object format, or requested semantics cannot be provided.
conflict Local precondition failed, ancestry is unsuitable, paths conflict, or remote authority cannot be resolved.
too-large Request, object, or requested read exceeds runtime limits.
busy Runtime work or storage quota prevents the operation.
unavailable Required data, transport, credentials, or signer is unavailable.
integrity Object hash, structure, signature, or authority verification failed.

The runtime MUST validate and authorize before observable mutations or publication. Policy MAY return not-found to hide unauthorized data. Ordinary errors establish that the requested mutation/publication did not occur; internal cache fills and unreachable objects are permitted. Started publication follows the receipt rules instead. Invalid or missing id cannot be correlated and MAY be discarded. A caller MUST NOT reuse an in-flight ID; a runtime detecting a duplicate MUST NOT execute a second effect or emit a second result for that ID.

Correlation IDs are not persistent deduplication keys. After losing a result, the caller MUST treat mutation outcome as uncertain. It SHOULD inspect local refs or remote state and use shell-mediated review before retrying publication. Repeating propose can create duplicate public proposals. There is no automatic retry guarantee, reattachment, or exactly-once remote publication in this draft.

Examples

The SHA-1 fixture changes README.md from Hi plus LF to Hello plus LF. The root message is Initial plus LF. Both commits use author and committer Example <example@example.org> 0 +0000, with no extra headers. These are test objects, not a live repository. Encoded paths and bytes are literal base64.

Open, then capture local refs:

-> { "type": "git.open", "id": "o1", "source": "https://git.example.org/tools.git" }
<- { "type": "git.open.result", "id": "o1", "result": { "repository": "repo-1", "objectFormat": "sha1", "operations": ["open", "close", "refs", "get", "read", "createCommit", "updateRef", "fetch", "push"], "remotes": [{ "remote": "remote-1", "operations": ["fetch", "push"] }] } }
-> { "type": "git.refs", "id": "r1", "repository": "repo-1" }
<- { "type": "git.refs.result", "id": "r1", "result": { "entries": [{ "ref": "refs/heads/main", "oid": "eb7f6a0e5a12e427bec3f9af5fd505fc2efc1fa4" }], "next": null } }

Create an edited commit, then install it with a local precondition:

-> { "type": "git.createCommit", "id": "c1", "repository": "repo-1", "request": { "parent": "eb7f6a0e5a12e427bec3f9af5fd505fc2efc1fa4", "changes": [{ "path": "UkVBRE1FLm1k", "action": "put", "mode": "100644", "data": "SGVsbG8K" }], "message": "Update README\n" } }
<- { "type": "git.createCommit.result", "id": "c1", "result": { "oid": "800cea77c0e8f383c0c6fb6c52b16980d634c17c" } }
-> { "type": "git.updateRef", "id": "u1", "repository": "repo-1", "request": { "ref": "refs/heads/main", "expected": "eb7f6a0e5a12e427bec3f9af5fd505fc2efc1fa4", "oid": "800cea77c0e8f383c0c6fb6c52b16980d634c17c" } }
<- { "type": "git.updateRef.result", "id": "u1", "result": { "ref": "refs/heads/main", "expected": "eb7f6a0e5a12e427bec3f9af5fd505fc2efc1fa4", "oid": "800cea77c0e8f383c0c6fb6c52b16980d634c17c" } }

Stale local precondition; reference unchanged by this request:

-> { "type": "git.updateRef", "id": "u2", "repository": "repo-1", "request": { "ref": "refs/heads/main", "expected": "eb7f6a0e5a12e427bec3f9af5fd505fc2efc1fa4", "oid": "800cea77c0e8f383c0c6fb6c52b16980d634c17c" } }
<- { "type": "git.updateRef.result", "id": "u2", "error": { "code": "conflict" } }

Inspect the committed tree and read a blob:

-> {"type": "git.get", "id": "g1", "repository": "repo-1", "oid": "800cea77c0e8f383c0c6fb6c52b16980d634c17c"}
<- {"type": "git.get.result", "id": "g1", "result": {"oid": "800cea77c0e8f383c0c6fb6c52b16980d634c17c", "kind": "commit", "size": 202, "commit": {"tree": "dc0a29da1d9b3f68dcd56af0e34f8df4fbf8b24f", "parents": ["eb7f6a0e5a12e427bec3f9af5fd505fc2efc1fa4"], "author": "RXhhbXBsZSA8ZXhhbXBsZUBleGFtcGxlLm9yZz4gMCArMDAwMA==", "committer": "RXhhbXBsZSA8ZXhhbXBsZUBleGFtcGxlLm9yZz4gMCArMDAwMA==", "message": "VXBkYXRlIFJFQURNRQo="}}}
-> {"type": "git.get", "id": "g2", "repository": "repo-1", "oid": "dc0a29da1d9b3f68dcd56af0e34f8df4fbf8b24f"}
<- {"type": "git.get.result", "id": "g2", "result": {"oid": "dc0a29da1d9b3f68dcd56af0e34f8df4fbf8b24f", "kind": "tree", "size": 37, "entries": [{"name": "UkVBRE1FLm1k", "mode": "100644", "oid": "e965047ad7c57865823c7d992b1d046ea66edf78"}], "next": null}}
-> {"type": "git.read", "id": "b1", "repository": "repo-1", "oid": "e965047ad7c57865823c7d992b1d046ea66edf78", "offset": 0, "length": 6}
<- {"type": "git.read.result", "id": "b1", "result": {"data": "SGVsbG8K", "offset": 0, "size": 6, "eof": true}}

Fetch records an observation without moving the local branch:

-> {"type": "git.fetch", "id": "f1", "repository": "repo-1", "remote": "remote-1", "ref": "refs/heads/main"}
<- {"type": "git.fetch.result", "id": "f1", "result": {"ref": "refs/heads/main", "oid": "800cea77c0e8f383c0c6fb6c52b16980d634c17c"}}

ngit publication: object upload accepted, signed-state acceptance uncertain. Assume repo-n and remote-n were opened and authorized, and the source commit is a fast-forward of the observed destination.

-> { "type": "git.push", "id": "p1", "repository": "repo-n", "request": { "remote": "remote-n", "ref": "refs/heads/main", "oid": "800cea77c0e8f383c0c6fb6c52b16980d634c17c" } }
<- {"type": "git.push.result", "id": "p1", "result": {"outcome": "unknown", "receipts": [{"effect": "objects", "destination": "object-store-1", "status": "accepted", "oid": "800cea77c0e8f383c0c6fb6c52b16980d634c17c"}, {"effect": "ref", "destination": "state-relay-1", "status": "unknown", "oid": "800cea77c0e8f383c0c6fb6c52b16980d634c17c", "ref": "refs/heads/main"}]}}

Proposal submission is independent of branch update. Assume this remote supports propose, and both commits are present with the stated ancestry.

-> { "type": "git.propose", "id": "p2", "repository": "repo-n", "request": { "remote": "remote-n", "target": "refs/heads/main", "base": "eb7f6a0e5a12e427bec3f9af5fd505fc2efc1fa4", "head": "800cea77c0e8f383c0c6fb6c52b16980d634c17c", "title": "Update README", "description": "Clarify the greeting." } }
<- {"type": "git.propose.result", "id": "p2", "result": {"outcome": "complete", "receipts": [{"effect": "proposal", "destination": "proposal-relay-1", "status": "accepted", "oid": "800cea77c0e8f383c0c6fb6c52b16980d634c17c"}]}}

Malformed input and denial despite declared domain availability:

-> { "type": "git.read", "id": "e1", "repository": "repo-1", "oid": "800cea77c0e8f383c0c6fb6c52b16980d634c17c", "offset": -1, "length": 20 }
<- { "type": "git.read.result", "id": "e1", "error": { "code": "invalid-request" } }
-> { "type": "git.push", "id": "e2", "repository": "repo-1", "request": { "remote": "remote-1", "ref": "refs/heads/main", "oid": "800cea77c0e8f383c0c6fb6c52b16980d634c17c" } }
<- { "type": "git.push.result", "id": "e2", "error": { "code": "denied" } }

Release access; subsequent reads fail:

-> {"type": "git.close", "id": "x1", "repository": "repo-1"}
<- {"type": "git.close.result", "id": "x1", "result": null}
-> {"type": "git.read", "id": "x2", "repository": "repo-1", "oid": "e965047ad7c57865823c7d992b1d046ea66edf78", "offset": 0, "length": 6}
<- {"type": "git.read.result", "id": "x2", "error": {"code": "invalid-handle"}}

A second endpoint using repo-1 also receives invalid-handle, even while the first endpoint's handle is live. Equal request IDs on different endpoints are independent; neither receives the other's result. If the first endpoint ends while publication is in flight, the shell retains receipts and delivers no late result to the second endpoint.

Shell Behavior

  • The shell MUST bind access to the projection-authenticated endpoint and its verified manifest/artifact identity. git declarations and handles do not establish user identity or publication permission.
  • The runtime MUST enforce repository, object/history, reference, remote, and operation scope on every request. Possession of an object ID is not read authority. A policy unable to authorize disclosed history MUST deny access. The runtime MUST deny an object read rather than redact or rewrite the content or structured fields of a hash-identified object.
  • The shell MUST keep account and repository selection under user control. A source URL, Nostr pubkey, author header, or remote identifier MUST NOT select credentials or grant maintainer authority by itself.
  • The runtime MUST enforce finite work, byte, pagination, storage, and operation duration limits. It MAY choose their values. Overload MUST produce busy or too-large; publication deadlines MUST preserve receipts and uncertainty.
  • The runtime MAY implement this contract using executables, libraries, local stores, or services. It MUST preserve the same observable semantics.

Security Considerations

  • Git content and configuration are untrusted. The runtime MUST prevent repository-controlled hooks, filters, credential helpers, diff drivers, Git aliases, command substitutions, and arbitrary remote helpers from executing. Only runtime-approved integrations may run. Napplet fields MUST NOT become command options, environment settings, executable names, or shell code.
  • The runtime MUST contain object parsing and storage independently of host path interpretation. It MUST bound decompression and graph traversal, reject malformed objects, and prevent traversal through aliases, symlinks, alternates, submodules, or malicious repository configuration into unauthorized stores.
  • Network destinations from source URLs, redirects, announcements, server responses, or Git metadata are untrusted hints. The runtime MUST apply network policy to every connection and redirect, including discovered object hosts and relays. It MUST NOT send credentials to an unapproved destination.
  • Publication can disclose entire reachable history, including deleted secrets. The shell MUST authorize that history, not merely the currently edited files. Permission to read or create local commits does not grant upload permission.
  • Repository handles, remote metadata, receipts, locators, and errors MUST NOT expose host paths, credentials, private keys, signer tokens, or another endpoint's repositories. Private repository names and destinations require explicit disclosure policy. The shell MUST retain signing authority.
  • Object hashes prove content identity, not author identity, maintainer authority, code safety, or reproducibility of a napplet artifact. Signed repository state also does not make referenced code safe to execute.

Implementations

No implementation of this NAP is verified. Git and ngit are implementation targets; their existing interfaces do not establish support for this draft.

Changelog

  • bb71090 - Defined shared Git object access, commit creation, local reference transactions, remote fetch/push, optional proposals, publication receipts, and native/ngit authority boundaries.