OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / blind-protocol / abi-registry.js

612 lines · javascript ref:
import b4a from 'b4a'
import c from 'compact-encoding'
import {
  ADMISSION_COST_RULES,
  ADMISSION_CONFORMANCE_CLASS,
  ADMISSION_MODE,
  AGE_BAND,
  ALL_OPERATION_BITS,
  ABI_STATUS,
  CELL_SIZE_CLASS,
  CELL_RECEIPT_RESULT,
  CLOCK_UNSAFE_OPERATION_BITS,
  CONTROL_CHANNEL_ID_TYPE,
  CORE_SESSION_CLASS,
  CORE_ACK_RESULT,
  COST_CLASS_RULE_ID,
  DRAINING_OPERATION_BITS,
  DISPATCH_LIMITS,
  DURABILITY_PROFILE_ID,
  DURABILITY_RPO_BAND,
  DURABILITY_RTO_BAND,
  DOMAIN_PURPOSE,
  DOMAIN_RECIPE,
  DOMAIN_REGISTRY,
  ENDPOINT_ROLE,
  ENDPOINT_LIMITS,
  ERROR_CODE,
  ERROR_PROFILE_ID,
  ERROR_PROFILE_ROWS,
  ERROR_RETRY_AFTER_MODE,
  FAMILY,
  FAMILY_ROUTES,
  FORWARD_CLOSE_KIND,
  FORWARD_CIRCUIT_CLASS,
  FRAME_KIND,
  IMPLEMENTED_SCHEMAS,
  INBOX_APPEND_AUTH_MODE,
  INBOX_APPEND_RESULT,
  INBOX_FRAME_CLASS,
  INBOX_MANAGE_OPERATION,
  INBOX_RECEIPT_RESULT,
  LEASE_CLASS_EPOCHS,
  OPERATION,
  OPERATION_CAP_ROWS,
  OPERATION_PROFILE_ROWS,
  OHTTP_TRANSPORT_ERROR_CODE,
  OHTTP_TRANSPORT_ERROR_ROWS,
  OHTTP_DELIVERY_BOUNDARY,
  OHTTP_RETRY_ACTION,
  OPERATION_COUNT,
  OUTER_CLASS,
  PROTOCOL,
  PRIVACY_PROFILE,
  PUBLIC_PROFILE_LIMITS,
  REQUEST_COMMITMENT_DOMAIN_ID,
  REDUNDANCY_CLASS,
  RESULT_SIGNATURE_DOMAIN_ID,
  SCHEMA_CATEGORY,
  STREAM_TRANSITION,
  STREAM_WIRE_CLASS,
  STORE_LIFECYCLE_STATE,
  TRANSPORT_EXPORTER_ID,
  TRANSPORT_ID,
  TRANSPORT_SUPPORT,
  HEALTH_CLOCK_STATE,
  HEALTH_INTEGRITY_STATE,
  HEALTH_REBALANCE_STATE,
  AUXILIARY_SIGNATURE_DOMAIN_ID,
  assertReleaseReady,
  draftSchemaId,
  schemaCategory
} from './registry.js'
import {
  admissionCostRuleV1,
  domainRegistryEntryV1,
  errorProfileEntryV1,
  operationProfileV1
} from './schemas.js'

function encodeWith (encoding, value) {
  const state = { start: 0, end: 0, buffer: null }
  encoding.preencode(state, value)
  state.buffer = b4a.alloc(state.end)
  state.start = 0
  encoding.encode(state, value)
  return state.buffer
}

function listEncoding (itemEncoding) {
  return {
    preencode (state, values) {
      c.uint.preencode(state, values.length)
      for (const value of values) itemEncoding.preencode(state, value)
    },
    encode (state, values) {
      c.uint.encode(state, values.length)
      for (const value of values) itemEncoding.encode(state, value)
    }
  }
}

const nameIdEncoding = {
  preencode (state, value) {
    c.uint.preencode(state, value.id)
    c.string.preencode(state, value.name)
  },
  encode (state, value) {
    c.uint.encode(state, value.id)
    c.string.encode(state, value.name)
  }
}

