@@ -9,6 +9,8 @@ export const PUBLISH_PROTOCOL = "cellscript-registry-publish-v1";
99export const PUBLISH_ACTION = "publish" ;
1010export const DEPLOYMENT_PROTOCOL = "cellscript-registry-deployment" ;
1111export const DEPLOYMENT_ACTION = "record_deployment" ;
12+ export const AVAILABILITY_PROTOCOL = "cellscript-registry-availability-v1" ;
13+ export const AVAILABILITY_ACTION = "set_availability" ;
1214export const REGISTRY_SCHEMA_VERSION = 1 ;
1315export const ARTIFACT_PROFILE_CONTRACT_SCHEMA = "cellscript-registry-profile-contract-v1" ;
1416export const CELLSCRIPT_EDITION = "2026" ;
@@ -124,6 +126,22 @@ export interface DeploymentPayload {
124126 cli_version : string ;
125127}
126128
129+ export interface AvailabilityPayload {
130+ protocol : typeof AVAILABILITY_PROTOCOL ;
131+ action : typeof AVAILABILITY_ACTION ;
132+ registry_origin : string ;
133+ namespace : string ;
134+ name : string ;
135+ release : string ;
136+ availability_status : Exclude < AvailabilityStatus , "quarantined" > ;
137+ reason ?: string ;
138+ capability_key_id : string ;
139+ nonce : string ;
140+ issued_at : string ;
141+ expires_at : string ;
142+ cli_version : string ;
143+ }
144+
127145export interface RegistryVersionEntry {
128146 version : string ;
129147 tag : string ;
@@ -419,6 +437,60 @@ export function validateDeploymentPayload(
419437 } ;
420438}
421439
440+ export function validateAvailabilityPayload (
441+ input : unknown ,
442+ registryOrigin : string ,
443+ now : Date ,
444+ ) : AvailabilityPayload {
445+ const value = assertPlainObject ( input , "invalid_availability_payload" ) ;
446+ if ( requireString ( value , "protocol" ) !== AVAILABILITY_PROTOCOL || requireString ( value , "action" ) !== AVAILABILITY_ACTION ) {
447+ throw new ApiError ( 400 , "invalid_availability_action" , "availability payload has the wrong protocol or action" ) ;
448+ }
449+ if ( requireString ( value , "registry_origin" ) !== registryOrigin ) {
450+ throw new ApiError ( 400 , "invalid_registry_origin" , "availability payload registry_origin does not match this API" ) ;
451+ }
452+ const availabilityStatus = requireString ( value , "availability_status" ) ;
453+ if ( ! ( availabilityStatus === "active" || availabilityStatus === "deprecated" || availabilityStatus === "yanked" ) ) {
454+ throw new ApiError ( 400 , "invalid_publisher_availability_status" , "publishers may set availability_status to active, deprecated, or yanked" ) ;
455+ }
456+ const reason = value [ "reason" ] === undefined ? undefined : requireString ( value , "reason" ) . trim ( ) ;
457+ if ( availabilityStatus === "yanked" && ! reason ) {
458+ throw new ApiError ( 400 , "availability_reason_required" , "yanking a release requires a reason" ) ;
459+ }
460+ if ( reason && reason . length > 500 ) {
461+ throw new ApiError ( 400 , "invalid_availability_reason" , "availability reason must be no longer than 500 characters" ) ;
462+ }
463+ const capabilityKeyId = requireString ( value , "capability_key_id" ) ;
464+ if ( ! / ^ c a p _ [ 0 - 9 a - f ] { 32 } $ / . test ( capabilityKeyId ) ) {
465+ throw new ApiError ( 400 , "invalid_capability_key_id" , "capability_key_id is malformed" ) ;
466+ }
467+ const nonce = requireString ( value , "nonce" ) ;
468+ if ( ! / ^ 0 x [ 0 - 9 a - f A - F ] { 16 , } $ / . test ( nonce ) ) {
469+ throw new ApiError ( 400 , "invalid_nonce" , "nonce must be hex and at least 8 bytes" ) ;
470+ }
471+ const issuedAt = requireString ( value , "issued_at" ) ;
472+ const expiresAt = requireString ( value , "expires_at" ) ;
473+ parseTimestamp ( issuedAt , "issued_at" ) ;
474+ if ( parseTimestamp ( expiresAt , "expires_at" ) . getTime ( ) <= now . getTime ( ) ) {
475+ throw new ApiError ( 401 , "availability_payload_expired" , "availability payload has expired" ) ;
476+ }
477+ return {
478+ protocol : AVAILABILITY_PROTOCOL ,
479+ action : AVAILABILITY_ACTION ,
480+ registry_origin : registryOrigin ,
481+ namespace : validatePackageIdent ( requireString ( value , "namespace" ) , "namespace" ) ,
482+ name : validatePackageIdent ( requireString ( value , "name" ) , "name" ) ,
483+ release : validateVersion ( requireString ( value , "release" ) ) ,
484+ availability_status : availabilityStatus ,
485+ ...( reason ? { reason } : { } ) ,
486+ capability_key_id : capabilityKeyId ,
487+ nonce,
488+ issued_at : issuedAt ,
489+ expires_at : expiresAt ,
490+ cli_version : requireString ( value , "cli_version" ) ,
491+ } ;
492+ }
493+
422494export function sameCkbHash ( left : string , right : string ) : boolean {
423495 return left . replace ( / ^ 0 x / , "" ) . toLowerCase ( ) === right . replace ( / ^ 0 x / , "" ) . toLowerCase ( ) ;
424496}
0 commit comments