Acceptances
Some changes can break someone else, such as removing a field clients
read. jadpo check fails on them until a person accepts them. An
acceptance is one file per decision, listing every change it allows,
with the reason in a /// line. This one allows removing the priority
field from what API main sends as a TodoView:
acceptances/20261020T1400_remove_todo_priority.jadpo:
/// Priority was never shown by any client; the mobile app stopped sending it in 3.2.acceptance { changes: { main.TodoView.priority: ApiChange.output_field_removed }}
acceptance, changes: and ApiChange are built in. The rest of the entry
comes from the compiler: each entry is subject: Kind.change, with the
subject and the change copied from its message. A subject is a dotted name,
or, for a route, its API, method and path in quotes, since a route has no
name. You don't need to type one, since jadpo acceptance new drafts the
file (below).
- It counts only in the change that adds it. Acceptances already on
the before side are history, and editing one is an error
(
acceptance.edited).checkfails when a change needs a person and no added acceptance lists it (acceptance.missing), when a listed entry matches nothing (acceptance.unmatched), or when the reason is missing (acceptance.no_reason). A file that isn't one acceptance with itschanges:isacceptance.invalid. A quoted key anywhere else is a syntax error. - Drafting is a command.
jadpo acceptance new <purpose>writes a file listing every change not yet accepted, with no reason. An agent may run it, then stops: the missing reason keepscheckfailing until a person writes one. - The git host is the gate.
acceptances/,api/,access.jadpo_recordandnetwork.jadpo_recordare owned in CODEOWNERS, so the owner's approval of the pull request is what lets a decision in, and the host records who gave it (Review and sign-off).jadpo newwrites aCODEOWNERSlisting them, for the project to name its owners.
| Needs a person | Kind | Where it is described |
|---|---|---|
| A route removed, a required input field added, a rule tightened, an output field removed or retyped, a body sent as another media type, a failure code changed | ApiChange |
The public API record |
| An event removed, or an event's field removed, retyped, newly required or its rule tightened | ApiChange |
The event record |
| A column or table dropped, an entity removed, an entity moved to another store | DataChange |
Removing data |
| Any widening of who can do what, however it arises | PolicyChange |
Policy |
| A sign-in setting loosened past its limit | PolicyChange |
Magic links |
| Any change to how a role must be signed in to, tighter or looser, or a role's line added or removed | PolicyChange |
Roles that need a way in |
A git location added to, or widened in, packages:'s own_sources: |
PolicyChange |
Installing safely |
| A domain created or renamed | DomainChange |
Domains |
| A risky package change (Package upgrades) | PackageChange |
External services |
| A package's service: a new or changed container image, before it runs | PackageChange |
External services |
| Installing a third party's package from git rather than the registry (an unverified publisher) | PackageChange |
External services |
| A connection that receives personal data but can't erase it, with the reason the data stays | ErasureNotPossible |
External services, Erasure |
| A threshold raised or removed | ThresholdChange |
Monitoring |
Declining an error the compiler's catalogue marks as a security rule, such as policy.call_ungranted |
PolicyChange |
Writing a decline |
A new outside host the app can send data to: a provider's host, a connection's host:, an outbound: grant or an email relay |
NetworkChange |
External services |
Changes that only add need no acceptance: a new route, even one anyone may call, a new optional input field, a new output field, tightened policy, listing a query, a new entity that belongs to a tenant. The diff still shows them.
Each change the compiler reports, and the subject it names:
| Change | Subject | When |
|---|---|---|
ApiChange.route_removed |
"main GET /todos/{todo_id: Uuid}" |
A route's entry in the API record is gone |
ApiChange.input_field_required |
main.CreateTodo.due |
A field clients must send is new on a type they send, or on an event |
ApiChange.rule_tightened |
main.CreateTodo.title |
A field clients send allows fewer values: a bound raised or lowered, a rule added, a type changed, none no longer allowed, or a field that could be left out no longer can; an enum variant gone from what clients send |
ApiChange.output_field_removed |
main.TodoView.priority |
A field clients read is gone, from its type or from the type a route now answers with |
ApiChange.output_field_retyped |
main.TodoView.priority |
A field clients read allows more or other values |
ApiChange.media_type_changed |
"main GET /deeds/{deed_ref: Text}" |
A route's sends line in the API record is new, gone or names another media type (Sending raw bytes) |
ApiChange.failure_changed |
main.todo_locked |
A failure code is gone, or its status changed |
ApiChange.event_removed |
planning.TodoCompleted |
An event is gone from its domain's event record |
PolicyChange.route_public |
"main GET /books" |
A route that needed a signed-in caller is open to anyone |
PolicyChange.confirmation_removed |
"main POST /payouts/{payout_id: Uuid}/send" |
A route that needed the person's passkey no longer asks for it (Confirm one request with a passkey) |
PolicyChange.widened |
library.Book.read, library.Book.cost.read, lending.borrow |
A caller is added to an entity's or a field's grant, or to a listed action's or query's line; or a field's own list now follows its record's |
PolicyChange.restriction_removed |
library.Book.cost |
A field's own row, or its server_only, is gone |
PolicyChange.tenancy_loosened |
library.Book, or tenant |
An entity that belonged to the tenant is shared by every tenant, or the app's tenant is gone |
PolicyChange.shared_entity_added |
library.Genre |
A new entity shared by every tenant |
PolicyChange.listing_added |
lending.borrow |
An action is newly listed under actions:, which replaces its entities' grants |
PolicyChange.listing_removed |
catalogue.staff_books |
An action's or a query's line under actions: is gone |
PolicyChange.call_ungranted_declined |
lending.borrow |
A new decline policy.call_ungranted:, on the declaration it sits above |
PolicyChange.sign_in_loosened |
magic_link.expires_after |
A setting past its limit and looser than before |
PolicyChange.role_requirements_changed |
desk.StaffRole.admin |
A role's requires: line changed, tighter or looser, or a role's line added or removed |
PolicyChange.own_source_added |
"github.com/acme/" |
An entry in packages:'s own_sources: that wasn't there, or one that now covers more; not one below an entry already there, which lets nothing new in |
DomainChange.created |
billing |
A folder under domains/ that wasn't there |
NetworkChange.host_added |
couriers |
A connection reaches a host it didn't, a new connection's included; one entry covers every new host of the connection |
PackageChange.git_source |
AcmeCouriers.v1 |
A package locked from git that wasn't, or locked from another repository than it was, from a repository under none of packages:'s own_sources: |
DataChange.column_dropped |
Todo.legacy_flag |
A migration this change adds drops a retired column, by the field's born name, as changes.jadpo names it |
DataChange.table_dropped |
Todo |
A migration this change adds drops a retired table, by its entity's born name |
DataChange.entity_removed |
Todo |
A migration this change adds retires an entity's table, since the entity is gone |
DataChange.entity_moved |
Todo |
A migration this change adds gives an entity a table in another store, leaving its records behind |
PackageChange.operation_removed |
AcmeCouriers.bookings.get |
An operation, action or library function the app used is gone (Package upgrades) |
PackageChange.declaration_removed |
Postcodes.UkPostcode |
A type, failure or message the app wrote is gone |
PackageChange.operation_changed |
AcmeCouriers.bookings.get |
A used operation's request, input, output or what it changes, or a used function's or action's signature, is different |
PackageChange.field_removed |
AcmeCouriers.Booking.driver_phone |
A field or enum variant of a type the app uses is gone |
PackageChange.field_retyped |
AcmeCouriers.Booking.weight, or Postcodes.UkPostcode |
A field of a type the app uses has another type or rules; or a used refined type's base or rules, or a used failure's code or kind, changed |
PackageChange.failure_added |
AcmeCouriers.bookings.create.address_unverifiable |
A used operation, action or function can fail in a new way, named by the failure's code |
PackageChange.host_changed |
AcmeCouriers.host |
A connected provider package's hosts: changed; one entry covers them all |
PackageChange.credential_changed |
AcmeCouriers.credential |
A connected provider package's credential scheme changed |
ErasureNotPossible.data_stays |
couriers |
A connection receives personal data and can't erase it (Erasure at providers), with no acceptance on the before side listing it |
ThresholdChange.raised |
planning.todo_list.p95, planning.todo_list.within |
A threshold's limit is higher than the same-named threshold's on the before side, or its within: longer |
ThresholdChange.removed |
planning.todo_list.p95, or planning.todo_list |
A limit is gone from a threshold; or the threshold is gone, or watches something else |
ThresholdChange.lowered |
uploads.free_space_floor |
A disk store's free_space_floor: is lower than it was |
Two records give these checks a before side, and put what changed in
front of the reviewer: access.jadpo_record, who can do what, from the
permissions report, and network.jadpo_record, every outside host the
app can reach, from the network report. jadpo check keeps them at the
root beside api/, as it keeps the API record, and jadpo records update
writes them. A project with nothing for one to hold has no such file,
except that an app with a connections: block keeps
network.jadpo_record, its header alone while it reaches no host.
// Generated by Jadpo from who can do what; update it with `jadpo records update`, never by hand.record access 1tenant Companyentity work.Project create CompanyRole.editor read CompanyRole.editor read CompanyRole.viewer tenant company_idfield work.Project.budget read CompanyRole.editoraction todos.transfer_todo who Todo.owner_iddecline policy.call_ungranted lending.borrowsign_in magic_link code_attempts: 5 expires_after: 15m failed_codes_per_day: 20
// Generated by Jadpo from the hosts the app can reach; update it with `jadpo records update`, never by hand.record network 1connection couriers for AcmeCouriers.v1 host https://api.acme-couriers.example host https://sandbox.acme-couriers.exampleconnection main for database host config.database_url
A field's own list that lets nobody in is written read nobody, so it
reads apart from a field that follows its record's list. A route made
public is read from the API record, whose route entry says
authentication none.
Markdown for agents: /docs/acceptances.md.Built from Jadpo main at aee2e8f.