const classEncoding = {
  preencode (state, value) {
    c.uint.preencode(state, value.id)
    c.uint.preencode(state, value.value)
  },
  encode (state, value) {
    c.uint.encode(state, value.id)
    c.uint.encode(state, value.value)
  }
}

const namedValueEncoding = {
  preencode (state, value) {
    c.string.preencode(state, value.name)
    c.uint.preencode(state, value.value)
  },
  encode (state, value) {
    c.string.encode(state, value.name)
    c.uint.encode(state, value.value)
  }
}

const forwardCircuitClassEncoding = {
  preencode (state, value) {
    c.uint.preencode(state, value.id)
    c.uint.preencode(state, value.grantedInitialWindow)
    c.uint.preencode(state, value.maxCircuitBytes)
    c.uint.preencode(state, value.idleMillis)
    c.uint.preencode(state, value.lifetimeMillis)
  },
  encode (state, value) {
    c.uint.encode(state, value.id)
    c.uint.encode(state, value.grantedInitialWindow)
    c.uint.encode(state, value.maxCircuitBytes)
    c.uint.encode(state, value.idleMillis)
    c.uint.encode(state, value.lifetimeMillis)
  }
}

const coreSessionClassEncoding = {
  preencode (state, value) {
    c.uint.preencode(state, value.id)
    c.uint.preencode(state, value.maxSessionBytes)
    c.uint.preencode(state, value.idleMillis)
    c.uint.preencode(state, value.lifetimeMillis)
  },
  encode (state, value) {
    c.uint.encode(state, value.id)
    c.uint.encode(state, value.maxSessionBytes)
    c.uint.encode(state, value.idleMillis)
    c.uint.encode(state, value.lifetimeMillis)
  }
}

const operationCapEncoding = {
  preencode (state, value) {
    c.uint.preencode(state, value.familyId)
    c.uint.preencode(state, value.operationId)
    c.uint.preencode(state, value.requestSchemaId)
    c.uint.preencode(state, value.resultSchemaId)
    c.uint.preencode(state, value.maxRequestBodyBytes)
    c.uint.preencode(state, value.maxResultBodyBytes)
  },
  encode (state, value) {
    c.uint.encode(state, value.familyId)
    c.uint.encode(state, value.operationId)
    c.uint.encode(state, value.requestSchemaId)
    c.uint.encode(state, value.resultSchemaId)
    c.uint.encode(state, value.maxRequestBodyBytes)
    c.uint.encode(state, value.maxResultBodyBytes)
  }
}

const operationBitEncoding = {
  preencode (state, value) {
    c.uint.preencode(state, value.familyId)
    c.uint.preencode(state, value.operationId)
    c.uint.preencode(state, value.ordinal)
    c.uint.preencode(state, value.bit)
  },
  encode (state, value) {
    c.uint.encode(state, value.familyId)
    c.uint.encode(state, value.operationId)
    c.uint.encode(state, value.ordinal)
    c.uint.encode(state, value.bit)
  }
}

const ohttpTransportErrorProfileEncoding = {
  preencode (state, value) {
    c.uint.preencode(state, value.code)
    c.uint.preencode(state, value.protectedStatus)
    c.uint.preencode(state, value.deliveryBoundary)
    c.uint.preencode(state, value.retryAction)
  },
  encode (state, value) {
    c.uint.encode(state, value.code)
    c.uint.encode(state, value.protectedStatus)
    c.uint.encode(state, value.deliveryBoundary)
    c.uint.encode(state, value.retryAction)
  }
}

const familyEncoding = {
  preencode (state, value) {
    c.uint.preencode(state, value.id)
    c.string.preencode(state, value.name)
    c.string.preencode(state, value.route)
    listEncoding(nameIdEncoding).preencode(state, value.operations)
  },
  encode (state, value) {
    c.uint.encode(state, value.id)
    c.string.encode(state, value.name)
    c.string.encode(state, value.route)
    listEncoding(nameIdEncoding).encode(state, value.operations)
  }
}

