O ciclo de vida do consentimento: o que entra em cena depois da verificação de idade
Em 4 de novembro de 2025, a Apple acrescentou às App Store Server Notifications um tipo de notificação que dispara a partir de uma decisão dos pais, e não de um pagamento: RESCIND_CONSENT, que indica que “o pai, a mãe ou o responsável retirou o consentimento para o uso do app por uma criança”.12
É o único dos 23 tipos do serviço cujo payload traz um objeto appData em vez de informações de transação, e appData guarda uma transação de app assinada que existe “mesmo que o cliente não faça nenhuma compra dentro do app”.319 Um app que nunca vendeu nada ganha, portanto, seu primeiro motivo para manter um endpoint de notificações no ar.
A declaração de rede social trata de ler a faixa etária de uma pessoa: o entitlement, as barreiras e o significado dos limites devolvidos. Considere aquilo resolvido e comece um passo adiante. A verificação de idade responde qual é a idade de alguém. As três APIs aqui respondem quem aprova, se uma aprovação já concedida sobrevive a uma mudança no seu app e o que acontece quando um responsável a retira.21012
Resumo
- Três APIs ficam a jusante da verificação de idade, e a Apple as entrega como um conjunto: a Significant Change API sob o PermissionKit,
AppStore.ageRatingCodeno StoreKit e a notificação de servidorRESCIND_CONSENT.45 - A Apple associa sua lista de quatro ferramentas ao Texas, a Utah e à Louisiana, não apenas ao Texas; todas as datas que ela informou para esses três já passaram; e a Apple delimita o dever a “determinadas regiões, onde exigido por lei”.5815
- A barreira que decide se você executa qualquer parte disso é
AgeRangeService.requiredRegulatoryFeatures, nova no 26.4, e escrever por inteiro o fluxo que ela controla custa iOS 26.5.736 - A Apple se recusa a definir o que é uma mudança significativa e diz isso quatro vezes, encaminhando você ao jurídico. O único exemplo concreto que publica, ela atribui à lei: a Apple escreve que a legislação do Texas considera significativa uma mudança na classificação etária do seu app.48
- Sua classificação pode mudar sem nenhuma build sua. A Apple aposentou a faixa 15+ na Austrália e deu ao Vietnã um novo esquema de quatro faixas em 18 de junho de 2026, ambos sem pedir que nenhum desenvolvedor enviasse nada.937
- A revogação é o ramo que ninguém constrói, e a documentação da Apple mal o constrói também:
RESCIND_CONSENTnão aparece em nenhuma das tabelas de ciclo de vida da página que um time de servidor realmente lê.2
Conceder, reconceder, revogar. A Apple documenta os dois primeiros caminhos com um projeto de exemplo e uma matriz de casos no ambiente de testes. O terceiro ganha um valor de enum e quatro campos, que acabou sendo mais ou menos a atenção que ele recebe no meu próprio código também.
O calendário que a Apple publicou quatro vezes
Toda data abaixo vem das próprias notícias para desenvolvedores da Apple, e a sequência importa mais do que qualquer entrada isolada.
A Apple anunciou a SB2420 do Texas em 8 de outubro de 2025, com a data “A partir de 1º de janeiro de 2026”, e descreveu a própria resposta em vez do texto da lei: novas Apple Accounts de usuários com menos de 18 anos entrariam em um grupo de Compartilhamento Familiar, e “pais ou responsáveis precisarão dar consentimento para todos os downloads na App Store, compras de apps e transações do menor pelo sistema de Compra Dentro do App da Apple”.13 Em 4 de novembro a Apple nomeou as ferramentas e as lançou nos betas do 26.2.4 Em 23 de dezembro o plano parou: “Uma liminar recente emitida por um tribunal distrital suspendeu a aplicação da lei estadual SB2420 do Texas … a Apple vai pausar os planos de implementação anunciados anteriormente.”14 Em 3 de junho de 2026 ele recomeçou: “Devido a uma decisão judicial recente que derrubou a liminar sobre a lei SB 2420 do Texas … Essas mudanças entram em vigor a partir de 4 de junho de 2026.”5
Oito meses de idas e vindas sobre uma única lei, e as APIs não se mexeram uma única vez. Saíram no 26.2, continuaram disponíveis para uso no ambiente de testes durante toda a liminar e estavam prontas quando ela caiu.14 Quem leu a pausa de dezembro como licença para adiar perdeu cinco meses de preparação.
O resto do mapa chegou em 24 de fevereiro de 2026, e vai além do Texas.
| Jurisdição | Data que a Apple informa | O que a Apple informa que se aplica |
|---|---|---|
| Austrália, Brasil, Singapura | 24 de fevereiro de 2026 | A Apple bloqueia downloads de apps classificados 18+ “a menos que tenham sido confirmados como adultos por métodos razoáveis”15 |
| Utah | 6 de maio de 2026 | Categorias etárias compartilhadas para novas Apple Accounts mediante solicitação15 |
| Texas | 4 de junho de 2026 | Novas Apple Accounts “agora estão sujeitas à lei”: consentimento para downloads, Compras Dentro do App e mudanças significativas em nome de menores de 18 anos, e os responsáveis podem revogar5 |
| Louisiana | 1º de julho de 2026 | Categorias etárias compartilhadas para novas Apple Accounts mediante solicitação15 |
O Texas é a exceção no limite de idade, não nas ferramentas. A Apple descreve o Texas como consentimento “em nome de menores de 18 anos” e publica suas categorias como “menos de 13, 13-15, 16-17 ou mais de 18”, que é exatamente o que a API produz: “Você pode especificar até três marcos etários, que criam até quatro faixas etárias possíveis.”4529 Para Utah e Louisiana a Apple cita o compartilhamento de categoria etária mediante solicitação e depois afirma que todas as quatro ferramentas “foram ampliadas para ajudar os desenvolvedores a cumprir obrigações de conformidade para a Louisiana e Utah”, nomeando a Significant Change API entre elas.15 O ferramental não é exclusivo do Texas. Se as obrigações são exclusivas dele, a Apple se recusa a dizer: delimita o dever a “determinadas regiões, onde exigido por lei” e manda a pergunta para o jurídico.8
Não leia a tabela como uma afirmação jurídica nem como um parecer de conformidade. Ela registra o que a Apple anunciou e a data que a Apple atribuiu a cada anúncio. Sou engenheiro, não advogado, e tudo o que vem abaixo para na fronteira da API.
O Q&A que encaminha as dúvidas de conformidade ao jurídico traz uma notícia melhor para o planejamento de releases: perguntada se algo disso muda a revisão, a Apple responde “Não, não há mudanças no processo de App Review.”8 As obrigações caem em tempo de execução, em jurisdições nomeadas, e não no envio.
A divisão é limpa, portanto. Se o seu app deve consentimento no Texas, em Utah ou na Louisiana é pergunta para um advogado habilitado lá. Contra qual SDK compilar não é: a Apple afirma que “você precisa compilar seu app com os SDKs do iOS 26.2 e do iPadOS 26.2, ou posteriores, com o Xcode 26.2 (17C52) ou posterior” para sequer alcançar os frameworks, e que contas existentes no iOS 18 ou anterior “não serão afetadas”.8
A Apple não vai dizer o que “significativo” quer dizer
O buraco de definição está no centro do recurso, e quatro superfícies da Apple devolvem a pergunta em vez de respondê-la: a página do símbolo (“Você determina o que constitui uma atualização significativa com base nas regulamentações aplicáveis”),12 os dois posts de notícias (“é responsabilidade do desenvolvedor determinar quando há uma mudança significativa em seu app”),45 e o Q&A, perguntado diretamente se uma mudança de termos ou de privacidade conta (“Depende. Você determina o que constitui uma atualização significativa do app com base nas leis aplicáveis”).8
A Apple fornece exatamente um exemplo prático, e ele vem da lei, não da Apple: “A lei estadual do Texas considera uma mudança na classificação etária de um app como mudança significativa, e os desenvolvedores devem manter atualizadas suas seleções de classificação etária no App Store Connect.”4
O que a Apple de fato define é o texto que você escreve. SignificantAppUpdateTopic traz um exemplo bom e um ruim lado a lado, o que é uma franqueza incomum para uma página de símbolo:
// Specific
let topic = SignificantAppUpdateTopic(
description: "This update adds video calling and location sharing features."
)
// Vague
let topic = SignificantAppUpdateTopic(
description: "We've made improvements to the app."
)
A instrução da Apple em torno dessa listagem é direta: “Use linguagem concisa e compreensível que explique claramente o que mudou no seu app. Pais e responsáveis veem essa descrição ao decidir se concedem permissão.”12 O texto padrão de notas de versão agora vai parar diante de um pai decidindo se o filho mantém acesso, o que faz dele a string de changelog de maior risco que a maioria dos apps já vai enviar.
A obrigação atrelada à resposta não tem nada de vaga, embora a Apple hesite quanto ao gatilho. A Apple torna você “responsável por impedir o acesso ao seu app ou a recursos quando necessário” e depois afirma sem rodeios que “Até que o pai ou a mãe conceda consentimento, a criança precisa ser impedida de acessar a atualização significativa, o que pode incluir todos os dados do app e da conta ou recursos específicos.”8 A expressão “todos os dados do app e da conta” carrega muito peso ali. A Apple deixa o raio de impacto por sua conta e define a conta inteira como limite superior.
A máquina de estados, e onde o próprio exemplo da Apple vaza
A Apple publicou um projeto de exemplo, “Implementing age assurance and permissions”, que é a única descrição ponta a ponta do fluxo.6 Lê-lo como máquina de estados revela quatro ramos e uma listagem que vale reescrever.
O primeiro ramo é o barato. AgeRangeService.requiredRegulatoryFeatures devolve Set<AgeRangeService.RegulatoryFeature>, com três membros: declaredAgeRangeRequired, significantAppChangeRequiresParentalConsent e significantAppChangeRequiresAdultNotification.7 O exemplo da Apple verifica isso primeiro, e “Quando nenhum dos recursos está presente, o app pula o fluxo inteiro.”6 A Apple apresenta a propriedade como reflexo da “região e das configurações de conta” da pessoa, o que eu leio como promessa de que usuários fora das jurisdições nomeadas voltam sem nada, embora a Apple não garanta nada disso.7
Os ramos restantes se separam por idade e pela forma como a idade foi estabelecida:
| Pessoa | Recurso regulatório exigido | O que o exemplo da Apple faz |
|---|---|---|
| Menor | significantAppChangeRequiresParentalConsent |
Envia uma PermissionQuestion ao responsável6 |
| Adulto, método confirmado | significantAppChangeRequiresAdultNotification |
Apresenta o painel de reconhecimento do sistema6 |
| Adulto, sem método confirmado | qualquer um | Bloqueia o acesso “até que a pessoa verifique sua conta em Ajustes”6 |
| Compartilhamento recusado | qualquer um | Interpreta o caso e depois não mostra ramo algum para ele6 |
A terceira linha é a que merece atenção. Um adulto que nunca vinculou um método de pagamento à sua Apple Account é bloqueado pela implementação de referência da Apple, num fluxo criado para proteger crianças. A Apple não oferece ramo alternativo nem forma de contornar.
Cruzando isso com as declarações publicadas, o roteamento fica assim. A separação entre adulto e menor segue o exemplo da Apple somado à matriz de casos no ambiente de testes, onde uma conta de 18+ devolve lowerBound de 18 sem limite superior e um ageRangeDeclaration de confirmed ou selfDeclared. Repare no piso de disponibilidade: ler .confirmed custa iOS 26.5, um release depois de todo o resto do fluxo.61136
import DeclaredAgeRange
import PermissionKit
import SwiftUI
@available(iOS 26.5, *)
struct SignificantChangeGate: View {
enum Phase {
case checking
case clear
case awaitingGuardian(PermissionQuestion<SignificantAppUpdateTopic>)
case blocked
}
let changeDescription: String
@Environment(\.requestAgeRange) private var requestAgeRange
@Environment(\.showSignificantUpdateAcknowledgment) private var acknowledge
@State private var phase: Phase = .checking
var body: some View {
switch phase {
case .checking:
ProgressView().task { await resolve() }
case .clear:
ChangedFeatureView()
case .awaitingGuardian(let question):
PermissionButton(question: question) { Text("Ask a parent to approve") }
case .blocked:
AccountVerificationPrompt()
}
}
private func resolve() async {
let features = (try? await AgeRangeService.shared.requiredRegulatoryFeatures) ?? []
guard !features.isEmpty else {
phase = .clear // nothing applies to this person
return
}
guard let response = try? await requestAgeRange(ageGates: 18),
case let .sharing(range) = response else {
phase = .blocked // declined or unavailable: Apple documents no branch
return
}
let isAdult = range.lowerBound == 18
let isConfirmed = range.ageRangeDeclaration == .confirmed
switch (isAdult, isConfirmed) {
case (true, true) where features.contains(.significantAppChangeRequiresAdultNotification):
try? await acknowledge(updateDescription: changeDescription)
phase = .clear
case (true, false):
phase = .blocked // adult with no confirmed method
case (true, true):
phase = .clear
case (false, _):
guard features.contains(.significantAppChangeRequiresParentalConsent) else {
phase = .clear
return
}
let topic = SignificantAppUpdateTopic(description: changeDescription)
phase = .awaitingGuardian(PermissionQuestion(significantAppUpdateTopic: topic))
}
}
}
Enviar a pergunta é a metade fácil. Receber a resposta é onde o exemplo da Apple vaza, e a listagem é curta o bastante para ser lida com atenção:6
for await response in AskCenter.shared.responses(for: SignificantAppUpdateTopic.self) {
guard response.choice.answer == .approval else {
return
}
versionManager.handleAllChanges()
}
Um return dentro de um for await abandona a sequência. Uma recusa e o app para de observar aquele tópico até a próxima abertura do app, então um responsável que toca em Recusar e repensa um minuto depois manda uma aprovação para um fluxo que ninguém está lendo. Escreva continue e registre a recusa. Ler a listagem como defeito e não como intenção é inferência minha; a correção custa uma palavra-chave de qualquer jeito. Projete o listener como se a inicialização em segundo plano não existisse: só a sequência descontinuada que ele substituiu chegou a prometê-la.2021
Pendente é um estado, e não tem timeout que você controle
O meio do ciclo de vida é onde o trabalho de design realmente mora, e cinco comportamentos documentados o moldam. Comece por aquele que a Apple responde pela metade: PermissionQuestion.expirationDate é um Optional<Date> após o qual “a pessoa que recebe a pergunta não pode mais responder”, e a Apple não publica nenhum valor padrão para ele.16
Uma criança pode cancelar antes que o responsável sequer veja a pergunta: “Em qualquer momento do fluxo de envio da solicitação, a criança tem a opção de cancelar a solicitação … Nesse cenário, o sistema não entrega resposta ao app que chamou para aquela pergunta específica.”17 Sem resposta, sem erro, sem callback. Seu estado pendente fica pendente para sempre, a não ser que você mesmo estabeleça um limite de tempo.
Perguntar a um adulto lança erro. O artigo sobre o ambiente de testes documenta um caso que a página do símbolo deixa em branco: “Chamar AskCenter.ask(_:) para um usuário adulto lança esse erro porque ele não atende aos requisitos de solicitações de permissão parental.”11 AskError.notAvailable é um dos dois casos do enum que não trazem resumo na própria página, e é justamente o que um adulto dispara.18 Faça o roteamento por idade antes de perguntar, em vez de capturar o erro lançado.
O estado de aprovação pertence ao iCloud, não ao contêiner do app. O exemplo da Apple grava cada mudança reconhecida em NSUbiquitousKeyValueStore e observa didChangeExternallyNotification “para que outros dispositivos não apresentem o mesmo fluxo de novo”.6 Um pai que aprova no iPhone não deveria gerar uma segunda solicitação no iPad, e o PermissionKit não faz nada disso por você.
O melhor detalhe do exemplo resolve um problema que a maioria dos times entregaria errado. Instalações novas não deveriam consentir com uma mudança que nunca vivenciaram, então a Apple lê AppTransaction.originalAppVersion e “marca automaticamente como tratadas quaisquer mudanças que o app introduziu naquela versão ou antes dela”.619 O rastreamento de consentimento é, portanto, por mudança e por pessoa: identificadores de mudança com versões de introdução, não um booleano.
Sua classificação pode mudar sem nenhuma build
AppStore.ageRatingCode é uma static var que devolve Int? de forma assíncrona, disponível a partir do 26.2 em iOS, iPadOS, macOS, tvOS, visionOS e watchOS.10 O uso declarado pela Apple é comparação, não interpretação: “Use esta propriedade para buscar a classificação etária do seu app e compará-la com a última classificação conhecida, para verificar se ela mudou.”10 Comparação é tudo o que você tem, porque a Apple não publica em lugar nenhum da documentação um mapeamento de inteiro para faixa de classificação. Você não pode perguntar se é 13+, apenas se você é o que era da última vez, então o contrato real é que você persista o valor anterior e assuma a primeira execução.
Por que um app vigiaria a própria classificação fica óbvio quando você percebe que classificações mudam sem releases. Em 21 de maio de 2026 a Apple anunciou que, a partir de 18 de junho, “A classificação etária 15+ deixará de existir na App Store da Austrália”, e deu ao Vietnã um esquema de quatro faixas específico da região, derivado de respostas já existentes no questionário.9 Ambos aconteceram: a tabela da Austrália na Ajuda do App Store Connect agora publica 16+ e R 18+ sem linha 15+, e o Vietnã tem uma tabela própria.37 Nenhuma das mudanças exigiu envio, build ou qualquer ação de desenvolvedor, e a Apple escreve que a lei do Texas considera uma mudança de classificação uma mudança significativa.4
Compare com uma mudança de classificação que parte de você: “Quando um desenvolvedor atualiza a classificação etária do seu app, a classificação é atualizada em todos os dispositivos dos usuários assim que a versão entra no ar.”4 Suas próprias mudanças pegam carona num release que você pode instrumentar. As da Apple, não, e é essa lacuna que a propriedade preenche.
O problema de teste é pior que uma lacuna. Uma resposta de um funcionário da Apple nos Fóruns de Desenvolvedores diz isso direto: “Um valor 0 é esperado durante o desenvolvimento do seu app, quando ele é compilado e executado a partir do Xcode e de ambientes de teste (inclusive TestFlight).”22 Zero não é nil, então o próprio exemplo documentado pela Apple passa pelo guard let e devolve 0 como classificação válida, que é exatamente o que o desenvolvedor autor daquele tópico viu num dispositivo físico.1022
Siga esse raciocínio até o fim. Persista 0 como sua linha de base e a primeira execução a partir da App Store lê um código real, enxerga uma mudança e pede a um pai que consinta de novo com nada. Tratar 0 como sentinela que você nunca grava na linha de base é inferência minha a partir das duas afirmações da Apple, e é a única linha de código defensivo que eu não deixaria de enviar.
Os metadados de disponibilidade da Apple trazem ainda uma constatação que nenhuma página em prosa registra. As linhas de plataforma dessas quatro capacidades abrem buracos em lugares diferentes:
| Capacidade | iOS / iPadOS | macOS | Mac Catalyst | visionOS | tvOS / watchOS |
|---|---|---|---|---|---|
AppStore.ageRatingCode10 |
26.2 | 26.2 | sem linha | 26.2 | 26.2 |
requiredRegulatoryFeatures7 |
26.4 | 26.4 | 26.4 | sem linha | sem linha |
SignificantAppUpdateTopic, PermissionButton1226 |
26.2 | 26.2 | 26.2 | 26.2 | sem linha |
showSignificantUpdateAcknowledgment27 |
26.4 | sem linha | 26.4 | sem linha | sem linha |
Duas assimetrias saltam daí. Um app nativo de macOS pode descobrir que significantAppChangeRequiresAdultNotification se aplica a uma pessoa e não tem API nenhuma para atendê-la: showSignificantUpdateAcknowledgment(in:updateDescription:) recebe um UIWindowScene e não publica linha de macOS, e o SignificantUpdateAction do SwiftUI para nas mesmas três plataformas.27 A Apple chegou a fornecer uma variante com NSWindow para a chamada vizinha requestAgeRange, então a omissão soa como lacuna e não como política, o que é inferência minha.28
Segunda: a detecção alcança mais longe que a correção. ageRatingCode publica linhas de tvOS e watchOS; nada que peça consentimento faz o mesmo.10 Esses alvos conseguem observar uma mudança de classificação sem forma documentada de responder, e o Return entrega os dois: seu tvOS 1.0.1 está em READY_FOR_DISTRIBUTION no App Store Connect, e o app iOS distribuído embute um app de relógio.30 As duas leituras pressupõem que os metadados da Apple estão completos, o que os próprios metadados enfraquecem: ageRatingCode não publica linha de Mac Catalyst, enquanto todo símbolo vizinho publica.10
Para que serve um servidor depois que a Apple bloqueia a abertura do app
A Apple declara o comportamento de revogação em uma frase: “Quando um pai, mãe ou responsável revoga o consentimento para que a criança acesse um app, a Apple impedirá que o app seja aberto. Para tratar revogações de consentimento, use o valor RESCIND_CONSENT de notificationType.”8
Repare na ordem dessas orações. O sistema já bloqueia o acesso, então RESCIND_CONSENT não é um mecanismo de bloqueio, e construí-lo como tal é desperdiçá-lo. É o único sinal que você recebe sobre um usuário em cujo dispositivo seu código não roda mais. A Apple não diz nada sobre como registrar esse evento, então o análogo mais próximo que encontrei é uma solicitação de exclusão de conta: assinaturas a reconciliar, estado a congelar e uma família que pode reaparecer.
O payload é excepcionalmente magro. appData carrega quatro campos: appAppleId, bundleId, environment e signedAppTransactionInfo.3 Sem transação, sem assinatura, sem informação de renovação e sem nenhum identificador de conta seu. O elo com uma pessoa passa pela transação de app assinada, cujo appTransactionID a App Store gera “para cada Apple Account que baixa seu app e para cada membro do grupo familiar em apps que suportam Compartilhamento Familiar”.19 Ou seja, um app gratuito de fato tem uma chave durável por conta para casar, e um app que nunca guardou nenhuma recebe uma notificação que não consegue atribuir a ninguém.
O transporte não reserva surpresas: uma URL versão 2 por ambiente no App Store Connect sobre TLS 1.2 ou superior, 17.0.0.0/8 na lista de permissões, 200 a 206 para sucesso e 40x ou 50x para comprar cinco novas tentativas em 1, 12, 24, 48 e 72 horas, apenas em produção.2324
RESCIND_CONSENT também não traz subtipo. Cada um dos 19 valores de subtipo publicados se limita a um tipo de notificação nomeado, e a string RESCIND_CONSENT não aparece em lugar nenhum daquela página.25 Mais revelador ainda: a página notificationType da Apple mapeia 40 eventos em oito tabelas sob “Handle use cases for In-App Purchase life-cycle events”, e RESCIND_CONSENT não aparece em nenhuma delas; o valor existe na lista de valores possíveis e em nenhum outro ponto da página.2 A Apple documentou a notificação no material de verificação de idade e nunca a ligou à referência que um time de servidor de fato lê.
O que meus próprios projetos já fazem errado
Vasculhei meu próprio código antes de escrever sobre o dos outros. A regra de seleção era mecânica, e a regra é onde estava o bug: cada linha da tabela de Projetos Ativos no meu próprio CLAUDE.md que tem um projeto Xcode por trás, o que dá sete projetos e 508 arquivos, cobrindo todo arquivo Swift, arquivo de entitlements, property list e project.pbxproj.31 Zero ocorrências de PermissionKit, AskCenter, SignificantAppUpdateTopic, FamilyControls, ManagedSettings, DeviceActivity, CKShare, sharedCloudDatabase ou ageRatingCode. Cinco dos sete têm registro no App Store Connect. Water e Yawara não têm nenhum e nunca foram publicados, então leia esses dois como código, não como apps na mão de alguém.31 O Ace Citizenship parecia o lar mais provável para usuários menores de idade e é o menos provável: seu site companheiro declara a elegibilidade para o N-400 como “18 anos ou mais”, e o app não pede data de nascimento em lugar algum.31
Depois rodei os mesmos padrões em todos os repositórios sob ~/Projects, que é a varredura que eu deveria ter feito primeiro. Os mesmos padrões batem em 21 arquivos, sete deles código, e seis desses sete estão num único projeto iOS para o qual o registro nunca ganhou uma linha.32
O Randori é um diário de treino de jiu-jitsu, e ele tem exatamente a superfície de consentimento que a lista de padrões existe para capturar: 20 ocorrências de CKShare e sharedCloudDatabase em ConnectionStore.swift, RandoriApp.swift, CKPostTransport.swift, ProfileCardView.swift, CloudShareSheet.swift e SocialContracts.swift, onde a aceitação passa por userDidAcceptCloudKitShareWith quando o app já está rodando e por connectionOptions.cloudKitShareMetadata no lançamento a frio.32 O Randori também traz sua própria primitiva de consentimento para vestir o nome de outra pessoa: TagConsentStore falha em modo fechado, então um atleta cujo cliente não publicou uma política de marcação simplesmente não pode ser marcado.32
Ou seja: o único projeto com superfície real de consentimento escreveu o próprio registro e não adotou nada da Apple. Ele deve três coisas que eu não construí. Ligar as superfícies pessoa a pessoa que seus contratos mantêm adormecidas é o caso de manual do SignificantAppUpdateTopic. ageRatingCode não tem linha de base armazenada, e uma 1.0 ainda não lançada só rodou a partir do Xcode, do ambiente de testes ou do TestFlight, que é precisamente onde a Apple diz que a propriedade devolve 0.22 A revogação não tem onde aterrissar: o Randori não vende nada e não roda servidor próprio.32
A constatação mais interessante é que o consentimento de responsáveis já é entregue em três dos sete originais, sob um nome mais antigo.
O Pedir para Comprar é o mesmo mecanismo com o mesmo formato, e a Apple o descreve com palavras quase idênticas: “com o Pedir para Comprar, quando uma criança quer fazer uma compra ou download elegível, o sistema envia a solicitação de compra ao pai, mãe ou responsável”.34 Ele aparece como Product.PurchaseResult.pending, e uma aprovação chega por Transaction.updates e não no ponto da chamada, porque essa sequência carrega “transações que ocorrem fora do app, como transações de Pedir para Comprar”.35 Uma recusa não entrega nada: “Seu app não recebe transação porque você recusou o Pedir para Comprar.”34
Três dos sete vendem algo, e cada um trata o estado pendente de um jeito:33
| App | Produto | Tratamento de .pending |
|---|---|---|
| ResumeGeni | Assinatura mensal | Um resultado .pending nomeado, com caminho de reconciliação documentado |
| Reps | Assinatura, duas faixas | case .userCancelled, .pending: colapsados num só ramo |
| Ace Citizenship | Não consumível | Um case .pending: separado devolvendo false, idêntico ao cancelamento |
Dois dos três relatam um responsável em deliberação como um usuário recusando, e o que o usuário vê é pior que um rótulo errado. O Reps só fecha seu paywall quando purchase devolve true; o Ace só avança do paywall quando a própria chamada devolve sucesso. Em .pending, nenhuma das duas chamadas devolve algo acionável, então os dois paywalls ficam abertos sem erro, sem toast e sem spinner: um toque morto.33 O pai aprova minutos depois, e a transação cai num listener cuja interface nunca reconheceu que houve solicitação.
O ramo colapsado é exatamente a falha que o PermissionKit convida quando o que está em jogo é maior, e eu a entreguei duas vezes antes de a Apple dar ao padrão uma segunda API. Pendente não é um ramo exótico. Pendente é a cara de um fluxo de consentimento visto de dentro do app, e o padrão certo é um estado que você renderiza, não um valor que você devolve.
Um app já está a jusante da metade servidor, o que torna concreto o ramo que falta. O endpoint versão 2 do ResumeGeni registrou pelo menos 56 notificações desde 26 de junho de 2026, 55 delas do ambiente de testes e uma de produção, que é como sei que as duas URLs de ambiente estão cadastradas, e não só uma. Seu handler nomeia 13 tipos de notificação e despacha oito; os cinco restantes aparecem só em comentários. RESCIND_CONSENT não está em nenhum dos grupos, nem em qualquer outro ponto do repositório.33
Perguntas frequentes
Qual API pede consentimento a um responsável e qual pede a um adulto?
São frameworks diferentes, e é fácil inverter os dois. O consentimento parental passa pelo PermissionKit: um SignificantAppUpdateTopic embrulhado em PermissionQuestion(significantAppUpdateTopic:), enviado com PermissionButton, respondido por AskCenter.shared.responses(for:).12162126 O reconhecimento de adulto passa pelo Declared Age Range, como showSignificantUpdateAcknowledgment(in:updateDescription:).27 Verifique requiredRegulatoryFeatures antes, porque perguntar a um adulto pelo PermissionKit lança AskError.notAvailable.711
Como testar a revogação de consentimento sem uma conta familiar de verdade?
Ative o Modo Desenvolvedor, depois abra Ajustes, Desenvolvedor, Conta Apple de Teste, faça login, selecione a conta, toque em Gerenciar e escolha Revogar Consentimento do App. Digite o identificador do bundle e toque em Revogar Consentimento; o sistema mostra “Notification Triggered”.11 Com uma URL versão 2 configurada, seu servidor recebe RESCIND_CONSENT com um objeto appData cujos campos bundleId e environment confirmam que a notificação corresponde ao app certo.311 O ambiente de testes envia cada notificação uma única vez, sem novas tentativas, então um endpoint que devolve 50x no meio do teste não ganha segunda chance.24
Por que um app vigiaria a própria classificação etária em tempo de execução?
Porque a Apple muda classificações na loja sem nenhum envio seu, e escreve que a lei do Texas considera uma mudança de classificação uma mudança significativa, apontando então para a Significant Change API para solicitar consentimento parental.4 18 de junho de 2026 é o exemplo prático: a Austrália perdeu sua faixa 15+ e o Vietnã ganhou um esquema de quatro faixas, ambos aplicados a apps existentes a partir de respostas de questionário já existentes.937 A Apple não publica mapeamento do inteiro da propriedade para uma faixa de classificação, então comparar com um valor que você guardou é a única operação que ela suporta.10
O RESCIND_CONSENT bloqueia o usuário por mim?
O sistema já faz isso, e é aí que a maioria das implementações se perde. A Apple afirma que, quando um responsável revoga o consentimento, “a Apple impedirá que o app seja aberto”, e então direciona você à notificação para tratar o evento, não para aplicá-lo.8 Ou seja, o trabalho restante tem formato de servidor: congelar o estado da conta, reconciliar qualquer assinatura e parar de enviar push para um dispositivo que não consegue abrir seu app. Trate como você trata uma exclusão de conta, não como uma verificação de autorização que falhou.
Principais conclusões
Para desenvolvedores iOS:
- Audite hoje o seu switch de Product.PurchaseResult. O Pedir para Comprar é consentimento de responsável que já está no ar, .pending é como ele aparece, e colapsar esse caso em .userCancelled é o defeito que o PermissionKit vai convidar quando o que está em jogo é maior.3435
- Planeje iOS 26.5, não 26.4, se o seu fluxo distingue um adulto confirmado de um autodeclarado.36
- Corrija o return no listener de exemplo da Apple antes de copiá-lo, e nunca deixe 0 chegar à sua linha de base armazenada de ageRatingCode.622
Para times que entregam em várias plataformas Apple:
- Confira as linhas de disponibilidade antes de planejar o trabalho. Um app nativo de macOS pode detectar que o reconhecimento de adulto se aplica e não tem API para apresentá-lo; tvOS e watchOS conseguem ler ageRatingCode sem nenhuma API de consentimento por trás.71027
- Guarde o reconhecimento em NSUbiquitousKeyValueStore com chave por mudança, e use AppTransaction.originalAppVersion para isentar instalações novas de mudanças anteriores a elas.619
Para responsáveis por backend e por releases:
- Registre o appTransactionID por conta antes de precisar dele, e depois suba o endpoint versão 2 mesmo para um app gratuito. Um endpoint construído depois do fato recebe eventos de revogação que não consegue atribuir a ninguém.319
- Faça a descrição da mudança significativa passar por quem cuida do texto do seu produto. É a única string que um responsável lê enquanto decide se seu app mantém um usuário.12
Três dos quatro pontos de aplicação deste ciclo disparam a partir de algo que você controla: a chave da tela de lançamento num SDK, a macro @State num toolchain e a declaração de rede social num envio. O consentimento de responsáveis dispara a partir de um processo judicial e de uma tabela de classificação da loja. O índice completo da série é a Série Ecossistema Apple.
Referências
-
Apple, App Store Server Notifications changelog. Sob o título de 4 de novembro de 2025, New features: “Updated the
responseBodyV2DecodedPayloadto include the new payload object,appData” e “Added the notification typeRESCIND_CONSENTtonotificationType”. As duas entradas seguintes do changelog são de 10 de dezembro de 2025 e 27 de abril de 2026, nenhuma delas tocando em consentimento. Lido a partir da JSON de documentação da Apple em 26 de julho de 2026, já que a página HTML é renderizada por JavaScript. ↩ -
Apple, notificationType, App Store Server Notifications. Fonte da definição de
RESCIND_CONSENT(“A notification type that indicates the parent or guardian has withdrawn consent for a child’s app usage”) e da contagem usada aqui: a página publica 23 valores possíveis (CONSUMPTION_REQUEST, DID_CHANGE_RENEWAL_PREF, DID_CHANGE_RENEWAL_STATUS, DID_FAIL_TO_RENEW, DID_RENEW, EXPIRED, EXTERNAL_PURCHASE_TOKEN, GRACE_PERIOD_EXPIRED, METADATA_UPDATE, MIGRATION, OFFER_REDEEMED, ONE_TIME_CHARGE, PRICE_CHANGE, PRICE_INCREASE, REFUND, REFUND_DECLINED, REFUND_REVERSED, RENEWAL_EXTENDED, RENEWAL_EXTENSION, RESCIND_CONSENT, REVOKE, SUBSCRIBED, TEST). A stringRESCIND_CONSENTocorre exatamente uma vez no payload da página, dentro da lista de valores possíveis; as oito tabelas sob “Handle use cases for In-App Purchase life-cycle events” somam 40 linhas de evento entre elas (4, 6, 7, 7, 8, 6, 6 e 4 linhas incluindo cada cabeçalho) e nenhuma a menciona. Note queREVOKEé uma perda de direito por Compartilhamento Familiar, não uma retirada de consentimento: “an In-App Purchase the customer was entitled to through Family Sharing is no longer available through sharing”. Lido a partir da JSON de documentação da Apple em 26 de julho de 2026. ↩↩↩↩ -
Apple, appData, App Store Server Notifications, introduzido na versão 2.19. Fonte de “The
appDataobject is part of theresponseBodyV2DecodedPayload. This object is present in the payload when thenotificationTypeisRESCIND_CONSENT” e das quatro propriedades:appAppleId(“disponível para apps que os usuários baixam da App Store. Não está presente no ambiente de testes”),bundleId,environmentesignedAppTransactionInfo(umJWSAppTransaction). A página responseBodyV2DecodedPayload afirma a exclusividade de forma independente, descrevendoappDatacomo um campo que “appears when thenotificationTypeisRESCIND_CONSENT” e acrescentando que “Thedata,appData,summary, andexternalPurchaseTokenfields are mutually exclusive. The payload contains only one of these fields”. Essas duas páginas são a base para chamarRESCIND_CONSENTde único tipo de notificação cujo payload trazappData; a stringappDatanão aparece em ponto algum da páginanotificationType. ↩↩↩↩ -
Apple, Next steps for apps distributed in Texas, Apple Developer News, 4 de novembro de 2025. Fonte das categorias etárias do Texas (“under 13, 13-15, 16-17, or over 18”), do nome de framework que a Apple usa (“the Significant Change API under the PermissionKit framework”), da frase sobre responsabilidade do desenvolvedor (“It’s the developer’s responsibility to determine when there’s a significant change to their app”), do exemplo de classificação etária (“Texas state law considers a change in the age rating of an app to be a significant change, and developers should keep their age rating selections current in App Store Connect. When a developer updates their app’s age rating, the rating is updated on all user devices once the version is live”), do enquadramento do StoreKit (“Developers can use a new property type in StoreKit to automatically check when their app’s age rating has changed on a user’s device and then use the Significant Change API to request parental consent”), do comportamento de revogação (“A parent or guardian in Texas can withdraw consent for any app, which will block launching of the app on the child or teen’s device”) e da lista de quatro itens em “Next steps”. Também é a fonte que data a disponibilidade em beta no iOS 26.2 e iPadOS 26.2. Consultado em 26 de julho de 2026. ↩↩↩↩↩↩↩↩↩
-
Apple, Update for Apps Distributed in Texas, Apple Developer News, 3 de junho de 2026. Fonte da liminar derrubada (“Due to a recent court ruling lifting an injunction on Texas law SB 2420, new Apple Accounts in Texas are now subject to the law”), do escopo (“age assurance and parent or guardian consent on behalf of minors under the age of 18 for downloads, Apple In-App Purchases, and significant changes associated with an app. Parents or guardians will also be able to revoke their consent for any app they previously approved for their child”), da data de vigência (“These changes will go into effect starting June 4, 2026”), do lembrete repetido sobre responsabilidade do desenvolvedor e da mesma lista de quatro itens de implementação. Consultado em 26 de julho de 2026. ↩↩↩↩↩↩
-
Apple, Implementing age assurance and permissions, código de exemplo do Declared Age Range. Disponibilidade: iOS 26.5, iPadOS 26.5, Mac Catalyst 26.5, Xcode 27.0 beta; o artigo instrui a fazer login no iCloud “on a device with iOS 26.4 or later” antes de executar. Fonte do comportamento com conjunto vazio (“When neither feature is present, the app skips the flow entirely”), do marco etário de 18, das quatro categorias interpretadas (
.minor,.verifiedAdultdescrito como “an adult with a confirmed payment method”,.unverifiedAdultcomo “an adult without account verification”,.declinedSharing), do desfecho para adulto não verificado (“the app sets the phase to.blockedand prevents access until the person verifies their account in Settings”), do caminho do menor construindo umSignificantAppUpdateTopice umaPermissionQuestion, do envio viaPermissionButton, da listagem do listenerAskCenter.shared.responses(for:)citada aqui na íntegra, do comportamento em caso de recusa (“When the parent denies the request, the app prevents the minor from using it”), do rastreamento de reconhecimento emNSUbiquitousKeyValueStorecomdidChangeExternallyNotification(“so other devices don’t present the same flow again”) e da isenção viaoriginalAppVersion(“People who install the app when a significant change is already present don’t need to acknowledge it”). O projeto também exige a capability Declared Age Range e o serviço de armazenamento chave-valor do iCloud. Lido a partir da JSON de documentação da Apple em 26 de julho de 2026. ↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple, AgeRangeService.requiredRegulatoryFeatures e AgeRangeService.RegulatoryFeature, Declared Age Range. Ambos disponíveis a partir de iOS 26.4, iPadOS 26.4, Mac Catalyst 26.4 e macOS 26.4, sem linha de visionOS, tvOS ou watchOS. Declaração:
var requiredRegulatoryFeatures: Set<AgeRangeService.RegulatoryFeature> { get async throws }, lançandonotAvailable“if the regulatory feature’s service is unavailable”. O enum publica exatamente três casos:declaredAgeRangeRequired(“Indicates the person is required to share their age range with your app”),significantAppChangeRequiresAdultNotification(“Indicates that adult users must acknowledge your app’s significant change”) esignificantAppChangeRequiresParentalConsent(“Indicates a parent or guardian is required to acknowledge and consent to a significant app change”). ↩↩↩↩↩↩ -
Apple, Age assurance frameworks Q&A, Apple Developer Support. Fonte do piso de SDK (“you must build your app against the iOS 26.2 and iPadOS 26.2 SDKs, or later, with Xcode 26.2 (17C52) or later”), da ressalva sobre contas antigas (“Existing Apple Accounts running iOS 18 and iPadOS 18, or earlier … won’t be affected”, frase cuja oração omitida especifica “including adult and child accounts for kids and teens”), da resposta sobre responsabilidade (“Yes, developers are responsible for their own age restrictions” e “For questions about your compliance obligations, consult your legal counsel”), da delimitação regional citada duas vezes neste artigo (“In certain regions, where legally required, Apple uses age assurance methods to confirm an Apple Account holder’s age and shares age categories with you through the Declared Age Range API. In those regions, you must check the age of the people using your app”, reafirmada depois como “In regions where legally required, you need to check the age of the people using your app with the Declared Age Range API”), da obrigação de acesso (“For significant app updates, you’re responsible for preventing access to your app or features when required, and for handling the response from the parent or guardian. Until the parent provides consent, the child must be prevented from accessing the significant update, which may include all app and account data or specific features”), do comportamento de revogação (“When a parent or guardian revokes consent for their child to access an app, Apple will prevent the app from launching. To handle consent revocations, use the
RESCIND_CONSENTvalue fromnotificationType”), da resposta sobre App Review (“No, there are no changes to the App Review process”) e da resposta sobre termos e privacidade (“It depends. You determine what constitutes a significant app update based on applicable laws”). Lido em 26 de julho de 2026. ↩↩↩↩↩↩↩↩↩ -
Apple, Upcoming changes to age ratings in Australia and Vietnam, Apple Developer News, 21 de maio de 2026. Fonte de “Starting June 18, 2026, age ratings on the App Store will be updated in Australia and Vietnam”, da mudança australiana (“The 15+ age rating will no longer be available on the App Store in Australia. Apps currently rated 15+ with the following content descriptors will be updated to 16+”) e de seus três descritores (acesso irrestrito à web; informações médicas ou de tratamento frequentes; loot boxes), e da mudança vietnamita (“To align with Article 38 of Vietnam Decree 147, apps available on the App Store in Vietnam will require a region-specific age rating. Based on your age rating questionnaire responses in App Store Connect, your app will receive one of four ratings (00+(all ages), 12+, 16+, or 18+)”). Nenhuma das mudanças pede que o desenvolvedor envie uma build. Consultado em 26 de julho de 2026. ↩↩↩
-
Apple, AppStore.ageRatingCode, StoreKit. Declaração
static var ageRatingCode: Int? { get async }, disponível a partir de iOS 26.2, iPadOS 26.2, macOS 26.2, tvOS 26.2, visionOS 26.2 e watchOS 26.2, sem linha de Mac Catalyst. Valor de retorno: “An integer representing the current age rating code, ornilif the age rating is unavailable”. Fonte do enquadramento por comparação (“Use this property to fetch the age rating for your app and compare it with the last known age rating to check if it has changed”) e do encadeamento com o PermissionKit (“If your app’s age rating has changed, consider informing parents or guardians by using the Significant Change API”), frase na qual “Significant Change API” é o texto do link e a páginaSignificantAppUpdateTopicda Apple é o destino. O próprio exemplo da Apple na página é umguard letem torno da propriedade que imprime quando o valor está indisponível. Busquei na documentação da Apple um mapeamento desses inteiros para faixas de classificação (4+, 9+, 13+, 16+, 18+) em 26 de julho de 2026 e não encontrei nenhum nesta página, na página do tipoAppStorenem na referência de classificações etárias da Ajuda do App Store Connect. ↩↩↩↩↩↩↩↩↩ -
Apple, Testing age assurance in sandbox, StoreKit. Fonte do caminho no dispositivo (Ajustes, Desenvolvedor, Conta Apple de Teste, Gerenciar, depois “Age Assurance or Revoke App Consent”), da matriz de testes de seis linhas, cujas linhas de 18+ devolvem limite inferior 18 sem limite superior e declaração etária
selfDeclaredouconfirmed, do comportamento ao perguntar a adulto (“For 18+ test cases, PermissionKit throwsAskError.notAvailablerather than returning aPermissionChoice. CallingAskCenter.ask(_:)for an adult user throws this error because they don’t meet the requirements for parental permission requests”), dos passos de revogação terminando na confirmação “Notification Triggered” com “A notification will be sent to the developer server soon” e da nota sobre o payload (“your server receives aRESCIND_CONSENTnotificationType. The notification payload includes anappDataobject with app metadata, including thebundleIdandenvironmentfields”). As três linhas de menores na matriz são menos de 13 aprovado, 13 a 15 aprovado e 16 a 17 recusado, todas com declaração etáriaguardianDeclared. ↩↩↩↩↩ -
Apple, SignificantAppUpdateTopic, PermissionKit. Disponível a partir de iOS 26.2, iPadOS 26.2, Mac Catalyst 26.2, macOS 26.2 e visionOS 26.2; declarado
struct SignificantAppUpdateTopic, em conformidade comQuestionTopic, cominit(description: String). Fonte do adiamento da definição (“You determine what constitutes a significant update based on applicable regulations”), da orientação sobre a descrição (“Use concise, understandable language that clearly explains what changed in your app. Parents and guardians see this description when deciding whether to grant permission”) e dos dois comentários de código da listagem Specific/Vague reproduzidos na íntegra neste artigo. ↩↩↩↩↩↩ -
Apple, New requirements for apps available in Texas, Apple Developer News, 8 de outubro de 2025. Fonte do anúncio original (“Beginning January 1, 2026, a new state law in Texas … introduces age assurance requirements for app marketplaces and developers”, com o trecho elidido nomeando a SB2420), da exigência de Compartilhamento Familiar (“All new Apple Accounts for users under the age of 18 will be required to join a Family Sharing group, and parents or guardians will need to provide consent for all App Store downloads, app purchases, and transactions using Apple’s In-App Purchase system by the minor”) e do aviso prévio sobre Utah e Louisiana (“Similar requirements will come into effect later next year”). Consultado em 26 de julho de 2026. ↩
-
Apple, Update on age requirements for apps distributed in Texas, Apple Developer News, 23 de dezembro de 2025. Fonte da liminar (“A recent injunction issued by a district court suspended enforcement of Texas state law SB2420 … In light of this ruling, Apple will pause previously announced implementation plans and monitor the ongoing legal process”), da disponibilidade continuada das quatro ferramentas no ambiente de testes e de sua extensão a Utah e Louisiana. Consultado em 26 de julho de 2026. ↩↩
-
Apple, Age requirements for apps distributed in Brazil, Australia, Singapore, Utah, and Louisiana, Apple Developer News, 24 de fevereiro de 2026. Fonte da restrição de download 18+ (“Starting February 24, 2026, Apple will block users in Australia, Brazil, and Singapore from downloading apps rated 18+ unless they have been confirmed to be adults through reasonable methods. The App Store will perform this confirmation automatically. However, developers may have separate obligations to independently confirm that their users are adults”), das datas de Utah e Louisiana (“For users with new Apple Accounts in Utah as of May 6, 2026, and in Louisiana as of July 1, 2026, age categories will be shared with the developer’s app when requested through the Declared Age Range API”), da frase de expansão citada neste artigo e dos quatro links que a seguem (“The tools we previously announced have been expanded to help developers meet compliance obligations for Louisiana and Utah, including:” Declared Age Range API, Significant Change API sob o PermissionKit, novo tipo de propriedade de classificação etária no StoreKit, App Store Server Notifications), da consequência sobre loot boxes no Brasil e da primeira menção pública ao Significant Update Action (“Developers can use the Declared Age Range API to present significant update notifications to adults in these states through the Significant Update Action, now in beta”). Consultado em 26 de julho de 2026. ↩↩↩↩↩
-
Apple, PermissionQuestion e expirationDate, PermissionKit.
final class PermissionQuestion<Topic> where Topic : QuestionTopic, disponível a partir do iOS 26.0 com quatro inicializadores:init(handle:),init(handles:),init(communicationTopic:)einit(significantAppUpdateTopic:), este último introduzido no iOS 26.2 e descrito como criando “a permission question that asks parents or guardians for permission to continue using your app after a significant update”.expirationDateé declaradofinal var expirationDate: Date?com a discussão “Once the date passes, the person that receives the question can no longer respond”. A Apple não publica valor padrão para a propriedade nem orientação de setter para o tópico de atualização significativa. ↩↩ -
Apple, Creating a communication experience, PermissionKit. Fonte do comportamento de cancelamento citado aqui: “At any point during the send request flow, the child has the option to cancel the request, and decide not to send the question to their parent or guardian. In this scenario, the system doesn’t deliver a response to the calling app for that specific question”. Também é a fonte da restrição do framework ao iMessage, que a página inicial do PermissionKit apresenta como aviso Important: “Communication experiences using the
PermissionKitframework are only available using iMessage”. Note que o artigo documenta apenas o fluxo deCommunicationTopic; a Apple não publica artigo equivalente para o fluxo de atualização significativa, e os exemplos de código do artigo chamamCommunicationLimits.current.permissionResponses, um símbolo que devolve 404 na documentação da Apple em 26 de julho de 2026. ↩ -
Apple, AskError, PermissionKit.
enum AskError, em conformidade comLocalizedError, com seis casos. Quatro trazem resumo e chegam no iOS 26.1:unknown,communicationLimitsNotEnabled(“Indicates communication limits isn’t enabled to send permission requests”),contactSyncNotSetupeinvalidQuestion. Dois não publicam resumo em suas próprias páginas:systemError(underlyingError:)no iOS 26.1 e notAvailable no iOS 26.2, chegando junto comSignificantAppUpdateTopic. O significado denotAvailableaparece apenas no artigo sobre o ambiente de testes citado na nota 11;systemErrorao menos nomeia sua causa na assinatura. SecommunicationLimitsNotEnabledoucontactSyncNotSetuptambém podem surgir numa solicitação de atualização significativa, isso não está declarado. ↩ -
Apple, appTransactionID e originalAppVersion em
AppTransaction, StoreKit. Fonte da semântica do identificador (“The App Store generates a single, globally uniqueappTransactionIDfor each Apple Account that downloads your app and for each family group member for apps that support Family Sharing”), de sua estabilidade diante de novo download, reembolso, recompra e troca de loja, de sua presença nos payloads da versão 2 das App Store Server Notifications, e da linha decisiva para apps gratuitos: “TheappTransactionIDis available even if a customer makes no in-app purchases”.originalAppVersioné “The app version that the customer originally purchased from the App Store”, carregandoCFBundleShortVersionStringno macOS eCFBundleVersionnas demais plataformas, e sempre1.0no ambiente de testes. O tipo correspondente do lado do servidor é appTransactionId na App Store Server API, introduzido na versão 1.15. ↩↩↩↩↩ -
Apple, CommunicationLimits e updates, PermissionKit.
updatesé declaradofinal var updates: some AsyncSequence<PermissionResponse<CommunicationTopic>, Never> { get }e resumido como “Registers the communication topic with the system, so your app can be launched on-demand in the background to receive permission updates”. A documentação da Apple agrupaupdatese as duas sobrecargas deCommunicationLimits.ask(_:in:)sob um cabeçalho de APIs descontinuadas; a própria classe segue atual paraisKnownHandle(_:)eknownHandles(in:). A sequência substituta abandona a promessa: nem o resumo nem a discussão deAskCenter.responses(for:)mencionam inicialização em segundo plano, e é exatamente essa a lacuna de documentação apontada no corpo do texto. ↩ -
Apple, AskCenter e responses(for:), PermissionKit, ambos disponíveis a partir de iOS 26.2, iPadOS 26.2, Mac Catalyst 26.2, macOS 26.2 e visionOS 26.2.
AskCenteré umafinal classalcançada porstatic let shared, descrita como roteando “your questions through the appropriate family sharing channels” e entregando “responses back to your app when parents make their decisions”.responses(for:)é declaradofinal func responses<Topic>(for topicType: Topic.Type) -> some AsyncSequence<PermissionResponse<Topic>, Never> where Topic : QuestionTopice resumido como “Registers the topic type with the system and returns an asynchronous sequence of responses”, sem menção a inicialização em segundo plano. Existem quatro sobrecargas deask(_:in:): duas recebendoUIViewControllerem iOS, iPadOS e visionOS, e duas recebendoNSWindowno macOS, uma de cada por tipo de tópico.PermissionResponseexpõechoiceequestion;PermissionChoice.Answerpublica exatamente dois casos,approvaledenial. ↩↩ -
Resposta de funcionário da Apple em AppStore.ageRatingCode always returns 0 on real device, Apple Developer Forums. Post original de abril de 2026 relatando
0num dispositivo físico com iOS 26.4, conta do ambiente de testes conectada e classificação etária configurada no App Store Connect. A resposta, de um autor identificado como Apple Staff, afirma: “A APIageRatingCodedeve ser usada para observar mudanças na classificação etária do seu app ao longo do tempo, comparando-a com seu último valor conhecido. Se o código de classificação etária do seu app mudou, considere informar pais ou responsáveis usando a Significant Change API” e “Um valor0é esperado durante o desenvolvimento do seu app, quando ele é compilado e executado a partir do Xcode e de ambientes de teste (inclusive TestFlight)”. Consultado duas vezes em 26 de julho de 2026 com redação idêntica; o fórum é renderizado por JavaScript e exibe a idade da resposta como o carimbo relativo “1w” em vez de uma data, então nenhuma data de publicação é reportada aqui. Uma resposta de fórum de desenvolvedores é evidência mais fraca que uma página de documentação, e a documentação da Apple paraageRatingCodenão menciona o valor0em momento algum. A consequência extraída neste artigo, de que persistir0como linha de base fabrica uma falsa mudança na primeira build da App Store, é inferência minha a partir dessa resposta e do padrão de comparação documentado pela Apple. ↩↩↩↩ -
Apple, Enabling App Store Server Notifications. Fonte do piso de TLS (“your server must support the Transport Layer Security (TLS) 1.2 protocol or later”), da configuração de URL por ambiente no App Store Connect, da restrição de porta (443, ou 1024 e acima) e da sub-rede em lista de permissões (“adicione a sub-rede de endereços IP
17.0.0.0/8”, que “se aplica tanto ao ambiente de testes quanto ao ambiente de produção”). O artigo companheiro Receiving App Store Server Notifications descreve osignedPayloadassinado por JWS e, em 26 de julho de 2026, menciona apenas o objetodata, sem referência aappData. ↩ -
Apple, Responding to App Store Server Notifications. Fonte dos códigos de sucesso (“Send HTTP
200, or any HTTP code between200and206”), do gatilho de retentativa (“Send HTTP50xor40xto have the App Store retry the notification”), do cronograma de retentativas da versão 2 (“it retries five times, at 1, 12, 24, 48, and 72 hours after the previous attempt”), da limitação do ambiente de testes (“As notificações de retentativa estão disponíveis apenas no ambiente de produção. No ambiente de testes, o servidor da App Store tenta enviar a notificação uma única vez”) e do caminho de recuperação porGet-Notification-History. ↩↩ -
Apple, subtype, App Store Server Notifications. A página publica 19 valores possíveis (ACCEPTED, ACTIVE_TOKEN_REMINDER, AUTO_RENEW_DISABLED, AUTO_RENEW_ENABLED, BILLING_RECOVERY, BILLING_RETRY, CREATED, DOWNGRADE, FAILURE, GRACE_PERIOD, INITIAL_BUY, PENDING, PRICE_INCREASE, PRODUCT_NOT_FOR_SALE, RESUBSCRIBE, SUMMARY, UPGRADE, UNREPORTED, VOLUNTARY), cada um limitado a um tipo de notificação nomeado. A string
RESCIND_CONSENTnão aparece em ponto algum do payload da página, lido em 26 de julho de 2026. ↩ -
Apple, PermissionButton, PermissionKit.
@MainActor @preconcurrency struct PermissionButton<Topic, Label> where Topic : QuestionTopic, Label : View, disponível a partir de iOS 26.2, iPadOS 26.2, Mac Catalyst 26.2, macOS 26.2 e visionOS 26.2, com duas sobrecargas deinit(question:label:)restritas respectivamente aCommunicationTopiceSignificantAppUpdateTopic. Ele substituiCommunicationLimitsButton, que a Apple lista sob APIs descontinuadas na página do framework. ↩↩↩ -
Apple, AgeRangeService.showSignificantUpdateAcknowledgment(in:updateDescription:), SignificantUpdateAction e o valor de ambiente do SwiftUI showSignificantUpdateAcknowledgment. O método é declarado
@MainActor func showSignificantUpdateAcknowledgment(in windowScene: UIWindowScene, updateDescription: String) async throwse publica iOS 26.4, iPadOS 26.4 e Mac Catalyst 26.4, sem linha de macOS;AgeRangeServicelista apenas essa sobrecarga sob “Displaying update acknowledgments”.SignificantUpdateActione o valor de ambiente publicam as mesmas três plataformas. Aviso Important da Apple sobre o método: “Before calling this function, checkRegulatoryFeatureto determine if a person must acknowledge your significant app change”. A discussão do valor de ambiente acrescenta que você deve “Call this action from aButtonoronAppear(perform:)”. ↩↩↩↩ -
Apple, requestAgeRange(ageGates:::in:), Declared Age Range. A Apple publica duas sobrecargas: uma recebendo
in viewController: UIViewControllerem iOS 26.0, iPadOS 26.0 e Mac Catalyst 26.0, e outra recebendoin window: NSWindowno macOS 26.0. A existência de uma variante comNSWindowpara a solicitação de faixa etária, e sua ausência para o painel de reconhecimento citado na nota 27, é a base para ler a omissão no macOS como lacuna e não como decisão de política; a Apple não publicou nada em nenhum dos sentidos. ↩ -
Apple, Requesting people’s age range information in your app, Declared Age Range. Fonte da aritmética de marcos citada no corpo do texto (“Você pode especificar até três marcos etários, que criam até quatro faixas etárias possíveis”), da restrição de espaçamento (“Cada faixa deve ter no mínimo dois anos de duração”) e da semântica dos limites (“Quando o valor de
lowerBoundénil, a pessoa está abaixo do seu marco etário mais baixo” e “Quando oupperBoundénil, a pessoa atinge ou supera o seu marco etário mais alto”). Marcos em 13, 16 e 18 devolvem exatamente as quatro faixas que a Apple publica para o Texas, e as duas faixas limitadas atendem ao mínimo de dois anos: 13 a 15 abrange três anos e 16 a 17 abrange dois. O artigo também alerta que as regiões às quais a conta de uma pessoa pertence “determinam os marcos etários que o sistema usa para devolver faixas etárias, e podem diferir dos marcos etários que você especifica na sua requisição”. Lido a partir da JSON de documentação da Apple em 26 de julho de 2026. ↩ -
Levantamento do autor, 26 de julho de 2026: como as afirmações sobre plataformas foram estabelecidas. Valores de plataforma lidos da build setting
SUPPORTED_PLATFORMSde cada projeto, e não das chaves de deployment target, que o Xcode grava independentemente do destino: o Reps declaraappletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulatormais um target separadowatchos watchsimulator, e o Ace Citizenship declara apenasiphoneos iphonesimulator. Esse método subnotifica plataformas, e o Return é a prova: o único valor deSUPPORTED_PLATFORMSdo Return éiphoneos iphonesimulator macosx xros xrsimulator, enquanto seus targets de TV e relógio carregamSDKROOT = appletvoseSDKROOT = watchos, de modo que uma varredura porSUPPORTED_PLATFORMSnão vê nenhum dos dois. Toda afirmação de “entrega na plataforma X” neste artigo vem, portanto, do App Store Connect e não de uma build setting. Return: TV_OS 1.0 e 1.0.1 ambos READY_FOR_DISTRIBUTION, IOS e MAC_OS 1.0.1 idem, e o target iOS embuteReturnWatch Watch App(com.941apps.Return.watchkitapp) por uma fase Embed Watch Content, que é como um app de relógio chega ao pulso. Reps: IOS 1.1 e MAC_OS 1.1 READY_FOR_DISTRIBUTION, nenhuma versão TV_OS jamais nesse estado, e TV_OS 1.2 parada em WAITING_FOR_REVIEW desde 2 de junho de 2026, então o Reps entrega iOS e macOS. ↩ -
Levantamento do autor, 26 de julho de 2026, em macOS 26.5.2 (build 25F84) com Xcode 26.6 (build 17F113) e Swift 6.3.3. Regra de seleção, declarada para que o escopo possa ser conferido e reproduzido em vez de aceito por confiança: cada linha da tabela de Projetos Ativos na minha própria config de agente (
~/.claude/CLAUDE.md) que tem um projeto Xcode por trás, o que dá exatamente sete:Reps,Return,Banana List(publicado como Get Bananas),Ace-Citizenship,Water,ResumeGeniAppeYawara. A regra também é a falha do levantamento, porqueRandorinão tem linha nessa tabela eRandoriacabou sendo o projeto que importava. Cinco dos sete têm registro no App Store Connect (Reps 6776044339, Return 6756242021, Get Bananas 6756241534, Ace Citizenship 6532592671, ResumeGeni 6771154645);WatereYawaranão aparecem entre os 18 apps da conta, então nenhum dos dois jamais foi submetido e chamar qualquer um deles de app é exagero. Contagem de arquivos Swift por projeto: 77, 57, 55, 26, 34, 71 e 143. A varredura de consentimento cobriu*.swift,*.entitlements,*.plisteproject.pbxproj: 85, 65, 63, 34, 38, 74 e 149, que somam 508. Reproduzir essas contagens exige oito nomes de diretório podados, e não seis:build,DerivedData,.build,Pods,.git,worktreese, só no Reps,.venv(142 property lists emReps/.venveReps/server/.venv) e.xcode-state-backups(18). Podando apenas os seis primeiros, o Reps marca 245 e o total marca 668, então a lista de exclusões é estrutural e todo nome de que ela depende está impresso aqui. Os 16 padrões, todos sensíveis a maiúsculas:PermissionKit,CommunicationLimits,AskPermission,AskCenter,SignificantAppUpdateTopic,SignificantUpdateAction,PermissionTopic,com.apple.developer.family-controls,FamilyControls,ManagedSettings,DeviceActivity,AuthorizationCenter,CKShare,sharedCloudDatabase,publicCloudDatabaseeageRatingCode. Zero arquivos correspondentes nos sete projetos,AskCenterincluído. Dois padrões de controle rodados pelo pipeline idêntico voltaram diferentes de zero (StoreKitbateu em três arquivos no Reps,CKContainer|NSPersistentCloudKitContainer|SwiftDatabateu em 17 no Banana List), que é como sei que o pipeline lê arquivos em vez de falhar em silêncio. O onboarding do Ace Citizenship (IntroCarouselView,WelcomeView,AddStateView,AddRepresentativeView) não contém campo de idade ou de data de nascimento, e seuPrivacyInfo.xcprivacydeclaraNSPrivacyCollectedDataTypesvazio; a linha de elegibilidade “Age 18 or older” vem de~/Projects/acecitizenship.app/content/blog/n400-application-guide.md. ↩↩↩ -
Levantamento do autor, 26 de julho de 2026: a varredura ampliada e o Randori. A varredura ampliada rodou os mesmos 16 padrões em todos os repositórios sob
~/Projects, podando os mesmos oito nomes maisnode_modulese excluindo caminhos no gitignore, e bateu em 21 arquivos. Sete são código: seis arquivos Swift sobRandori/Randori/e_archive/Oishii-AI/Oishii AI/Services/CloudKitManager.swift(publicCloudDatabase, num projeto arquivado). Os outros 14 são prosa ou estado de máquina: nove planos e documentos de design do Randori, três posts no própriocontent/blog/deste site (um deles o rascunho deste artigo), uma nota de transferência do Obsidian eobsidian-signals/40-Projects/blakecrosley-com/wwdc-2026/.pulse_state.json, escrito por um script e não por uma pessoa. O Randori écom.wayofyawara.randori, app 6789693294 no App Store Connect com a versão 1.0 em PREPARE_FOR_SUBMISSION.CKShareesharedCloudDatabasebatem em 20 linhas de seis arquivos:ConnectionStore.swift(10),RandoriApp.swift(5),ProfileCardView.swift(2) e uma linha em cada um deCKPostTransport.swift,CloudShareSheet.swifteSocialContracts.swift.ConnectionStoredescreve o próprio formato no comentário de cabeçalho como “one zone (ProfileCardZone), one record type (ConnectionCard), one share”, mantémprivateCloudDatabaseesharedCloudDatabaselado a lado e implementaaccept(_ metadata: CKShare.Metadata)na linha 355,ensureOutboundShare()na linha 547, além de bloqueio e desbloqueio por conta.RandoriApp.swiftimplementauserDidAcceptCloudKitShareWithtanto no app delegate (linha 73) quanto no window scene delegate (linha 97, com o comentário “Warm: the app is running when the link is opened”), enquantoRandoriSceneDelegate.scene(_:willConnectTo:options:)lêconnectionOptions.cloudKitShareMetadatana linha 88, sob o comentário “Cold start: the invitation rides the connection options”, razão pela qual o corpo do texto atribui o lançamento a frio às connection options e não ao callback de aceitação.TagConsentStoreé o registro de consentimento próprio do app, delimitado por conta do iCloud, e seu padrão documentado é falhar em modo fechado: um parceiro cujo registro de política ainda não chegou não pode ser marcado.Randori/Randori.entitlementssolicita CloudKit, HealthKit eaps-environment. Todo padrão de consentimento exceto os dois de compartilhamento do CloudKit devolve zero naquele repositório, e o mesmo vale paraStoreKit,signedPayloadenotificationType, então o app não vende nada e não roda servidor próprio;docs/asc-metadata.mdprepara preço gratuito e classificação etária 4+. ↩↩↩↩ -
Levantamento do autor, 26 de julho de 2026: pontos de chamada do StoreKit e evidência de servidor. O StoreKit aparece em três projetos:
Reps/Reps/Services/RepsProStore.swift(renovação automática, duas faixas),Ace Citizenship/StoreKitManager.swift(um não consumível) eResumeGeni/Subscription/(uma assinatura mensal, controlada no servidor). O tratamento de.pendingcitado na tabela está emRepsProStore.swift:134(case .userCancelled, .pending:),StoreKitManager.swift:69-73(umcase .pending:separado cujo próprioreturn falseé a linha 73) eSubscriptionStore.swift:209-212(um resultado pendente nomeado). Os pontos de chamada são o que deixa o usuário na mão:RepsProPaywallView.swift:359-361fecha apenas dentro deif await store.purchase(plan), eContentView.swift:484-485libera apenas dentro deif success, então umfalsedevolvido para uma compra pendente não muda nada na tela em nenhum dos dois apps. O ResumeGeni, em vez disso, chega aPaywallView.swift:526, um ramo.pendingque exibe o toast “Waiting for approval. You’ll get access once it’s approved.” O endpoint de App Store Server Notifications do ResumeGeni éPOST /api/appstore/notificationsem~/Projects/resumegeni/app/routers/appstore.py; umPOSTde{"signedPayload":"probe"}devolve HTTP 400{"status":"invalid"}, alcançável apenas depois da flag de habilitação e dentro do verificador de cadeia de certificados da Apple, eGETdevolve 405. As duas URLs de ambiente estão cadastradas, e a evidência disso é entrega, não runbook: o banco D1941-analytics, tabelafunnel_events, guarda 56 linhas complatform='ios'cujos metadados trazemnotification_type, de 2026-06-26T15:48:51Z a 2026-07-25T16:40:11Z, das quais 55 trazemenvironmentsandboxe uma trazproduction(2026-07-21T18:08:21Z). Trate 56 como limite inferior, porque o serviço mapeia apenas cinco nomes de evento para linhas de funil; os quatro tipos de fato observados são DID_RENEW (42), SUBSCRIBED (6), EXPIRED (5) e DID_CHANGE_RENEWAL_STATUS (3).app/services/app_store_notification_service.pynomeia 13 tipos de notificação, dos quais oito alcançam um ramo de despacho executável em_funnel_event_name(linhas 75 a 89): SUBSCRIBED, OFFER_REDEEMED, DID_RENEW, REFUND, REVOKE, EXPIRED, DID_CHANGE_RENEWAL_STATUS e DID_FAIL_TO_RENEW. Os outros cinco só ocorrem em comentários: PRICE_INCREASE, RENEWAL_EXTENDED e METADATA_UPDATE na linha 73, EXTERNAL_PURCHASE_TOKEN e TEST na linha 187.RESCIND_CONSENT,CONSUMPTION_REQUEST,REFUND_DECLINEDeREFUND_REVERSEDdevolvem zero ocorrências em todo aquele repositório. Para completar sobre o que não é evidência:docs/SUBSCRIPTION_GO_LIVE.md:83de fato diz “configure AMBAS as URLs, a de Produção e a de Teste”, mas a linha é instrução de runbook sob um título que observa que o passo “needs your re-auth”, então registra uma intenção e não um cadastro concluído, e a afirmação de cadastro acima se apoia nas notificações entregues. ↩↩↩ -
Apple, Testing Ask to Buy in Xcode, StoreKit. Fonte da descrição do mecanismo pela Apple (“With Ask to Buy, when a child wants to make an eligible purchase or download, the system sends the purchase request to the parent or guardian”) e do comportamento em caso de recusa (“Your app doesn’t receive a transaction because you declined Ask to Buy”). O artigo também documenta o botão Ask to Buy sob Purchase Options no editor de configuração do StoreKit, e o gerenciador de transações indica Pending Ask to Buy, Ask to Buy Approved e Ask to Buy Declined. ↩↩↩
-
Apple, Product.PurchaseResult.pending e Transaction.updates, StoreKit, ambos disponíveis a partir do iOS 15.0. Fonte do resumo do caso (“The purchase is pending, and requires action from the customer”), do caminho de resolução (“If a pending purchase succeeds, StoreKit delivers the resulting
Transactionin the transactionupdates”) e do propósito da sequência (“This sequence receives transactions that occur outside of the app, such as Ask to Buy transactions, offer code redemptions, and purchases that customers make in the App Store”). O enum Product.PurchaseResult publica três casos:success(_:),pendingeuserCancelled. O próprio exemplo da Apple na página do enum comenta o ramo pendente como “The purchase requires action from the customer. If the transaction completes, it’s available throughTransaction.updates”. ↩↩ -
Todo símbolo da listagem de roteamento acima foi verificado contra a JSON de documentação da Apple em 26 de julho de 2026.
AgeRangeService.sharedéstatic let shared: AgeRangeService(iOS 26.0). Os valores de ambiente do SwiftUI sãorequestAgeRange,var requestAgeRange: DeclaredAgeRangeAction { get }(iOS 26.0), eshowSignificantUpdateAcknowledgment,var showSignificantUpdateAcknowledgment: SignificantUpdateAction { get }(iOS 26.4). O piso@available(iOS 26.5, *)da listagem não vem de nenhum dos dois:AgeRangeService.AgeRangeDeclaration.confirmedpublica iOS 26.5, iPadOS 26.5, Mac Catalyst 26.5 e macOS 26.5, um release depois da ação de reconhecimento, então qualquer código que distinga um adulto confirmado herda o piso mais alto. O próprio projeto de exemplo da Apple publica a mesma disponibilidade 26.5.6AgeRangeService.AgeRangeexpõelowerBound,upperBound,ageRangeDeclarationdeclaradovar ageRangeDeclaration: AgeRangeService.AgeRangeDeclaration?, eactiveParentalControls. A comparação== .confirmedcontra esse opcional é válida porqueAgeRangeDeclarationestá em conformidade comEquatableeHashable, segundo sua seção de relações. O inicializador doPermissionButtonéinit(question: PermissionQuestion<Topic>, @ViewBuilder label: @escaping () -> Label), restrito aSignificantAppUpdateTopicna sobrecarga usada aqui.26ChangedFeatureVieweAccountVerificationPromptsão placeholders para suas próprias views, não símbolos da Apple. ↩↩↩ -
Apple, Age ratings values and definitions, Ajuda do App Store Connect, lido em 26 de julho de 2026, como fonte confirmatória de que as mudanças de 18 de junho de 2026 anunciadas na nota 9 entraram em vigor. Sob “Australia age rating values” a tabela agora publica duas classificações, 16+ e R 18+, sem linha 15+. Passou a existir uma seção separada “Vietnam age rating values”, apresentada como “As required by Article 38 of Vietnam Decree 147”, e sua linha 00+ é definida como apps que “contain no objectionable material but may contain instances of the following content”, listando controles parentais, verificação de idade, conteúdo gerado por usuários, mensagens e chat, publicidade e concursos infrequentes. A lista de descritores da Apple de 21 de maio para a migração australiana não corresponde à lista de gatilhos 16+ atual desta página, uma discrepância que não resolvi e na qual não me apoio, porque a afirmação extraída aqui é apenas que a faixa 15+ desapareceu e que a tabela do Vietnã existe. ↩↩↩