{"records":[{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.tag","cid":"bafyreicw23phjfsm2nibu5watqley76gpekdgfxddabugxsgfdpuv7rwei","value":{"id":"io.atcr.tag","defs":{"main":{"key":"any","type":"record","record":{"type":"object","required":["repository","tag"],"properties":{"tag":{"type":"string","maxLength":128,"description":"Tag name (e.g., 'latest', 'v1.0.0', '12-slim')"},"manifest":{"type":"string","format":"at-uri","description":"AT-URI of the manifest this tag points to (e.g., 'at://did:plc:xyz/io.atcr.manifest/abc123'). Preferred over manifestDigest for new records."},"mediaType":{"type":"string","maxLength":255,"description":"OCI media type of the manifest (e.g., 'application/vnd.oci.image.manifest.v1+json' or 'application/vnd.oci.image.index.v1+json')"},"updatedAt":{"type":"string","format":"datetime","description":"Timestamp of last tag update"},"repository":{"type":"string","maxLength":255,"description":"Repository name (e.g., 'myapp'). Scoped to user's DID."},"manifestDigest":{"type":"string","maxLength":128,"description":"DEPRECATED: Digest of the manifest (e.g., 'sha256:...'). Kept for backward compatibility with old records. New records should use 'manifest' field instead."}}},"description":"A named tag pointing to a specific manifest digest"}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.sailor.star","cid":"bafyreid4lrismiztnmflwupis7ynfgigjeb3anf2yfsj3xc3idssaskyue","value":{"id":"io.atcr.sailor.star","defs":{"main":{"key":"any","type":"record","record":{"type":"object","required":["subject","createdAt"],"properties":{"subject":{"type":"string","format":"at-uri","description":"AT URI of the repository page being starred (e.g., at://did:plc:abc/io.atcr.repo.page/myapp)"},"createdAt":{"type":"string","format":"datetime","description":"Star creation timestamp"}}},"description":"A star (like) on a container image repository. Stored in the starrer's PDS, similar to Bluesky likes. Subject is an AT URI pointing to the repo page record being starred."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.sailor.profile","cid":"bafyreiddhegnrwnt4rlgdfczxe7a45kxz5u4ke53mtakdzzqedkswedvvi","value":{"id":"io.atcr.sailor.profile","defs":{"main":{"key":"literal:self","type":"record","record":{"type":"object","required":["createdAt"],"properties":{"createdAt":{"type":"string","format":"datetime","description":"Profile creation timestamp"},"ociClient":{"type":"string","maxLength":32,"description":"Preferred client for pull commands (docker, podman, buildah, nerdctl, crane). 'none' shows the image reference only. Defaults to docker if empty.","knownValues":["docker","podman","buildah","nerdctl","crane","none"]},"updatedAt":{"type":"string","format":"datetime","description":"Profile last updated timestamp"},"defaultHold":{"type":"string","format":"did","description":"Default hold DID for blob storage. If null, user has opted out of defaults."},"registryDomain":{"type":"string","maxLength":255,"description":"Preferred registry domain for UI display. Must be one of the appview's configured registry domains. Empty means the primary (first configured) domain."},"autoRemoveUntagged":{"type":"boolean","description":"Automatically delete manifest records that become untagged after a tag overwrite. Layers are cleaned up by hold garbage collection."}}},"description":"User profile for ATCR registry. Stores preferences like default hold for blob storage."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.repo.private","cid":"bafyreidjodoewpjv5xsqim4x4bmbnd7z34mwjahnn3vtz3xacutowpx52i","value":{"id":"io.atcr.repo.private","defs":{"main":{"key":"any","name":"Private container repository","type":"space","collections":["io.atcr.repo.page","io.atcr.manifest","io.atcr.tag"],"description":"One sailor's private container repository. The sailor is the space authority and the only writer; the AppView writes on their behalf through their OAuth session. Reads are decided by the AppView as the space's managing app: the owner and the crew of the hold the repository's images live on are admitted, everyone else is refused. The page, manifest and tag records of a private repository live here instead of in the sailor's public repo, so the repository leaves no trace on the firehose."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.repo.page","cid":"bafyreigzrvi7gvltrkuet6mtvguc5xxwrq4nqs2av3vmbaakrxt2o3fzzu","value":{"id":"io.atcr.repo.page","defs":{"main":{"key":"any","type":"record","record":{"type":"object","required":["repository","createdAt","updatedAt"],"properties":{"avatar":{"type":"blob","accept":["image/png","image/jpeg","image/webp"],"maxSize":3000000,"description":"Repository avatar/icon image."},"createdAt":{"type":"string","format":"datetime","description":"Record creation timestamp"},"updatedAt":{"type":"string","format":"datetime","description":"Record last updated timestamp"},"repository":{"type":"string","maxLength":256,"description":"The name of the repository (e.g., 'myapp'). Must match the rkey."},"userEdited":{"type":"boolean","description":"Whether the description was manually edited by the user. When true, auto-population from manifest annotations is skipped on push."},"visibility":{"type":"string","default":"public","maxLength":16,"description":"Whether this repository is visible to everyone or only to the owner and the crew of the hold its images live on. Defaults to \"public\". A private repository's page, manifest and tag records live in the owner's io.atcr.repo.private space instead of their public repo.","knownValues":["public","private"]},"description":{"type":"string","maxLength":100000,"description":"Markdown README/description content for the repository page."}}},"description":"Repository page metadata including description and avatar. Users can edit this directly in their PDS to customize their repository page."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.manifest","cid":"bafyreibqqkjhn4r5nxh3jpsrtkifjfcz3xqpde52hvmjnuzse6h4xogs6q","value":{"id":"io.atcr.manifest","defs":{"main":{"key":"tid","type":"record","record":{"type":"object","required":["repository","digest","mediaType","schemaVersion","createdAt"],"properties":{"config":{"ref":"#blobReference","type":"ref","description":"Reference to image configuration blob"},"digest":{"type":"string","maxLength":128,"description":"Content digest (e.g., 'sha256:abc123...')"},"layers":{"type":"array","items":{"ref":"#blobReference","type":"ref"},"description":"Filesystem layers (for image manifests)"},"holdDid":{"type":"string","format":"did","description":"DID of the hold service where blobs are stored (e.g., 'did:web:hold01.atcr.io'). Primary reference for hold resolution."},"subject":{"ref":"#blobReference","type":"ref","description":"Optional reference to another manifest (for attestations, signatures)"},"createdAt":{"type":"string","format":"datetime","description":"Record creation timestamp"},"manifests":{"type":"array","items":{"ref":"#manifestReference","type":"ref"},"description":"Referenced manifests (for manifest lists/indexes)"},"mediaType":{"type":"string","maxLength":128,"description":"OCI media type","knownValues":["application/vnd.oci.image.manifest.v1+json","application/vnd.docker.distribution.manifest.v2+json","application/vnd.oci.image.index.v1+json","application/vnd.docker.distribution.manifest.list.v2+json"]},"repository":{"type":"string","maxLength":255,"description":"Repository name (e.g., 'myapp'). Scoped to user's DID."},"annotations":{"type":"unknown","description":"Optional OCI annotation metadata. Map of string keys to string values (e.g., org.opencontainers.image.title → 'My App')."},"holdEndpoint":{"type":"string","format":"uri","description":"Hold service endpoint URL where blobs are stored. DEPRECATED: Use holdDid instead. Kept for backward compatibility."},"manifestBlob":{"type":"blob","description":"The full OCI manifest stored as a blob in ATProto."},"schemaVersion":{"type":"integer","description":"OCI schema version (typically 2)"}}},"description":"A container image manifest following OCI specification, stored in ATProto"},"platform":{"type":"object","required":["architecture","os"],"properties":{"os":{"type":"string","maxLength":32,"description":"Operating system (e.g., 'linux', 'windows', 'darwin')"},"variant":{"type":"string","maxLength":32,"description":"Optional CPU variant (e.g., 'v7' for ARM)"},"osVersion":{"type":"string","maxLength":64,"description":"Optional OS version"},"osFeatures":{"type":"array","items":{"type":"string","maxLength":64},"description":"Optional OS features"},"architecture":{"type":"string","maxLength":32,"description":"CPU architecture (e.g., 'amd64', 'arm64', 'arm')"}},"description":"Platform information describing OS and architecture"},"blobReference":{"type":"object","required":["mediaType","size","digest"],"properties":{"size":{"type":"integer","description":"Size in bytes"},"urls":{"type":"array","items":{"type":"string","format":"uri"},"description":"Optional direct URLs to blob (for BYOS)"},"digest":{"type":"string","maxLength":128,"description":"Content digest (e.g., 'sha256:...')"},"mediaType":{"type":"string","maxLength":128,"description":"MIME type of the blob"},"annotations":{"type":"unknown","description":"Optional OCI annotation metadata. Map of string keys to string values."}},"description":"Reference to a blob stored in S3 or external storage"},"manifestReference":{"type":"object","required":["mediaType","size","digest"],"properties":{"size":{"type":"integer","description":"Size in bytes"},"digest":{"type":"string","maxLength":128,"description":"Content digest (e.g., 'sha256:...')"},"platform":{"ref":"#platform","type":"ref","description":"Platform information for this manifest"},"mediaType":{"type":"string","maxLength":128,"description":"Media type of the referenced manifest"},"annotations":{"type":"unknown","description":"Optional OCI annotation metadata. Map of string keys to string values."}},"description":"Reference to a manifest in a manifest list/index"}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.updateCrewTier","cid":"bafyreihltnsiqvt2mc6zy3yrgargiswb5hjmzcewg4qf6mrrly4zvxdwxa","value":{"id":"io.atcr.hold.updateCrewTier","defs":{"main":{"type":"procedure","input":{"schema":{"type":"object","required":["userDid","tierRank"],"properties":{"userDid":{"type":"string","format":"did","description":"DID of the crew member whose tier is being updated."},"tierRank":{"type":"integer","minimum":0,"description":"Tier rank index (0-based, maps to hold tier list by position)."}}},"encoding":"application/json"},"errors":[{"name":"AuthRequired","description":"Valid appview token required."},{"name":"UserNotFound","description":"User is not a crew member on this hold."}],"output":{"schema":{"type":"object","required":["tierName"],"properties":{"tierName":{"type":"string","maxLength":64,"description":"Resolved tier name on this hold."}}},"encoding":"application/json"},"description":"Update a crew member's tier. Only accepts requests from the trusted appview."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.subscribeScanJobs","cid":"bafyreiba5mul5r3bljixvyr2ibgbxts6qpwu5o4j64ivatlthcetdcno4m","value":{"id":"io.atcr.hold.subscribeScanJobs","defs":{"main":{"type":"subscription","errors":[{"name":"InvalidSecret","description":"Scanner shared secret is invalid"}],"message":{"schema":{"refs":["#scanJob","#scanResult"],"type":"union"}},"parameters":{"type":"params","properties":{"cursor":{"type":"integer","description":"Sequence number to resume from. If omitted, starts from latest. Use -1 to receive only new jobs."},"workers":{"type":"integer","maximum":32,"minimum":1,"description":"How many scans this scanner runs concurrently. The hold keeps this many jobs in flight for the connection. Omitted or unusable means one."},"instance":{"type":"string","maxLength":64,"description":"Stable identity of the scanner process, sent on every connect. A scanner that reconnects with the same value resumes the jobs it was holding when the connection dropped, instead of having them offered to another scanner. Omitted means the hold assigns a per-connection identity and the scanner's in-flight work is reclaimed rather than resumed."}}},"description":"Subscribe to vulnerability scan jobs via WebSocket. Scanners connect to receive pending scan jobs and send back results. Authenticated via shared secret (query parameter or X-Scanner-Secret header)."},"scanJob":{"type":"object","required":["type","seq","digest","repository","userDid","holdDid","holdEndpoint"],"properties":{"seq":{"type":"integer","description":"Monotonic sequence number for cursor-based resumption"},"tag":{"type":"string","maxLength":256,"description":"Optional tag that triggered the scan"},"type":{"type":"string","const":"scan_job","maxLength":32,"description":"Message type discriminator"},"digest":{"type":"string","maxLength":128,"description":"Manifest digest (e.g., sha256:abc123...)"},"holdDid":{"type":"string","format":"did","description":"DID of the hold where the image is stored"},"userDid":{"type":"string","format":"did","description":"DID of the image owner"},"priority":{"type":"integer","description":"Scan priority (lower = higher priority). Tier-based scheduling."},"repository":{"type":"string","maxLength":256,"description":"Repository name (e.g., myapp)"},"holdEndpoint":{"type":"string","format":"uri","description":"HTTP endpoint of the hold for blob downloads"}},"description":"A scan job dispatched from hold to scanner. Sent as a JSON WebSocket message."},"scanResult":{"type":"object","required":["type","digest","summary"],"properties":{"sbom":{"type":"bytes","maxLength":104857600,"description":"SBOM blob (SPDX JSON format, max 100MB)"},"type":{"type":"string","const":"scan_result","maxLength":32,"description":"Message type discriminator"},"error":{"type":"string","maxLength":1024,"description":"Error message if scan failed"},"digest":{"type":"string","maxLength":128,"description":"Manifest digest that was scanned"},"summary":{"ref":"#vulnSummary","type":"ref","description":"Vulnerability count summary"},"vulnReport":{"type":"bytes","maxLength":104857600,"description":"Grype vulnerability report blob (JSON, max 100MB)"},"scannerVersion":{"type":"string","maxLength":64,"description":"Scanner version string"}},"description":"A scan result sent from scanner back to hold. Sent as a JSON WebSocket message."},"vulnSummary":{"type":"object","required":["critical","high","medium","low","total"],"properties":{"low":{"type":"integer","minimum":0,"description":"Count of low severity vulnerabilities"},"high":{"type":"integer","minimum":0,"description":"Count of high severity vulnerabilities"},"total":{"type":"integer","minimum":0,"description":"Total vulnerability count"},"medium":{"type":"integer","minimum":0,"description":"Count of medium severity vulnerabilities"},"critical":{"type":"integer","minimum":0,"description":"Count of critical severity vulnerabilities"}}}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.stats.daily","cid":"bafyreiciooyfii5l3gfjg5qm5nlkcgimwynwtykhr2lpoechikehdo6iuu","value":{"id":"io.atcr.hold.stats.daily","defs":{"main":{"key":"any","type":"record","record":{"type":"object","required":["ownerDid","repository","date","pullCount","pushCount","updatedAt"],"properties":{"date":{"type":"string","maxLength":10,"description":"Date in YYYY-MM-DD format (UTC)"},"ownerDid":{"type":"string","format":"did","description":"DID of the image owner (e.g., did:plc:xyz123)"},"pullCount":{"type":"integer","minimum":0,"description":"Number of manifest downloads on this date"},"pushCount":{"type":"integer","minimum":0,"description":"Number of manifest uploads on this date"},"updatedAt":{"type":"string","format":"datetime","description":"RFC3339 timestamp of when this record was last updated"},"repository":{"type":"string","maxLength":256,"description":"Repository name (e.g., myapp)"}}},"description":"Daily repository statistics stored in the hold's embedded PDS. Tracks pull/push counts per owner+repository+date combination. Record key is deterministic: base32(sha256(ownerDID + \"/\" + repository + \"/\" + date)[:16]). Complements cumulative io.atcr.hold.stats records by providing daily granularity for trend charts."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.stats","cid":"bafyreieslim3xwnekf365z2sdrli24tzhy27badelazvuhop56enuinra4","value":{"id":"io.atcr.hold.stats","defs":{"main":{"key":"any","type":"record","record":{"type":"object","required":["ownerDid","repository","pullCount","pushCount","updatedAt"],"properties":{"lastPull":{"type":"string","format":"datetime","description":"RFC3339 timestamp of last pull"},"lastPush":{"type":"string","format":"datetime","description":"RFC3339 timestamp of last push"},"ownerDid":{"type":"string","format":"did","description":"DID of the image owner (e.g., did:plc:xyz123)"},"pullCount":{"type":"integer","minimum":0,"description":"Number of manifest downloads"},"pushCount":{"type":"integer","minimum":0,"description":"Number of manifest uploads"},"updatedAt":{"type":"string","format":"datetime","description":"RFC3339 timestamp of when this record was last updated"},"repository":{"type":"string","maxLength":256,"description":"Repository name (e.g., myapp)"}}},"description":"Repository statistics stored in the hold's embedded PDS. Tracks pull/push counts per owner+repository combination. Record key is deterministic: base32(sha256(ownerDID + \"/\" + repository)[:16])."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.space","cid":"bafyreighfmvc22zamjs5gyv6yswiy5o3rc4f7li4mn2vm7mmtefnqzzpru","value":{"id":"io.atcr.hold.space","defs":{"main":{"key":"literal:self","name":"ATCR hold","type":"space","collections":["io.atcr.hold.crew","io.atcr.hold.layer","io.atcr.hold.image.config","io.atcr.hold.scan","io.atcr.hold.stats","io.atcr.hold.stats.daily"],"description":"A hold's permissioned space. The hold is the space authority and the only writer; readers are its crew (private hold) or any sailor with a valid delegation token (public hold). Every record the hold authors about a sailor lives here: crew membership, layer inventory, image configs, vulnerability scans and push/pull counts. The hold's public repo keeps only its captain record, its Bluesky posts and its profiles."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.scan","cid":"bafyreidqtmx2ivfrmj5cfki2zej5fgjqux4svdv3rrkv2evs5f2jdld5yi","value":{"id":"io.atcr.hold.scan","defs":{"main":{"key":"any","type":"record","record":{"type":"object","required":["manifest","repository","userDid","critical","high","medium","low","total","scannerVersion","scannedAt"],"properties":{"low":{"type":"integer","minimum":0,"description":"Count of low severity vulnerabilities"},"high":{"type":"integer","minimum":0,"description":"Count of high severity vulnerabilities"},"total":{"type":"integer","minimum":0,"description":"Total vulnerability count"},"medium":{"type":"integer","minimum":0,"description":"Count of medium severity vulnerabilities"},"reason":{"type":"string","maxLength":256,"description":"Optional human-readable explanation for non-ok status (e.g. 'unscannable artifact type application/vnd.cncf.helm.config.v1+json')."},"status":{"type":"string","maxLength":32,"description":"Outcome of the scan attempt. 'ok' (or omitted, for back-compat) means the scanner produced an SBOM. 'failed' means the scanner ran but errored. 'skipped' means the scanner intentionally bypassed this artifact type (e.g. helm charts).","knownValues":["ok","failed","skipped"]},"userDid":{"type":"string","format":"did","description":"DID of the image owner"},"critical":{"type":"integer","minimum":0,"description":"Count of critical severity vulnerabilities"},"manifest":{"type":"string","format":"at-uri","description":"AT-URI of the scanned manifest (e.g., at://did:plc:xyz/io.atcr.manifest/abc123...)"},"sbomBlob":{"type":"blob","accept":["application/spdx+json"],"description":"SBOM blob (SPDX JSON format) uploaded to the hold's blob storage"},"scannedAt":{"type":"string","format":"datetime","description":"RFC3339 timestamp of when the scan completed"},"repository":{"type":"string","maxLength":256,"description":"Repository name (e.g., myapp)"},"scannerVersion":{"type":"string","maxLength":64,"description":"Version of the scanner that produced this result (e.g., atcr-scanner-v1.0.0)"},"vulnReportBlob":{"type":"blob","accept":["application/vnd.atcr.vulnerabilities+json"],"description":"Grype vulnerability report blob (JSON) with full CVE details"}}},"description":"Vulnerability scan results for a container manifest. Stored in the hold's embedded PDS. Record key is deterministic: the manifest digest hex without the 'sha256:' prefix, so re-scans upsert the existing record."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.requestCrew","cid":"bafyreibq6q4g23lovz5yhtc3sygycaxz2zsrs2bemws2awsjuw3ixef27y","value":{"id":"io.atcr.hold.requestCrew","defs":{"main":{"type":"procedure","input":{"schema":{"type":"object","properties":{"role":{"type":"string","default":"member","maxLength":32,"description":"Requested role (default: 'member')"},"permissions":{"type":"array","items":{"type":"string","maxLength":64},"description":"Requested permissions (default: ['blob:read', 'blob:write'])"}}},"encoding":"application/json"},"errors":[{"name":"AuthRequired"},{"name":"RegistrationDisabled"}],"output":{"schema":{"type":"object","required":["status"],"properties":{"cid":{"type":"string","format":"cid","description":"CID of the crew record"},"uri":{"type":"string","format":"at-uri","description":"AT-URI of the crew record (if created or already exists)"},"status":{"type":"string","maxLength":32,"description":"Result status","knownValues":["created","already_member"]},"message":{"type":"string","maxLength":256,"description":"Human-readable status message"}}},"encoding":"application/json"},"description":"Request crew membership for the hold. Authorization depends on the captain record: if allowAllCrew is true, any authenticated user can join; otherwise, only the owner can join."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.purgeManifest","cid":"bafyreibnfl65wlbedwsipi5lkbmonkqp7wtj2meqsikirv42ooocatl33a","value":{"id":"io.atcr.hold.purgeManifest","defs":{"main":{"type":"procedure","input":{"schema":{"type":"object","required":["manifestUri"],"properties":{"manifestUri":{"type":"string","format":"at-uri","description":"AT-URI of the manifest record, e.g. at://did:plc:xyz/io.atcr.manifest/<digest>"}}},"encoding":"application/json"},"errors":[{"name":"AuthRequired"},{"name":"InvalidRequest"},{"name":"PurgeFailed"}],"output":{"schema":{"type":"object","required":["success","layersDeleted","scanDeleted","imageConfigDeleted"],"properties":{"success":{"type":"boolean","description":"Whether the purge completed successfully"},"scanDeleted":{"type":"boolean","description":"Whether a scan record was deleted"},"layersDeleted":{"type":"integer","description":"Number of layer records deleted"},"imageConfigDeleted":{"type":"boolean","description":"Whether an image config record was deleted"}}},"encoding":"application/json"},"description":"Purge layer, scan, and image-config records associated with a manifest. Used by the appview when a user deletes a manifest, and by the hold's own labeler subscriber on takedown receipt. Idempotent: missing records are not errors. Does not delete S3 blobs (GC handles that based on remaining references)."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.notifyManifest","cid":"bafyreihuiwxnikwoai3qfuveusjn5ibyt3kqzzkmzoeqqqyd6obugb4axy","value":{"id":"io.atcr.hold.notifyManifest","defs":{"main":{"type":"procedure","input":{"schema":{"type":"object","required":["repository","userDid","manifestDigest"],"properties":{"tag":{"type":"string","maxLength":128,"description":"Image tag (optional, required for Bluesky posts)"},"userDid":{"type":"string","format":"did","description":"DID of the image owner"},"manifest":{"ref":"#manifestInfo","type":"ref","description":"The pushed manifest. Required for 'push'; absent for 'pull' and 'visibility'."},"operation":{"type":"string","default":"push","maxLength":16,"description":"Operation type (defaults to 'push' for backward compatibility). 'visibility' records a new visibility for an already pushed manifest and does nothing else; it carries no manifest body and moves no counter.","knownValues":["push","pull","visibility"]},"repository":{"type":"string","maxLength":256,"description":"Image repository name"},"visibility":{"type":"string","default":"public","maxLength":16,"description":"Visibility of the sailor repository this manifest belongs to. The hold records it against the manifest and enforces it on OCI blob reads: a layer or image config blob named only by private manifests is served to the hold's owner and crew only, even on a public hold. Absent means public. Re-sending the notification with a different value is how a repository is flipped. Honoured on a push only; ignored on a pull, whose token belongs to the puller rather than the repository owner.","knownValues":["public","private"]},"manifestDigest":{"type":"string","maxLength":128,"description":"Manifest digest for building layer record AT-URIs"}}},"encoding":"application/json"},"errors":[{"name":"InvalidOperation"},{"name":"UserMismatch"},{"name":"QuotaExceeded"}],"output":{"schema":{"type":"object","required":["success","operation","statsUpdated"],"properties":{"postUri":{"type":"string","format":"at-uri","description":"AT-URI of the created Bluesky post (if postCreated is true)"},"success":{"type":"boolean","description":"Whether the operation completed successfully"},"operation":{"type":"string","maxLength":16,"description":"The operation that was performed ('push' or 'pull')"},"postCreated":{"type":"boolean","description":"Whether a Bluesky post was created (push only)"},"statsUpdated":{"type":"boolean","description":"Whether stats were successfully updated"},"layersCreated":{"type":"integer","description":"Number of layer records created (push only)"}}},"encoding":"application/json"},"description":"Notify hold about a manifest push, pull or visibility change. For pushes: records the repository's visibility and the manifest's blobs, creates layer records and optionally posts to Bluesky. For pulls: just increments stats. Pushes and pulls always increment the matching count. For visibility changes: records the new visibility and nothing else, so no counter moves and no manifest body is needed."},"blobInfo":{"type":"object","properties":{"size":{"type":"integer"},"digest":{"type":"string","maxLength":128},"mediaType":{"type":"string","maxLength":256}}},"layerInfo":{"type":"object","properties":{"size":{"type":"integer"},"digest":{"type":"string","maxLength":128},"mediaType":{"type":"string","maxLength":256}}},"manifestInfo":{"type":"object","properties":{"config":{"ref":"#blobInfo","type":"ref"},"layers":{"type":"array","items":{"ref":"#layerInfo","type":"ref"}},"subject":{"ref":"#blobInfo","type":"ref","description":"Manifest this artifact refers to (attestations, signatures, SBOMs). Absent for ordinary images."},"manifests":{"type":"array","items":{"ref":"#childManifestInfo","type":"ref"},"description":"Child manifests for multi-arch images"},"mediaType":{"type":"string","maxLength":256,"description":"OCI media type"}},"description":"OCI manifest information"},"platformInfo":{"type":"object","properties":{"os":{"type":"string","maxLength":64},"architecture":{"type":"string","maxLength":64}}},"childManifestInfo":{"type":"object","properties":{"size":{"type":"integer"},"digest":{"type":"string","maxLength":128},"platform":{"ref":"#platformInfo","type":"ref"},"mediaType":{"type":"string","maxLength":256}}}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.listTiers","cid":"bafyreieotz4pfttohhfbc7ro6rndtwilhdgsnpkl7qjg3h2ktgpvkmptli","value":{"id":"io.atcr.hold.listTiers","defs":{"main":{"type":"query","output":{"schema":{"type":"object","required":["tiers"],"properties":{"tiers":{"type":"array","items":{"ref":"#defs/tierInfo","type":"ref"}}}},"encoding":"application/json"},"description":"List the hold's available tiers with storage quotas (no pricing info)."},"tierInfo":{"type":"object","required":["name","quotaBytes","quotaFormatted","scanOnPush"],"properties":{"name":{"type":"string","maxLength":64,"description":"Tier name."},"quotaBytes":{"type":"integer","description":"Storage quota in bytes."},"scanOnPush":{"type":"boolean","description":"Whether pushing triggers an immediate vulnerability scan."},"quotaFormatted":{"type":"string","maxLength":32,"description":"Human-readable quota (e.g. '5.0 GB')."}}}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.layer","cid":"bafyreifmmd7tmj4i3iavissbi4fvm3goi4ntjyylu3eu3yx3uoatvdc3pu","value":{"id":"io.atcr.hold.layer","defs":{"main":{"key":"tid","type":"record","record":{"type":"object","required":["digest","size","mediaType","manifest","userDid","createdAt"],"properties":{"size":{"type":"integer","description":"Size in bytes"},"digest":{"type":"string","maxLength":128,"description":"Layer digest (e.g., sha256:abc123...)"},"userDid":{"type":"string","format":"did","description":"DID of user who uploaded this layer"},"manifest":{"type":"string","format":"at-uri","description":"AT-URI of the manifest that included this layer (e.g., at://did:plc:xyz/io.atcr.manifest/abc123)"},"createdAt":{"type":"string","format":"datetime","description":"RFC3339 timestamp of when the layer was uploaded"},"mediaType":{"type":"string","maxLength":128,"description":"Media type (e.g., application/vnd.oci.image.layer.v1.tar+gzip)"}}},"description":"Represents metadata about a container layer stored in the hold. Stored in the hold's embedded PDS for tracking and analytics."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.initiateUpload","cid":"bafyreigw67w7huubzrxogure2zmyuiqqdmoydnozf46cwfsaltgad7nspi","value":{"id":"io.atcr.hold.initiateUpload","defs":{"main":{"type":"procedure","input":{"schema":{"type":"object","required":["digest"],"properties":{"digest":{"type":"string","maxLength":128,"description":"The blob digest (e.g., sha256:abc123...)"}}},"encoding":"application/json"},"errors":[{"name":"InvalidDigest"}],"output":{"schema":{"type":"object","required":["uploadId"],"properties":{"uploadId":{"type":"string","maxLength":256,"description":"Unique identifier for this upload session"}}},"encoding":"application/json"},"description":"Start a new multipart upload session for an OCI blob. Returns an uploadId to be used with subsequent part upload and completion endpoints."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.image.config","cid":"bafyreigvyvojmjxxrx6ztudmanuwgishc2s6styr7oalsmbbhg4udr7pqa","value":{"id":"io.atcr.hold.image.config","defs":{"main":{"key":"any","type":"record","record":{"type":"object","required":["manifest","configJson","createdAt"],"properties":{"manifest":{"type":"string","format":"at-uri","description":"AT-URI of the manifest this config belongs to"},"createdAt":{"type":"string","format":"datetime","description":"RFC3339 timestamp of when the config was stored"},"configJson":{"type":"string","maxLength":1000000,"description":"Raw OCI image config JSON blob"}}},"description":"OCI image configuration for a container manifest. Stored in the hold's embedded PDS. Record key is the manifest digest hex without the 'sha256:' prefix (deterministic, one per manifest). Contains the full OCI config JSON including history (Dockerfile commands), environment variables, entrypoint, labels, etc."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.getQuota","cid":"bafyreid3xkdn4syxitk7qxhehczrihtkzakqouidzrssvvlejqkeipmasy","value":{"id":"io.atcr.hold.getQuota","defs":{"main":{"type":"query","errors":[{"name":"InvalidUserDid"}],"output":{"schema":{"type":"object","required":["userDid","uniqueBlobs","totalSize"],"properties":{"tier":{"type":"string","maxLength":32,"description":"Quota tier name (e.g., 'deckhand', 'bosun', 'quartermaster')"},"limit":{"type":"integer","description":"Storage limit in bytes (absent if unlimited)"},"userDid":{"type":"string","format":"did","description":"DID of the user"},"totalSize":{"type":"integer","description":"Total size in bytes of unique blobs"},"uniqueBlobs":{"type":"integer","description":"Number of unique blob digests"}}},"encoding":"application/json"},"parameters":{"type":"params","required":["userDid"],"properties":{"userDid":{"type":"string","format":"did","description":"DID of the user to get quota for"}}},"description":"Get storage quota information for a user. Calculates total unique blob storage by iterating layer records and deduplicating by digest."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.getPartUploadUrl","cid":"bafyreidbpbaaozowexogddd3j24dhz472zjojs3df3ucqjruzfaxhx2uay","value":{"id":"io.atcr.hold.getPartUploadUrl","defs":{"main":{"type":"procedure","input":{"schema":{"type":"object","required":["uploadId","partNumber"],"properties":{"uploadId":{"type":"string","maxLength":256,"description":"Upload session ID from initiateUpload"},"partNumber":{"type":"integer","minimum":1,"description":"Part sequence number (1-indexed)"}}},"encoding":"application/json"},"errors":[{"name":"InvalidUploadId"},{"name":"InvalidPartNumber"}],"output":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"URL to PUT the part data to"},"method":{"type":"string","default":"PUT","maxLength":16,"description":"HTTP method to use (usually PUT)"},"headers":{"type":"unknown","description":"Additional headers required for the request (e.g., content-type)"}}},"encoding":"application/json"},"description":"Get a presigned URL or endpoint info for uploading a specific part of a multipart upload."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.exportUserData","cid":"bafyreicstoas6yz7a2qsf5shobx4olu523qqy2uc7opjaewopain7kemey","value":{"id":"io.atcr.hold.exportUserData","defs":{"main":{"type":"query","errors":[{"name":"AuthRequired"}],"output":{"schema":{"type":"object","required":["exportedAt","holdDid","userDid","isCaptain","layerRecords","statsRecords","blueskyPosts"],"properties":{"holdDid":{"type":"string","format":"did","description":"DID of this hold service"},"userDid":{"type":"string","format":"did","description":"DID of the user whose data was exported"},"isCaptain":{"type":"boolean","description":"Whether the user is the captain (owner) of this hold"},"crewRecord":{"ref":"#crewExport","type":"ref","description":"User's crew record (if they are a crew member)"},"exportedAt":{"type":"string","format":"datetime","description":"RFC3339 timestamp of when the export was generated"},"blueskyPosts":{"type":"array","items":{"ref":"#postExport","type":"ref"},"description":"Bluesky posts that mention the user"},"layerRecords":{"type":"array","items":{"ref":"#layerExport","type":"ref"},"description":"Layer records uploaded by the user"},"statsRecords":{"type":"array","items":{"ref":"#statsExport","type":"ref"},"description":"Repository stats records owned by the user"}}},"encoding":"application/json"},"parameters":{"type":"params","properties":{}},"description":"Export all user data from this hold (GDPR compliance). Returns all records stored on this hold's PDS that reference the authenticated user's DID."},"crewExport":{"type":"object","properties":{"role":{"type":"string","maxLength":32},"tier":{"type":"string","maxLength":32},"addedAt":{"type":"string","format":"datetime"},"permissions":{"type":"array","items":{"type":"string","maxLength":64}}}},"postExport":{"type":"object","properties":{"uri":{"type":"string","format":"at-uri"},"text":{"type":"string","maxLength":30000,"maxGraphemes":3000},"createdAt":{"type":"string","format":"datetime"}}},"layerExport":{"type":"object","properties":{"size":{"type":"integer"},"digest":{"type":"string","maxLength":128},"manifest":{"type":"string","format":"at-uri"},"createdAt":{"type":"string","format":"datetime"},"mediaType":{"type":"string","maxLength":256}}},"statsExport":{"type":"object","properties":{"lastPull":{"type":"string","format":"datetime"},"lastPush":{"type":"string","format":"datetime"},"pullCount":{"type":"integer"},"pushCount":{"type":"integer"},"updatedAt":{"type":"string","format":"datetime"},"repository":{"type":"string","maxLength":256}}}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.deleteUserData","cid":"bafyreieica7kk7eghdrdvofbmndvvjv7cse2p4hg6uzqs4lo44wcpsyvcu","value":{"id":"io.atcr.hold.deleteUserData","defs":{"main":{"type":"procedure","input":{"schema":{"type":"object","properties":{}},"encoding":"application/json"},"errors":[{"name":"AuthRequired"},{"name":"DeletionFailed"}],"output":{"schema":{"type":"object","required":["success","crewDeleted","layersDeleted","statsDeleted"],"properties":{"success":{"type":"boolean","description":"Whether the deletion completed successfully"},"crewDeleted":{"type":"boolean","description":"Whether the user's crew record was deleted (false if user is captain)"},"statsDeleted":{"type":"integer","description":"Number of stats records deleted"},"layersDeleted":{"type":"integer","description":"Number of layer records deleted"}}},"encoding":"application/json"},"description":"Delete all user data from this hold (GDPR compliance). Deletes crew record (if not captain), layer records, and stats records. Does NOT delete actual blob data from S3 - only PDS records."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.crew","cid":"bafyreicnukay6rqlxpqfw5qisrfsybj2qhxprmxwtkdzise5a2iokmnldy","value":{"id":"io.atcr.hold.crew","defs":{"main":{"key":"any","type":"record","record":{"type":"object","required":["member","role","permissions","addedAt"],"properties":{"role":{"type":"string","maxLength":32,"description":"Member's role in the hold","knownValues":["owner","admin","write","read"]},"tier":{"type":"string","maxLength":32,"description":"Optional tier for quota limits (e.g., 'deckhand', 'bosun', 'quartermaster'). If empty, uses defaults.new_crew_tier from quotas.yaml."},"member":{"type":"string","format":"did","description":"DID of the crew member"},"addedAt":{"type":"string","format":"datetime","description":"RFC3339 timestamp of when the member was added"},"permissions":{"type":"array","items":{"type":"string","maxLength":64},"description":"Specific permissions granted to this member"}}},"description":"Crew member in a hold's embedded PDS. Grants access permissions to push blobs to the hold. Stored in the hold's embedded PDS (one record per member)."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.completeUpload","cid":"bafyreigbfwzy6rrzrfovlceev2me7wvcw4cgul5f23daxylzzo5776ifry","value":{"id":"io.atcr.hold.completeUpload","defs":{"main":{"type":"procedure","input":{"schema":{"type":"object","required":["uploadId","digest","parts"],"properties":{"parts":{"type":"array","items":{"ref":"#partInfo","type":"ref"},"description":"List of uploaded parts with their ETags"},"digest":{"type":"string","maxLength":128,"description":"Final blob digest (e.g., sha256:abc123...)"},"uploadId":{"type":"string","maxLength":256,"description":"Upload session ID from initiateUpload"}}},"encoding":"application/json"},"errors":[{"name":"InvalidUploadId"},{"name":"InvalidDigest"},{"name":"MissingParts"},{"name":"CompletionFailed"}],"output":{"schema":{"type":"object","required":["status","digest"],"properties":{"digest":{"type":"string","maxLength":128,"description":"The digest of the completed blob"},"status":{"type":"string","const":"completed","maxLength":16,"description":"Always 'completed' on success"}}},"encoding":"application/json"},"description":"Finalize a multipart upload and move the assembled blob to its final location."},"partInfo":{"type":"object","required":["partNumber","etag"],"properties":{"etag":{"type":"string","maxLength":256,"description":"ETag returned when the part was uploaded"},"partNumber":{"type":"integer","minimum":1,"description":"Part sequence number (1-indexed)"}},"description":"Information about a completed upload part"}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.captain","cid":"bafyreichjwhf6ugvbespowvc7jyc4zzq2qnzmlp5nalx6m3fenoaq7msoa","value":{"id":"io.atcr.hold.captain","defs":{"main":{"key":"literal:self","type":"record","record":{"type":"object","required":["owner","public","allowAllCrew","enableBlueskyPosts","deployedAt"],"properties":{"owner":{"type":"string","format":"did","description":"DID of the hold owner"},"public":{"type":"boolean","description":"Whether this hold allows public blob reads (pulls) without authentication"},"region":{"type":"string","maxLength":64,"description":"S3 region where blobs are stored"},"successor":{"type":"string","format":"did","description":"DID of successor hold for migration redirect"},"deployedAt":{"type":"string","format":"datetime","description":"RFC3339 timestamp of when the hold was deployed"},"allowAllCrew":{"type":"boolean","description":"Allow any authenticated user to register as crew"},"enableBlueskyPosts":{"type":"boolean","description":"Enable Bluesky posts when manifests are pushed"}}},"description":"Represents the hold's ownership and metadata. Stored as a singleton record at rkey 'self' in the hold's embedded PDS."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.hold.abortUpload","cid":"bafyreihcli3wbtkyrrpiod62izlyqnwmjdd77e4y6ycaywz6vpdeb46ima","value":{"id":"io.atcr.hold.abortUpload","defs":{"main":{"type":"procedure","input":{"schema":{"type":"object","required":["uploadId"],"properties":{"uploadId":{"type":"string","maxLength":256,"description":"Upload session ID from initiateUpload"}}},"encoding":"application/json"},"errors":[{"name":"InvalidUploadId"},{"name":"AbortFailed"}],"output":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","const":"aborted","maxLength":16,"description":"Always 'aborted' on success"}}},"encoding":"application/json"},"description":"Cancel a multipart upload and cleanup any temporary data."}},"$type":"com.atproto.lexicon.schema","lexicon":1}},{"uri":"at://did:web:lexicon.atcr.io/com.atproto.lexicon.schema/io.atcr.authFullApp","cid":"bafyreic4alq6enwhdvcy5smx3rvq6utemlu2rlae3c5sgs6wbdppr2od3q","value":{"id":"io.atcr.authFullApp","defs":{"main":{"type":"permission-set","title":"AT Container Registry","detail":"Push and pull container images to the ATProto Container Registry. Includes creating and managing image manifests, tags, and repository settings.","permissions":[{"type":"permission","action":["create","update","delete"],"resource":"repo","collection":["io.atcr.manifest","io.atcr.tag","io.atcr.sailor.star","io.atcr.sailor.profile","io.atcr.repo.page"]}],"title:langs":{},"detail:langs":{}}},"$type":"com.atproto.lexicon.schema","lexicon":1}}],"cursor":"io.atcr.authFullApp"}