const schemaEncoding = {
  preencode (state, value) {
    c.uint.preencode(state, value.category)
    c.uint.preencode(state, value.categoryLocalSchemaId)
    c.string.preencode(state, value.name)
    c.buffer.preencode(state, value.canonicalSchemaBytes)
  },
  encode (state, value) {
    c.uint.encode(state, value.category)
    c.uint.encode(state, value.categoryLocalSchemaId)
    c.string.encode(state, value.name)
    c.buffer.encode(state, value.canonicalSchemaBytes)
  }
}

const wireAbiV1Encoding = {
  preencode (state, value) {
    c.string.preencode(state, value.magic)
    c.uint.preencode(state, value.formatVersion)
    c.string.preencode(state, value.protocolFamily)
    c.uint.preencode(state, value.protocolMajor)
    c.uint.preencode(state, value.protocolMinor)
    c.string.preencode(state, value.mediaType)
    listEncoding(familyEncoding).preencode(state, value.families)
    listEncoding(nameIdEncoding).preencode(state, value.schemaCategories)
    listEncoding(nameIdEncoding).preencode(state, value.frameKinds)
    listEncoding(nameIdEncoding).preencode(state, value.admissionModes)
    listEncoding(nameIdEncoding).preencode(state, value.streamTransitions)
    listEncoding(nameIdEncoding).preencode(state, value.transportSupportBits)
    listEncoding(nameIdEncoding).preencode(state, value.endpointRoles)
    listEncoding(nameIdEncoding).preencode(state, value.privacyProfiles)
    listEncoding(nameIdEncoding).preencode(state, value.domainPurposes)
    listEncoding(nameIdEncoding).preencode(state, value.domainRecipes)
    listEncoding(nameIdEncoding).preencode(state, value.costClassRuleKinds)
    listEncoding(nameIdEncoding).preencode(state, value.errorCodes)
    listEncoding(nameIdEncoding).preencode(state, value.errorProfileIds)
    listEncoding(nameIdEncoding).preencode(state, value.errorRetryAfterModes)
    listEncoding(nameIdEncoding).preencode(state, value.ohttpTransportErrorCodes)
    listEncoding(nameIdEncoding).preencode(state, value.ohttpDeliveryBoundaries)
    listEncoding(nameIdEncoding).preencode(state, value.ohttpRetryActions)
    listEncoding(nameIdEncoding).preencode(state, value.transportIds)
    listEncoding(nameIdEncoding).preencode(state, value.transportExporterIds)
    listEncoding(nameIdEncoding).preencode(state, value.controlChannelIdTypes)
    listEncoding(nameIdEncoding).preencode(state, value.durabilityProfileIds)
    listEncoding(nameIdEncoding).preencode(state, value.durabilityRpoBands)
    listEncoding(nameIdEncoding).preencode(state, value.durabilityRtoBands)
    listEncoding(nameIdEncoding).preencode(state, value.redundancyClasses)
    listEncoding(nameIdEncoding).preencode(state, value.ageBands)
    listEncoding(nameIdEncoding).preencode(state, value.cellReceiptResults)
    listEncoding(nameIdEncoding).preencode(state, value.inboxManageOperations)
    listEncoding(nameIdEncoding).preencode(state, value.inboxAppendAuthModes)
    listEncoding(nameIdEncoding).preencode(state, value.inboxReceiptResults)
    listEncoding(nameIdEncoding).preencode(state, value.inboxAppendResults)
    listEncoding(nameIdEncoding).preencode(state, value.admissionConformanceClasses)
    listEncoding(nameIdEncoding).preencode(state, value.coreAckResults)
    listEncoding(nameIdEncoding).preencode(state, value.forwardCloseKinds)
    listEncoding(nameIdEncoding).preencode(state, value.storeLifecycleStates)
    listEncoding(nameIdEncoding).preencode(state, value.healthClockStates)
    listEncoding(nameIdEncoding).preencode(state, value.healthIntegrityStates)
    listEncoding(nameIdEncoding).preencode(state, value.healthRebalanceStates)
    listEncoding(classEncoding).preencode(state, value.cellClasses)
    listEncoding(classEncoding).preencode(state, value.inboxClasses)
    listEncoding(classEncoding).preencode(state, value.outerClasses)
    listEncoding(classEncoding).preencode(state, value.streamClasses)
    listEncoding(classEncoding).preencode(state, value.leaseClasses)
    listEncoding(namedValueEncoding).preencode(state, value.dispatchLimits)
    listEncoding(namedValueEncoding).preencode(state, value.endpointLimits)
    listEncoding(namedValueEncoding).preencode(state, value.publicProfileLimits)
    listEncoding(namedValueEncoding).preencode(state, value.operationRegistryValues)
    listEncoding(forwardCircuitClassEncoding).preencode(state, value.forwardCircuitClasses)
    listEncoding(coreSessionClassEncoding).preencode(state, value.coreSessionClasses)
    listEncoding(domainRegistryEntryV1).preencode(state, value.domainRegistry)
    listEncoding(errorProfileEntryV1).preencode(state, value.errorProfiles)
    listEncoding(ohttpTransportErrorProfileEncoding).preencode(state, value.ohttpTransportErrorProfiles)
    listEncoding(admissionCostRuleV1).preencode(state, value.admissionCostRules)
    listEncoding(operationProfileV1).preencode(state, value.operationProfiles)
    listEncoding(operationCapEncoding).preencode(state, value.operationCaps)
    listEncoding(operationBitEncoding).preencode(state, value.operationBits)
    listEncoding(schemaEncoding).preencode(state, value.implementedSchemas)
    listEncoding(c.string).preencode(state, value.requiredSchemaNames)
    listEncoding(c.string).preencode(state, value.missingSchemaNames)
  },
  encode (state, value) {
    c.string.encode(state, value.magic)
    c.uint.encode(state, value.formatVersion)
    c.string.encode(state, value.protocolFamily)
    c.uint.encode(state, value.protocolMajor)
    c.uint.encode(state, value.protocolMinor)
    c.string.encode(state, value.mediaType)
    listEncoding(familyEncoding).encode(state, value.families)
    listEncoding(nameIdEncoding).encode(state, value.schemaCategories)
    listEncoding(nameIdEncoding).encode(state, value.frameKinds)
    listEncoding(nameIdEncoding).encode(state, value.admissionModes)
    listEncoding(nameIdEncoding).encode(state, value.streamTransitions)
    listEncoding(nameIdEncoding).encode(state, value.transportSupportBits)
    listEncoding(nameIdEncoding).encode(state, value.endpointRoles)
    listEncoding(nameIdEncoding).encode(state, value.privacyProfiles)
    listEncoding(nameIdEncoding).encode(state, value.domainPurposes)
    listEncoding(nameIdEncoding).encode(state, value.domainRecipes)
    listEncoding(nameIdEncoding).encode(state, value.costClassRuleKinds)
    listEncoding(nameIdEncoding).encode(state, value.errorCodes)
    listEncoding(nameIdEncoding).encode(state, value.errorProfileIds)
    listEncoding(nameIdEncoding).encode(state, value.errorRetryAfterModes)
    listEncoding(nameIdEncoding).encode(state, value.ohttpTransportErrorCodes)
    listEncoding(nameIdEncoding).encode(state, value.ohttpDeliveryBoundaries)
    listEncoding(nameIdEncoding).encode(state, value.ohttpRetryActions)
    listEncoding(nameIdEncoding).encode(state, value.transportIds)
    listEncoding(nameIdEncoding).encode(state, value.transportExporterIds)
    listEncoding(nameIdEncoding).encode(state, value.controlChannelIdTypes)
    listEncoding(nameIdEncoding).encode(state, value.durabilityProfileIds)
    listEncoding(nameIdEncoding).encode(state, value.durabilityRpoBands)
    listEncoding(nameIdEncoding).encode(state, value.durabilityRtoBands)
    listEncoding(nameIdEncoding).encode(state, value.redundancyClasses)
    listEncoding(nameIdEncoding).encode(state, value.ageBands)
    listEncoding(nameIdEncoding).encode(state, value.cellReceiptResults)
    listEncoding(nameIdEncoding).encode(state, value.inboxManageOperations)
    listEncoding(nameIdEncoding).encode(state, value.inboxAppendAuthModes)
    listEncoding(nameIdEncoding).encode(state, value.inboxReceiptResults)
    listEncoding(nameIdEncoding).encode(state, value.inboxAppendResults)
    listEncoding(nameIdEncoding).encode(state, value.admissionConformanceClasses)
    listEncoding(nameIdEncoding).encode(state, value.coreAckResults)
    listEncoding(nameIdEncoding).encode(state, value.forwardCloseKinds)
    listEncoding(nameIdEncoding).encode(state, value.storeLifecycleStates)
    listEncoding(nameIdEncoding).encode(state, value.healthClockStates)
    listEncoding(nameIdEncoding).encode(state, value.healthIntegrityStates)
    listEncoding(nameIdEncoding).encode(state, value.healthRebalanceStates)
    listEncoding(classEncoding).encode(state, value.cellClasses)
    listEncoding(classEncoding).encode(state, value.inboxClasses)
    listEncoding(classEncoding).encode(state, value.outerClasses)
    listEncoding(classEncoding).encode(state, value.streamClasses)
    listEncoding(classEncoding).encode(state, value.leaseClasses)
    listEncoding(namedValueEncoding).encode(state, value.dispatchLimits)
    listEncoding(namedValueEncoding).encode(state, value.endpointLimits)
    listEncoding(namedValueEncoding).encode(state, value.publicProfileLimits)
    listEncoding(namedValueEncoding).encode(state, value.operationRegistryValues)
    listEncoding(forwardCircuitClassEncoding).encode(state, value.forwardCircuitClasses)
    listEncoding(coreSessionClassEncoding).encode(state, value.coreSessionClasses)
    listEncoding(domainRegistryEntryV1).encode(state, value.domainRegistry)
    listEncoding(errorProfileEntryV1).encode(state, value.errorProfiles)
    listEncoding(ohttpTransportErrorProfileEncoding).encode(state, value.ohttpTransportErrorProfiles)
    listEncoding(admissionCostRuleV1).encode(state, value.admissionCostRules)
    listEncoding(operationProfileV1).encode(state, value.operationProfiles)
    listEncoding(operationCapEncoding).encode(state, value.operationCaps)
    listEncoding(operationBitEncoding).encode(state, value.operationBits)
    listEncoding(schemaEncoding).encode(state, value.implementedSchemas)
    listEncoding(c.string).encode(state, value.requiredSchemaNames)
    listEncoding(c.string).encode(state, value.missingSchemaNames)
  }
}

function sortedNameIds (object) {
  return Object.entries(object)
    .map(([name, id]) => ({ name, id }))
    .sort((a, b) => a.id - b.id || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
}

function sortedClasses (object) {
  return Object.entries(object)
    .map(([id, value]) => ({ id: Number(id), value }))
    .sort((a, b) => a.id - b.id)
}

function sortedNamedValues (object) {
  return Object.entries(object)
    .map(([name, value]) => ({ name, value }))
    .sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)
}

function sortedTupleClasses (object) {
  return Object.entries(object)
    .map(([id, value]) => ({ id: Number(id), ...value }))
    .sort((a, b) => a.id - b.id)
}

function assertClosedRegistries () {
  const requiredDomainIds = [
    ...Object.values(REQUEST_COMMITMENT_DOMAIN_ID),
    ...Object.values(RESULT_SIGNATURE_DOMAIN_ID),
    ...Object.values(AUXILIARY_SIGNATURE_DOMAIN_ID)
  ]
  if (DOMAIN_REGISTRY.length !== requiredDomainIds.length) {
    throw new Error('domain registry is missing or has unknown rows')
  }
  const seenDomainBytes = new Set()
  for (let index = 0; index < DOMAIN_REGISTRY.length; index++) {
    const entry = DOMAIN_REGISTRY[index]
    const expectedId = requiredDomainIds[index]
    const expectedPurpose = index < 16
      ? DOMAIN_PURPOSE.REQUEST_COMMITMENT
      : index < 27 ? DOMAIN_PURPOSE.RESULT_SIGNATURE : DOMAIN_PURPOSE.AUXILIARY_SIGNATURE
    const expectedRecipe = expectedPurpose === DOMAIN_PURPOSE.REQUEST_COMMITMENT
      ? DOMAIN_RECIPE.OPERATION_DEFINED_COMMITMENT_PREIMAGE
      : DOMAIN_RECIPE.ED25519_DOMAIN_LEN64_PAYLOAD
    if (entry.domainId !== expectedId || entry.purpose !== expectedPurpose || entry.recipeId !== expectedRecipe) {
      throw new Error('domain registry has an unknown ID or wrong purpose/recipe')
    }
    if (!/^[\x20-\x7e]{1,96}$/.test(entry.exactAsciiBytes) || seenDomainBytes.has(entry.exactAsciiBytes)) {
      throw new Error('domain registry has duplicate or non-canonical ASCII bytes')
    }
    seenDomainBytes.add(entry.exactAsciiBytes)
  }

  const retryableCodes = new Set([9, 16, 17, 18])
  if (ERROR_PROFILE_ROWS.length !== 20) throw new Error('error profile 1 must have exactly 20 rows')
  for (let index = 0; index < ERROR_PROFILE_ROWS.length; index++) {
    const entry = ERROR_PROFILE_ROWS[index]
    const code = index + 1
    if (entry.errorProfileId !== ERROR_PROFILE_ID.CANONICAL_V1 || entry.code !== code ||
        entry.directCorrelatedStatus !== 200 || entry.protectedInnerStatus !== 200 ||
        entry.retryable !== (retryableCodes.has(code) ? 1 : 0) ||
        entry.retryAfterMode !== (code === 18 ? 1 : 0)) {
      throw new Error('error profile 1 has an unknown, missing, duplicate, or inconsistent row')
    }
  }

  if (OHTTP_TRANSPORT_ERROR_ROWS.length !== 3) throw new Error('OHTTP transport error mapping must have exactly three rows')
  const expectedTransportRows = [
    [1, 400, OHTTP_DELIVERY_BOUNDARY.BEFORE_VALID_DISPATCH, OHTTP_RETRY_ACTION.NONE],
    [2, 503, OHTTP_DELIVERY_BOUNDARY.BEFORE_TARGET_HANDOFF, OHTTP_RETRY_ACTION.FRESH_HPKE_SAME_DESTINATION_POLICY],
    [3, 504, OHTTP_DELIVERY_BOUNDARY.MAY_HAVE_REACHED_TARGET, OHTTP_RETRY_ACTION.RECONCILE_WITHOUT_AUTOMATIC_RETRY]
  ]
  for (let index = 0; index < expectedTransportRows.length; index++) {
    const row = OHTTP_TRANSPORT_ERROR_ROWS[index]
    const expected = expectedTransportRows[index]
    if (row.code !== expected[0] || row.protectedStatus !== expected[1] ||
        row.deliveryBoundary !== expected[2] || row.retryAction !== expected[3]) {
      throw new Error('OHTTP transport error mapping has an unknown or misplaced row')
    }
  }

  if (OPERATION_CAP_ROWS.length !== OPERATION_PROFILE_ROWS.length || OPERATION_CAP_ROWS.some((row, index) => {
    const profile = OPERATION_PROFILE_ROWS[index]
    return row.familyId !== profile.familyId || row.operationId !== profile.operationId ||
      row.requestSchemaId !== profile.requestSchemaId || row.resultSchemaId !== profile.resultSchemaId ||
      row.maxRequestBodyBytes !== profile.maxRequestBodyBytes || row.maxResultBodyBytes !== profile.maxResultBodyBytes
  })) throw new Error('operation cap registry is not the exact projection of operation profiles')
}

export function wireAbiRegistryValue (schemaCatalogEntries = null) {
  assertClosedRegistries()
  const families = Object.entries(FAMILY)
    .map(([name, id]) => ({
      name,
      id,
      route: FAMILY_ROUTES[id],
      operations: sortedNameIds(OPERATION[name])
    }))
    .sort((a, b) => a.id - b.id)

  const catalogByName = schemaCatalogEntries == null
    ? null
    : new Map(schemaCatalogEntries.map(entry => [b4a.toString(entry.schemaName, 'ascii'), entry]))
  const wireSchemas = IMPLEMENTED_SCHEMAS
    .filter(schema => schemaCategory(schema.name) === SCHEMA_CATEGORY.WIRE)
    .map(schema => {
      const catalog = catalogByName && catalogByName.get(schema.name)
      if (catalog) {
        return {
          category: catalog.category,
          categoryLocalSchemaId: catalog.categoryLocalSchemaId,
          name: schema.name,
          canonicalSchemaBytes: catalog.canonicalSchemaBytes
        }
      }
      // Diagnostic package callers can inspect/encode the registry without the
      // stable-master source file. Authority generation always supplies the
      // compiled master catalog and therefore never uses this fallback.
      return {
        category: SCHEMA_CATEGORY.WIRE,
        categoryLocalSchemaId: draftSchemaId(schema.name),
        name: schema.name,
        canonicalSchemaBytes: b4a.from(JSON.stringify(schema.fields), 'utf8')
      }
    })
    .sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)
  if (catalogByName && (wireSchemas.some(schema => schema.category !== SCHEMA_CATEGORY.WIRE) ||
      wireSchemas.some(schema => schema.categoryLocalSchemaId === 0) ||
      wireSchemas.length !== ABI_STATUS.wireRequiredSchemaNames.length)) {
    throw new Error('compiled master catalog does not contain the complete WIRE schema set')
  }

  return {
    magic: 'hiverelay-blind-abi-v1',
    formatVersion: 1,
    protocolFamily: PROTOCOL.family,
    protocolMajor: PROTOCOL.major,
    protocolMinor: PROTOCOL.minor,
    mediaType: PROTOCOL.mediaType,
    families,
    schemaCategories: sortedNameIds(SCHEMA_CATEGORY),
    frameKinds: sortedNameIds(FRAME_KIND),
    admissionModes: sortedNameIds(ADMISSION_MODE),
    streamTransitions: sortedNameIds(STREAM_TRANSITION),
    transportSupportBits: sortedNameIds(TRANSPORT_SUPPORT),
    endpointRoles: sortedNameIds(ENDPOINT_ROLE),
    privacyProfiles: sortedNameIds(PRIVACY_PROFILE),
    domainPurposes: sortedNameIds(DOMAIN_PURPOSE),
    domainRecipes: sortedNameIds(DOMAIN_RECIPE),
    costClassRuleKinds: sortedNameIds(COST_CLASS_RULE_ID),
    errorCodes: sortedNameIds(ERROR_CODE),
    errorProfileIds: sortedNameIds(ERROR_PROFILE_ID),
    errorRetryAfterModes: sortedNameIds(ERROR_RETRY_AFTER_MODE),
    ohttpTransportErrorCodes: sortedNameIds(OHTTP_TRANSPORT_ERROR_CODE),
    ohttpDeliveryBoundaries: sortedNameIds(OHTTP_DELIVERY_BOUNDARY),
    ohttpRetryActions: sortedNameIds(OHTTP_RETRY_ACTION),
    transportIds: sortedNameIds(TRANSPORT_ID),
    transportExporterIds: sortedNameIds(TRANSPORT_EXPORTER_ID),
    controlChannelIdTypes: sortedNameIds(CONTROL_CHANNEL_ID_TYPE),
    durabilityProfileIds: sortedNameIds(DURABILITY_PROFILE_ID),
    durabilityRpoBands: sortedNameIds(DURABILITY_RPO_BAND),
    durabilityRtoBands: sortedNameIds(DURABILITY_RTO_BAND),
    redundancyClasses: sortedNameIds(REDUNDANCY_CLASS),
    ageBands: sortedNameIds(AGE_BAND),
    cellReceiptResults: sortedNameIds(CELL_RECEIPT_RESULT),
    inboxManageOperations: sortedNameIds(INBOX_MANAGE_OPERATION),
    inboxAppendAuthModes: sortedNameIds(INBOX_APPEND_AUTH_MODE),
    inboxReceiptResults: sortedNameIds(INBOX_RECEIPT_RESULT),
    inboxAppendResults: sortedNameIds(INBOX_APPEND_RESULT),
    admissionConformanceClasses: sortedNameIds(ADMISSION_CONFORMANCE_CLASS),
    coreAckResults: sortedNameIds(CORE_ACK_RESULT),
    forwardCloseKinds: sortedNameIds(FORWARD_CLOSE_KIND),
    storeLifecycleStates: sortedNameIds(STORE_LIFECYCLE_STATE),
    healthClockStates: sortedNameIds(HEALTH_CLOCK_STATE),
    healthIntegrityStates: sortedNameIds(HEALTH_INTEGRITY_STATE),
    healthRebalanceStates: sortedNameIds(HEALTH_REBALANCE_STATE),
    cellClasses: sortedClasses(CELL_SIZE_CLASS),
    inboxClasses: sortedClasses(INBOX_FRAME_CLASS),
    outerClasses: sortedClasses(OUTER_CLASS),
    streamClasses: sortedClasses(STREAM_WIRE_CLASS),
    leaseClasses: sortedClasses(LEASE_CLASS_EPOCHS),
    dispatchLimits: sortedNamedValues(DISPATCH_LIMITS),
    endpointLimits: sortedNamedValues(ENDPOINT_LIMITS),
    publicProfileLimits: sortedNamedValues(PUBLIC_PROFILE_LIMITS),
    operationRegistryValues: sortedNamedValues({
      ALL_OPERATION_BITS,
      CLOCK_UNSAFE_OPERATION_BITS,
      DRAINING_OPERATION_BITS,
      OPERATION_COUNT
    }),
    forwardCircuitClasses: sortedTupleClasses(FORWARD_CIRCUIT_CLASS),
    coreSessionClasses: sortedTupleClasses(CORE_SESSION_CLASS),
    domainRegistry: DOMAIN_REGISTRY.map(entry => ({
      ...entry,
      exactAsciiBytes: b4a.from(entry.exactAsciiBytes, 'ascii')
    })),
    errorProfiles: ERROR_PROFILE_ROWS,
    ohttpTransportErrorProfiles: OHTTP_TRANSPORT_ERROR_ROWS,
    admissionCostRules: ADMISSION_COST_RULES,
    operationProfiles: OPERATION_PROFILE_ROWS,
    operationCaps: OPERATION_CAP_ROWS,
    operationBits: OPERATION_PROFILE_ROWS.map((row, ordinal) => ({
      familyId: row.familyId,
      operationId: row.operationId,
      ordinal,
      bit: 2 ** ordinal
    })),
    implementedSchemas: wireSchemas,
    requiredSchemaNames: [...ABI_STATUS.wireRequiredSchemaNames].sort(),
    missingSchemaNames: [...ABI_STATUS.wireMissingSchemaNames].sort()
  }
}

export function encodeWireAbiRegistry (schemaCatalogEntries = null) {
  // A candidate registry may intentionally describe not-yet-frozen rows so it
  // can be reviewed and exercised before authority regeneration.  Never let
  // the final-authority encoder turn that candidate into publishable bytes.
  // This also produces the governing release blocker instead of an incidental
  // validation error against the older frozen runtime registry.
  assertReleaseReady()
  return encodeWith(wireAbiV1Encoding, wireAbiRegistryValue(schemaCatalogEntries))
}

// Transitional API aliases share the final-authority gate. A review candidate
// may expose unfrozen rows through the value API, but neither encoder emits
// bytes until the candidate is release-ready; after freezing both names remain
// byte-identical. The aliases may be removed only in a later package major.
export const draftAbiRegistryValue = wireAbiRegistryValue
export const encodeDraftAbiRegistry = encodeWireAbiRegistry
export const draftRegistryEncoding = wireAbiV1Encoding

export { wireAbiV1Encoding }