← The specification

Decision log

Rendered from spec at76d46f3 (2026-10-25) · its source · Edit this page

Status: Accepted

Every decision the maintainer makes, in the order it was made, and where it now lives in this specification.


How a decision is recorded

A decision is recorded in this specification on the day it is made (GOV-R49). Where it decides what is built, it lands as a requirement or an ADR, and the row here points at it. Where it decides how the work is done, the dated row here is the record. A note kept outside a repository is not where a decision lives: nobody else can read it, nobody reviews it, and it goes when the machine it is on does.

A row states the decision as it was made. Why it was made is the requirement’s or the ADR’s to say, and a row whose decision has not reached either yet says so.

Decisions about a repository outside this organisation, such as nightworksio/php-mutation-gate, are recorded in that repository.

2026-09-29

Decision Where it lives
What the phone keeps: a separate 32-byte data key in the platform keychain at its narrowest accessibility; AES-256-GCM through Laravel’s Encrypter with that key, never the app-wide key; the payload sealed and the bookkeeping (kind, read time, shape version, a keyed hash of the stack) queryable ADR-0035, N27
Readings kept per stack (health, what runs, updates, household), read only until a fresh reading arrives; repair offers never kept; preferences kept (lock period, notification kinds, where you were, stack order) N27
Markers for what is new on updates, household requests and findings, filterable per stack and in a What’s new list by kind and stack N27
Kept readings live for 1–365 days or until the stack is forgotten, per phone, default 30; readings in an old shape are discarded and preferences migrated N27
Without secure storage nothing is kept between launches, and the settings screen says why; a lost key wipes the unreadable store and leaves pairings in place N27
Storage lives inside the module that owns it (src/Internal/Store and its own migrations), behind a port that module declares; no module exists only to keep data ADR-0035
Finding your way (N28): the bottom bar is hidden on screens that are not tabs; Doing and Logs count as Services; Allowance is an operator screen with the full menu; the switcher is a bottom sheet; sign-in shows the switcher only; What’s new and the settings rows are placeholders until N27 fills them; menu items push their screen; the drawer comes from each stack screen through the shared trait N28
Short state words and menu labels in English and Dutch, as approved in the N28 work N28
Navigation and controls carry one- or two-word labels with an icon, and sentences only where something is explained Not yet a requirement: the plain-language pass over the app’s wording needs a voice rule

2026-09-30

Decision Where it lives
Every refusal carries a structured, machine-readable reason, and the status stays 403 ARCH-R138, ARCH-R139, N1-R75, N3-R17
N28: the Dutch group headings are Huishouden · Toegang · Machine · Instellingen · Hulp; a placeholder screen says “This isn’t in this version of the app yet.” / “Dit zit nog niet in deze versie van de app.”; the ☰ screen-reader label is “Menu” N28

2026-10-05

Decision Where it lives
NZBHydra2’s authentication is seeded as D1-R22 to D1-R26 drafted, locked in 0.17.0 beside F9-R2 D1, 0.17.0
What backup::REBUILT holds moves to a per-service field in stack.toml, as a follow-up across the spec, the stack and the core Not yet a requirement
Two core pull requests, lemonfiber#864 and #870, are held until each security flag on them is checked adversarially and fixed or shown not to apply This row
A consumer of sdk-python vendors it and writes the vendored revision to _vendor/lemonfiber/REVISION as 40 hexadecimal characters This row
After the first release pushes ghcr.io/lemonfiber/lemonfiber, the package is made public This row

2026-10-07

Decision Where it lives
F8’s engine failure (option C): a problem with PLUGIN-35 or PLUGIN-36, plus a structured per-step field on the problem, optional and absent elsewhere G4-R17
The core’s offer is optional (option A): refused if it has moved, and its absence acts as it does without one spec#652
Pause, resume and restart are idempotent; diagnose and update are not ARCH-R160
The event stream is tied to its job: the envelope carries job; a pull is state; the command line says pull; a start pulls what is missing, then brings the stack up, with Compose 2.22 or later web-API contract, spec#649
N28-R6: option A N28-R6
sdk-python lists the breaking changes it accepts in pyproject.toml This row
Companion slice 13: D1 (b), D2 (c), D3 (a), D4 (a), D5 (a), D6 (b) This row; the options these letters choose between are not recorded here
Every repository a version is satisfied in keeps its own tracker, and the release gate and the no-stubs gate read all of them spec#654
A tracker row is one requirement: its id, its state, its evidence by path and test, and optionally the commit it landed in; the binary’s rows move to that shape spec#654
A report of where every goal of every unreleased version stands (met, built but unmarked, claimed, open) runs on a schedule, and just goals <version> writes it locally spec#657
Work is claimed by the Spec: lines of open and draft pull requests, and ./claim is retired spec#657
A feature’s maturity is generated from the trackers and the manifests, with the reverse check in the specification’s CI first spec#658
A manifest comment never says how far a goal has got, and a lint with a narrow, tested word list refuses it with a clear message spec#659
Every decision lands as a pull request on this specification the same day, with a dated log for decisions about the process GOV-R49
The kept-and-where half of the companion’s register moves into the companion’s tracker, and the register’s disagreements with the code are fixed This row
The policy in AGENTS.md, the AI contributors’ rules and the working notes kept outside the repositories moves into this section; every AGENTS.md points first at the report, then at this section; subagents work in worktrees and the main session in the main checkout GOV-R50, GOV-R51
IMPLEMENTATION-STATUS.md is no longer committed once the documentation site’s roadmap reads the report, and is generated until then; a tracker stays under 1,000 lines, split into status/<feature>.toml where it would not spec#654
Each generation of the report is published where it stays fetchable, as a workflow artifact or release asset, with the revisions it read recorded in it; nothing is committed hourly; the documentation site vendors a copy of state.json with its provenance, and its format is held stable and documented spec#657
An agent checks CI on every pull request it owns while it waits on any one, and fixes reds at once GOV-R53
The forge is read through REST only, a pull request’s checks at most every ten minutes; the spec, the binary and sdk-php no longer require a pull request to be up to date, so a branch is rebased only on a real conflict GOV-R52, GOV-R54
The roadmap, the board, the repositories, the work in flight, the releases and the proposals are on lemonfiber.app, rendered from the report’s snapshot; the documentation site renders no project status. This replaces the rows above that had the documentation site’s roadmap read the report and vendor state.json: IMPLEMENTATION-STATUS.md goes once the frontpage reads the snapshot and the documentation site no longer mirrors it REPO-R39, REPO-R64, REPO-R68
The changelog is published at lemonfiber.app/releases/, from the core’s changelog JSON REPO-R64
The snapshot is published as the assets of a rolling board release in spec, replaced on each run, and as the run’s artifact; it is a new board.json beside state.json, whose format: 1 is unchanged OPS-R80, OPS-R81
A repository the report cannot read is listed under unread, its goals read unknown, and the report still publishes; the release gate still refuses OPS-R76
The report runs when a manifest, the catalogue or a tracker changes on a default branch and when a release is published, and hourly OPS-R82
Where the snapshot cannot be read, the frontpage’s build fails and the previous deployment stays live; no committed copy stands in REPO-R40
The board’s interactive parts are a framework island (Preact or Svelte), bundled with the site REPO-R65
The website helps a contributor compose a change and never creates anything itself: its forms build the change, and submitting opens GitHub prefilled under the person’s own account, where they open the pull request, or the issue that becomes one; no bot or server acts on their behalf GOV-R60, GOV-R61
A proposal or a gap enters as a pull request adding a Draft proposal with no identifier, and identifiers are allocated when a maintainer approves it; the RFC issue form stays an optional entry a maintainer converts GOV-R40 to GOV-R45
A claim made with the developer command line opens with an empty signed commit carrying Spec: and a sign-off; a claim made from the website adds the repository’s status.toml row as open GOV-R57
A repository holds at most three open pull requests opened by people and agents, the organisation’s bots not counted; the command line refuses a fourth claim, a bot comments, and the board flags the repository GOV-R58
The report derives what the board flags about claims, so the website and the command line show the same: a repository over the cap, a goal of an unreleased version cited by more than one open pull request from people or agents, and a draft with no commit for more than fourteen days GOV-R58, OPS-R80
The release train’s tracker and planning issues are retired: the board’s version page replaces the tracker, and a release blocker is a list in the manifest OPS-R83, OPS-R44, OPS-R45, OPS-R55
Contributor material moves to a site of its own, apart from the documentation site ADR-0040, REPO-R69 to REPO-R73, REPO-R80
The specification is rendered on lemonfiber.app, not on the documentation site ADR-0040, REPO-R79
The documentation site stays on Astro Starlight, extended ADR-0040
The developer command line has a repository of its own, tool-lfdev, and installs as lfdev tool-lfdev, REPO-R74
lfdev is written against Python’s standard library alone REPO-R74
The contributor site is the repository website-contribute.lemonfiber.app, served at contribute.lemonfiber.app website-contribute.lemonfiber.app
lemonfiber.app renders the specification at the commit of spec the board snapshot read, and rebuilds it on the same event as the board REPO-R79
Each release’s contract artefact describes the event stream, carries an example of every kind, read and action from the core’s golden fixtures validated in CI, and is accompanied by the code registry as contract/codes.json, a contract-diff.json against the previous release and a generated OpenAPI document (D24, D25, D26) ARCH-R173 to ARCH-R177

2026-10-08

Decision Where it lives
The machinery the documentation and contributor sites share lives once, in website-kit, a package each site takes by commit as it takes brand; nothing is published to a registry REPO-R84, REPO-R85
What a credential buys is never released: a value captured from a call that carried or read a credential-store value is held to that credential’s service as the credential is, and a release on it is refused F8-R16, F8-R17
The documentation keeps every minor from 0.16.0 on, plus next REPO-R89
Stable documentation renders the commits each release recorded; a repository the release recorded nothing for is rendered at its default branch’s last commit on or before the release date REPO-R88
A released pair carries from, the service its value was read from, on the rehearsal and on the record, beside its release ARCH-R147, ARCH-R150
Every repository’s AGENTS.md opens with the pointer to the board and the shared rules and holds no more than 120 lines, refused by hygiene otherwise; every repository’s file is brought within it in the same release GOV-R50
An approved proposal lands as Draft: a new feature at status: draft, and rows added to an existing feature each opening with *Draft:*; a later reviewed pull request removing the marking hardens it GOV-R42, GOV-R44, GOV-R48
The web console lists, mints and revokes integration keys: the operator’s password is typed for each mint and never kept, and the secret is shown once, in a panel that drops it when closed or on reload C10-R2, C10-R3
The web console installs, updates and removes plugins, bound to the offer: it draws the offer’s whole manifest (what it writes, what it reaches, its verification, every released pair with its release and from) before the yes, and the yes names that offer F6-R15, F6-R17
The web console takes the operator’s side of a mobile hand-off now; the member’s side gets a design of its own later G9; the member’s side is not yet a requirement
The web console explains a domain term inline wherever it appears (option b): its own words and every glossary word or form found in what lemonfiber writes, with a switch kept per browser to turn explanations off G2-R1, G2-R7
The web console draws the scannable hand-off code with one small, zero-dependency, MIT-licensed QR encoder taken as a runtime dependency, pinned exactly and rendered as inline SVG; the companion uses the same choice for its pairing code This row, G9-R2
sdk-ts is kept by the web console’s agent, which adds its client calls for the setup routes once the contract describes their bodies This row; the bodies are ARCH-R133
The core publishes the setup routes’ bodies in the contract (option a): wizard::Answer and recovery::Choice derive their schema, and the six setup routes are described with them ARCH-R133
What’s new (news, news-items) stays with the companion, and the web console does not mark new items N27
The documentation site cuts a release’s frozen build on its own nightly run after the release’s day ends; release-finalize sends it no dispatch, since on the release day there is nothing it may cut REPO-R88, REPO-R89
The companion keeps what it implements only in its status/<feature>.toml tracker: its .docs/requirements pages are retired once the tracker holds every requirement they named, and the prose still worth keeping moves into the docblocks of the code that keeps each requirement OPS-R74; this row
The companion installs a plugin as drafted for N5, N20 and N25: it rehearses the install in full, approves each value that would leave the machine on its own and agrees to the install separately, and takes the source in one text field that explains the three shapes it may take N5, N20, N25
The companion takes no recipe inputs: a plugin whose recipe asks for one is rehearsed in full, and its install says the inputs are given at the web console or the terminal N17-R6; this row
The companion’s plugin work lands in two pull requests: the list, installing, the rehearsal, agreeing and following first, then updating and removing This row
The documentation site’s stack-manifest pages keep only what an operator does with a stack and link the field reference to the contract on the frontpage; once the core publishes contract/stack-manifest.schema.json, as it does the plugin manifest’s, the site renders the field tables from it REPO-R82, ARCH-R172
The organisation requires a sign-off on every commit made through the web, so GitHub appends the merger’s sign-off to a squash merge This row, GOV-R29
A squash merge in every repository takes the pull request’s title and body as the commit message, and a required squash-message check, shared like dco, holds the title to a conventional subject and the body to the Spec: line and each author’s sign-off GOV-R5, GOV-R29, GOV-R62
The frontpage shows all twenty-two services the stack runs, lemonfiber’s own request gate and decline service among them, and its build fails where its list and the stack manifest’s include differ This row, ARCH-R171
The frontpage offers a claim on a page per goal, /claim/<id>/, linked from the work to pick up and from the requirement’s row: a claimed goal names its pull requests and their authors and offers nothing, a met one says so, each repository shows its open pull requests against the cap and offers nothing at it, and the claim is offered as lfdev claim <id> and as the tracker row to paste in GitHub’s editor, with the pull request’s title and the Spec: and sign-off lines its body ends with This row, GOV-R57, GOV-R58, GOV-R62
The frontpage’s propose and gap forms are proved end to end from an account outside the organisation, by a checklist in the website’s README that the maintainer runs This row, GOV-R60
The release train keeps one bump pull request per repository, on one branch rebuilt from main for each version and retitled to it; the pin fan-out’s per-version pull requests are closed in favour of it OPS-R85
The shared checks that run in seconds are one job per pull request, gates, each a step that runs whatever the others concluded, its summary listing every result and branch protection requiring it as one context; what writes to a pull request is a second job that judges nothing; the tests, coverage, CodeQL’s analysis and Sonar stay jobs of their own Q-R82
The plugin reading lists each recipe input a plugin asks the operator for, with its name, its question and whether it is a secret, so a surface collects it before the yes; a secret input is never shown back F8-R19
On a stack with no configuration the web console shows the setup wizard in place of everything else until setup is applied A2-R1
Where configuration already exists the web console offers no wizard: it works as usual, and a “Change setup” entry leads to the reconfiguration screens A2-R14, A4
An apply that stopped part-way opens the web console’s wizard on a recovery screen first, naming what was written and offering resume, roll back and start over A2-R10
The web console’s wizard ends on a “Setup written” screen that starts the stack, then connects its services as seed does, with the console one press away throughout A2-R13, A2-R17
Until lemonfiber reports what a setup step that only informs has found, the web console’s wizard says what that step is for and claims no finding A2-R5, A2-R9
Going without a VPN in the web console’s wizard is sent only once the operator has ticked a sentence saying everyone they share with will see their home address A2
A plugin reading lists each input its recipes ask the operator for, with its name, its question and whether it is a secret; no client sends a secret input, the companion takes no input at all, and the web console takes the ones that are not secret F6-R17

2026-10-09

Decision Where it lives
No step of gates runs code out of a pull request’s tree: each action it calls is one known to read the tree as data; markdownlint is handed the tracked Markdown, read out of git, and the canonical configuration, never a configuration of the pull request’s; OSV-Scanner runs in a container given the tree read-only and nothing of the job’s, with every call analysis off; and a workflow edits or removes a sticky comment only where its own token wrote it Q-R82, GOV-R33
A pull request cannot change what its repository’s checks run on its own say (option A): pin-only, run from the base branch on pull_request_target with a read-only token and nothing of the pull request checked out, fails any change under .github/workflows/ or .github/actions/ other than spec’s pins moving forward along main, and such a change is merged by a maintainer by choice; it is required in every repository once that repository calls it Q-R83
A change to what a repository’s checks run passes pin-only when the pull request carries workflows-approved, added by a maintainer after its last push and after the user said yes to that pull request; a push, a force push or the label’s removal voids it. The user chose this over turning enforce_admins off to merge such a change, which is never done Q-R83
The MCP server is built on the official mcp package’s low-level server, so the tools it lists can differ by key This row, F13
The MCP server generates its tools from sdk-python’s vendored contract/, taken at the same commit as the client: one pin for both This row, integration-mcp
The capabilities answer says which scope the credential asking has, so the MCP server words a member’s tools in the household’s words without inferring who is asking ARCH-R179, F13
An MCP action tool requires the offer its rehearsal answered and is refused by the server without one; a call reaching the core with no offer still acts as ARCH-R164 says F13
An MCP action tool follows the work it started for up to 45 seconds, then answers with the job’s name, and a job tool redeems it This row, F13
The MCP server’s HTTP mode takes the person’s key as Authorization: Bearer, and a write tool’s destructive hint is the contract’s disturbs This row, F13-R5, F13-R8
Until the stack’s capabilities have been read the MCP server offers one connection tool saying where it stands; after a refusal it keeps the last list and every tool says the key was refused This row, F13-R9
A program serving clients beyond its machine offers four certificate modes: the operator’s files, ACME from any RFC 8555 authority (self-hosted ones such as step-ca included), a root lemonfiber makes and constrains to the program’s names, and a self-signed certificate pinned by fingerprint; pinned is served where nothing is chosen, setting both file paths chooses files, and acme and private-ca are served only where LEMONFIBER_TLS_MODE names them; a private root lives ten years, constrained to the program’s names, and issues 30-day certificates; there is always a way that depends on no company outside the house, and these four are the contract for every lemonfiber program serving other machines, the core’s remote access included ARCH-R185 to ARCH-R198, F13-R12, F13-R13
ACME is spoken in-process through the EFF’s acme library and tested against Pebble; TLS-ALPN-01 is answered on the serving port by reading the ClientHello; port 80 opens only while an HTTP-01 challenge is pending; choosing acme in LEMONFIBER_TLS_MODE accepts the authority’s terms, whose address is logged; renewal failures reach the log hourly inside seven days and a /health answer; keys are ECDSA P-256 by default ARCH-R185 to ARCH-R198
DNS-01 ships with rfc2136, cloudflare, route53, gcloud, azuredns, digitalocean, hetzner, desec, duckdns and exec, each tested against a stand-in, every credential read from a file and never logged ARCH-R185 to ARCH-R198
What a tool result carries from the stack goes back to the model as data, delimited from the server’s own words F13-R14
The operator’s event stream carries playing, every active session in the envelope GET /api/playing answers, sent again every 5 seconds as a member’s stream is, so the Home Assistant integration follows what is playing from the stream rather than polling (user, 2026-10-09) ARCH-R178
The three sites load brand’s faces — Golos Text for body, Bricolage Grotesque for headings, DM Mono for code — served from website-kit, and the documentation and contributor sites’ headers carry brand’s logo mark beside the wordmark (user, 2026-10-09) REPO-R84
The three sites are hosted on Cloudflare (user, 2026-10-09): each is an assets-only Worker that its deploy workflow uploads with wrangler, using the organisation’s CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID, and the job reports and stops while either is unset; the headers the host sends are in the _headers file each build carries, written by website-kit, never in a zone rule; the domains move to the Workers once each answers at its workers.dev address This row, REPO-R84
A private root excludes whole each kind of name it is given none of, every address where no address is given and every host name where no host name is, carries an extended key usage of TLS server alone, and names what it issues by a label that is none of the names ARCH-R192

2026-10-11

Decision Where it lives
A bump finds and edits its rolling pull request in one shared place in this repository, which every bump moves to in one coordinated pass: it lists open pull requests by owner and branch, accepts only one whose head is the repository itself and which the release app opened, refuses unless its head is the commit the bump just made, and only then edits or opens it and arms auto-merge where asked; anything else fails and edits nothing OPS-R87
The core writes every Compose entry, the stack’s own and every plugin’s, from one typed service model; first-party and third-party plugins declare a service in the same closed vocabulary, and trust decides who may install, never what a manifest can say ADR-0043, ARCH-R224, ARCH-R225, ARCH-R246
An environment value is a constant, an operator setting with a default, or the URL of a capability’s filler; a mount stays inside the service’s own directory or the single data root; a device or a privilege comes only from a named shape, one added for each the stack needs; there is no field for the engine’s socket ADR-0043, ARCH-R226 to ARCH-R239
The core derives the networks from the asks between capabilities and assigns the door’s fixed address; no manifest names a network or an address, extending the networks a plugin’s service joins ADR-0043, ARCH-R241 to ARCH-R245
One operator storage setting, a bind or an NFS export with its server and export, applied by the generator to the single data mount of every service that takes data; the NAS overlay retires ADR-0043, ARCH-R240
A peer’s URL is named by the capability it fills, { url_of = "<capability>" }, never by a service id ADR-0043, ARCH-R232
There is no shared default network: every link is derived from an ask, and the stack’s wiring gains the two it lacked, the indexer manager asking indexer.proxy and the book curator asking for the download clients ADR-0043, ARCH-R241, ARCH-R242, stack-manifest
An ask and a url_of reach the adapter; a consumer reaches the fronted upstream’s own API by naming it with native; the core, an adapter and its upstream share a private network ADR-0043, ARCH-R243, ARCH-R253
A service’s state lives only under its own directory, with no engine volumes; the in-place move copies the proxy’s two volumes there once ADR-0043, ARCH-R228
No script is mounted into the egress guard: its filler reports the forwarded port over network.egress-guard and the core sets it on the torrent client over download.torrent ADR-0043, ARCH-R251
A fourth environment value, { secret = "<name>" }, names a declared [[secret]], is written to a per-service 0600 environment file and never into the project, and is redacted from rehearsals and diagnostics; Unpackerr’s URLs are one url_of for each curator ADR-0043, ARCH-R232, ARCH-R247
Settings live in the core’s settings store, editable from the command line, the web surface and the companion’s operator screens, and secrets in the secret store; the in-place move reads the stack’s environment file once, files each value under its plugin, journals it, and stops reading the file ADR-0043, ARCH-R248, ARCH-R249
Posture is declared; confined is allowed only once the plugin’s own CI has run the image under it, and nothing is refused at run time on account of posture ADR-0043, ARCH-R250
network.via starts its capability’s filler first and waits until it is healthy, whatever its profile, as the one named exception to B1-R14 ADR-0043, ARCH-R236, B1-R14
render may be declared only by a service providing media.serve, is approved by the operator apart from the offer as egress-guard is, and adds the device only where the host has /dev/dri ADR-0043, ARCH-R237, ARCH-R238
grants and its old spelling capabilities are still read and then refused, naming the shape that replaces each grant, superseding the promise that the old spelling is always accepted ADR-0043, ARCH-R246
The generated Compose project ships in 0.18.0: its requirements join 0.18.0’s goals under goals-change review, ARCH-R86, ARCH-R87, ARCH-R88 and ARCH-R101 are superseded, and F14-R23, F14-R27 and B1-R14 are amended in place ADR-0043, 0.18.0
An operator-written overlay on their own machine is merged last, never accepted from a plugin, shown as unvetted by the doctor and every rehearsal, and journalled: the escape hatch of ADR-0021 §6 on those terms ADR-0043, ARCH-R252
url_of is narrowed to one curator by the medium it files: { url_of = "library.curate", files = "tv", native = "servarr" } ADR-0043, ARCH-R232
render may be declared by a service providing media.serve or by the upstream a media.serve adapter fronts, and is approved by the operator apart from the offer ADR-0043, ARCH-R237, N25-R11
Settings get a web API surface, GET /api/settings and the settings-set and settings-clear actions, with companion screens and wording; settings are operator-only and a secret is never read back ADR-0043, ARCH-R254 to ARCH-R257, N25-R12 to N25-R14
standard keeps today’s plugin hardening for every service, the stack’s own included: no-new-privileges, only CHOWN, DAC_OVERRIDE, FOWNER, SETGID, SETUID and KILL plus what a shape adds, and a bounded process count; confined adds a read-only root, every capability dropped, the uid user and no shape; every plugin’s CI runs each image under its declared posture, and the media stack’s CI runs the stack’s images under standard before 0.18.0 ships ADR-0043, ARCH-R234, ARCH-R250, REPO-R93
A service’s further mount sources live in its own mount tree, ./mounts/<id>, apart from the configuration directory its container writes, and no source lies inside another, so no container can turn a source into a link the engine follows when it remounts ADR-0043, ARCH-R227, ARCH-R228
The companion lists, mints and revokes integration keys for the operator only, with no member screen N29
The companion mints a key only as a claim: the phone never mints a plain key and never receives a secret N29, ADR-0044
A mint from the phone is proved by a signature from a key pair in the phone’s hardware, StrongBox or the Secure Enclave preferred and the trusted environment accepted and shown as such, a key in software refused, usable only after face or fingerprint, registered once with the operator password and the platform’s attestation, over a single-use challenge the core issues; the operator lists and revokes device keys, and the password is never kept on the phone ADR-0044, C10, N29
The key link is handed over as a QR code, as copyable text and through the system share sheet, on a screen concealed from screenshots, recording and the app switcher, with when the claim lapses, and gone once left N29
The core accepts a device key only from an app identity it trusts, the Android package and signing-certificate digest or the iOS Team ID and bundle ID; the build ships the official NightWorksIO companion’s identities, the operator adds their own build’s as a setting over the command line and the web API, journaled and alerted, and an attestation from any other app is refused with a code of its own ADR-0044, C10
Attestations are verified against the platform roots shipped in the build, with no request leaving the machine; Google’s attestation revocation list is not fetched, as the accepted trade-off ADR-0044, C10
On iOS a device key’s backing enclave means the genuine app placed it in the Secure Enclave: App Attest vouches for the app instance and a hash of the public half as its client data, not for the key’s hardware ADR-0044, C10
A password change revokes every device key C10-R27, ADR-0044
The device key’s rows, the claim’s rows and N29 ship in 0.18.0, joining its goals under goals-change review, and C10-R2 is amended in place to accept a device key’s signature in place of the password 0.18.0, C10

2026-10-12

Decision Where it lives
The stack’s settings stay in its environment file, behind one closed table naming each by its setting, until the release that stops reading the stack’s static Compose files; a plugin’s settings and secrets go to the settings and secret stores at once ADR-0046
ARCH-R249 is amended in place: the first run of the release that stops reading the static Compose files reads the environment file once and files every value the assembled parts and the published settings read from it, journalling each, and the file is never read for them again; a part’s later move in place migrates nothing ADR-0046, ARCH-R249
An assembled part keeps its plugin manifest’s [[secret]], each held in the service id’s namespace ADR-0046, ARCH-R275, stack-manifest
The journal records a secret’s fingerprint and never its value, not even sealed; the secret store keeps one previous value, which undo restores only while the fingerprints match, and otherwise undo says the secret must be replaced at the terminal; E4-R1 is amended in place to match ADR-0046, ARCH-R276, E4
A core setting is one of a closed list lemonfiber publishes, beginning with LEMONFIBER_DEVICE_KEY_APPS; lemonfiber config keeps every other LEMONFIBER_* key, and each command refuses the other’s names, naming the right one; A4-R13 is amended in place to match ADR-0046, ARCH-R278, A4
ARCH-R257 is amended in place: the settings event goes to the operator alone, never to a member’s stream or a key’s ADR-0046, ARCH-R257
A credential that crosses to a plugin is read from its holder whenever an entry is written and never copied into the secret store, so replacing it regenerates what reads it ADR-0046, ARCH-R277
ARCH-R275 to ARCH-R278 ship in 0.18.0, joining its goals under goals-change review 0.18.0
A part with no network surface is proved live by a [[proof]] that runs its image’s own program, run = { args = [...] }, arguments to the image’s own entrypoint, on [[proof]] only; claim probes stay requests, and an expect checking only exit is refused as a status alone is ADR-0048, ARCH-R283, ARCH-R105
A run is a fresh container from the pinned digest under the service’s posture, user and memory limit, with no network and a throwaway copy of its starting files, removed afterwards; 30 seconds by default and 120 at most, 64 KiB of standard output read and 1 KiB recorded, standard error never judged, control characters stripped, an argv with no shell, an environment of the entry’s constants and TZ, PUID and PGID ADR-0048, ARCH-R284
A record mode performing the same bounded run writes a run’s recording ADR-0048, ARCH-R285, ARCH-R120, ARCH-R122
A run proof runs in the plugin’s CI, at install and update, in plugin prove, in the release gate against recordings, and in the doctor, which runs it only when the doctor is run and keeps its verdict until the digest, arguments, posture, user, starting files or release change; REPO-R96 and C1-R15 are amended in place ADR-0048, ARCH-R286, ARCH-R287, REPO-R96, C1
The core starts a proof’s container through the engine API, as an exception to ADR-0008 ADR-0048
ARCH-R283 to ARCH-R287 ship in 0.18.0, joining its goals under goals-change review 0.18.0
A device key’s attestation travels as an object tagged by platform: android with chain, each certificate’s DER in base64url, the key’s own first, or ios with object, the App Attest attestation object’s CBOR in base64url; ARCH-R258 is amended in place ARCH-R258, web-api-getting-in
On iOS the client data hash App Attest signs is the SHA-256 of exactly the bytes public_key decodes to, hashed once; C10-R23 is amended in place C10-R23, ARCH-R258
An App Attest attestation is accepted from the production and the development environment alike; on Android the attestation challenge is not checked, a key’s backing is the lower of the attestation’s and the key’s security levels, and verified boot is not checked C10-R23
The core’s list of official companion identities is empty until the companion’s first store release, and the core release that follows it fills the list; C10-R29 is amended in place C10-R29, releasing
A device key records the password verifier it was registered under: setting the password at the terminal revokes every device key at once, and every device-key request revokes any whose verifier is no longer the one kept, each journaled and alerted; a challenge is bound to the token that asked for it, the per-run token included C10-R24, C10-R27
workflow-pins and upstream-drift use the 24-hour window Q-R68 allows, read from one constant both scripts share: inside it a commit not taken is a warning naming it and the hour it becomes a refusal, and a commit dated later than the run counts as past it Q-R68
The pin fan-out arms its rolling pull request to merge itself at the commit it made; a fan-out a tag starts waits twenty minutes in the fan-out environment and goes on only where its tag is still the newest, and one started by hand with a tag does neither OPS-R48
Q-R83 is amended in place: workflows-approved approves the change a pull request makes, read from the forge each time over its whole tree as the objects and modes each changed path names, since an approved workflow may run any script in it; a push that leaves that change as it was keeps the approval, and one that changes it, a change of the base, or a head held less than a minute before the label voids it; Dependabot’s action bumps are not exempted Q-R83
Q-R83 is amended in place: a label event runs pin-only only for workflows-approved, every other label skipping the job in a group of its own run, and only a push cancels a pin-only run in progress Q-R83
The release workflow’s actions are pinned at the commit and version the other workflows in the core’s tree use, refused where they disagree, rather than from a table in a script This row

2026-10-13

Decision Where it lives
Restoring a configuration backup revokes every device key once it lands, journaled and alerted; each phone registers again with the password once C10-R32
A key minted with a device key’s signature records that device key as proved_by, shown in the keys listing; revoking the device key leaves those keys standing and its alert names them C10-R33, ARCH-R293
C10-R32, C10-R33 and ARCH-R293 ship in 0.18.0, joining its goals under goals-change review 0.18.0
The core checks an Android attestation chain against a snapshot of Google’s attestation status list embedded in each build and refreshed at release time, refusing a revoked or suspended certificate with a code of its own; nothing is fetched, and the version reports the snapshot’s date C10-R34, ARCH-R294, releasing
Face or fingerprint before a device key signs stays the companion’s duty; the core does not read it from an attestation N29-R3
C10-R34 and ARCH-R294 ship in 0.18.0, joining its goals under goals-change review 0.18.0
lemonfiber plugin record records every recording a manifest names, requests as well as runs: one fresh container of the pinned digest per service under its posture, its first run done through the adapter lemonfiber has for it, an operator’s probe asked with the key that first run left, every credential and the container’s identity scrubbed, and a service recorded whole or not at all; ARCH-R285 is amended in place ADR-0049, ARCH-R285, ARCH-R295, ARCH-R296, ARCH-R297
A first-party plugin’s upstream image pins move every week to the newest release of each pin’s major, one rolling pull request per image, recorded again by lemonfiber plugin record in the same change, through spec’s upstream-pins workflow; the media stack’s pins stops in the change that turns the callers on ADR-0049, REPO-R98
ARCH-R295 to ARCH-R297 ship in 0.18.0, joining its goals under goals-change review 0.18.0
The data root is the one lemonfiber config key DATA_ROOT, holding an absolute host path or nfs://<server>/<export>; the NAS overlay’s server and export are folded into it once; an NFS data root is reported as nas mode with the host-path checks said not run. ARCH-R240 is amended in place ARCH-R240, C5
The operator’s overlay is one fixed file, overlay.yml in lemonfiber’s configuration directory; LEMONFIBER_OVERLAY is retired and a path recorded under it is named by the doctor with where to move it. ARCH-R252 is amended in place ARCH-R252
Standing beside an existing setup records its alternative ports as a setting the generator writes the published ports from, journalled and reversible, and writes no overlay; this is part of the ARCH-R252 amendment ARCH-R252, A5
lemonfiber-decline and lemonfiber-request-gate publish an image from every commit on main as main-<commit> and move their plugin.toml pin to it as a first-party plugin does, while train tags publish and tell the stack as before; OPS-R90 is amended in place OPS-R90
A part the bundle pins installed is offered by the up of a form holding its profile, installed from the embedded tree through the plugin path, and nothing of the form is written or started without the yes bundle
A stack running a part as its own service is offered the move in place by lemonfiber update, rehearsed and undone as one unit; declined, the stack’s service keeps running from the pin’s copy and what its deleted in-process adapter filled is reported unfilled bundle
ARCH-R298 to ARCH-R301 ship in 0.18.0, joining its goals under goals-change review 0.18.0
Until the release that moves the media server in place, the source switch keeps its service, claims and recordings from the media stack as frozen at the switch, with its in-process adapter: its plugin’s copy holds what fills its capabilities in the adapter service, and the move needs the generated project the compose cutover brings bundle
Each conformance case declares the body it posts, asked, as a value of its operation’s request type the core declares beside the operation and publishes in conformance.json, with placeholders plugin record fills from a minted credential, upstream.json or an earlier case’s answer; the cases are asked after the first run, the contracts in speaks order and each contract’s cases in published order. ARCH-R199 and ARCH-R296 are amended in place ARCH-R199, ARCH-R296
A first-party plugin’s own image pin is recorded again when it moves, as an upstream’s is: manifest-pin takes every recording the manifest names again from the published image with lemonfiber plugin record at the lemonfiber targets.toml names, writes proofs.json from them in a job holding no secret, and the job holding the release App’s token commits them with the manifest on the rolling branch, refusing links and working the move out again itself; a commit on main that changes only plugin.toml, recordings/, proofs.json and targets.toml publishes no image. OPS-R90 and OPS-R91 are amended in place OPS-R90, OPS-R91

2026-10-19

Decision Where it lives
lemonfiber plugin record scrubs the recording machine’s own name, kernel version and operating system, as the engine API reports them, wherever they appear, as it scrubs a container’s identity; a number an answer gives about the machine, such as its count of processors, stays as answered. ARCH-R297 is amended in place ARCH-R297

2026-10-20

Decision Where it lives
A manifest is refused where a claim whose capability a service an adapter of the same plugin fronts provides binds a probe whose vocabulary entry says credential = "operator", naming the probe and the adapter. lemonfiber holds only the adapter’s key for such a service, and a check of it goes through the adapter’s conformance cases. ARCH-R295 is amended in place to point at it ARCH-R302, ARCH-R295
ARCH-R302 ships in 0.18.0 with ARCH-R295, the goal it closes 0.18.0
stream.guard’s apply a guard carries an envelope: the media server filler’s declaration verbatim, the one proxy address the guard trusts and where it asks the core about a live-stream pass; the guard it enforces reads all of it back. ARCH-R207 is amended in place: the declaration unaltered and nothing added to it, inside the envelope ADR-0050, ARCH-R303, ARCH-R207
media.serve’s declaration names refused paths, answered 403 without asking anything, and per byte path the query parameters that name another item, each refused or asked about too; the refusals at install stay. ARCH-R206 is amended in place ADR-0050, ARCH-R304, ARCH-R206
A request.gate route carries its upstream’s credential and the hashes of the tokens it admits, so applying the routes applies the tokens; the routes it admits answers each credential’s fingerprint, never its value; the two operations stay ADR-0050, ARCH-R305
The core asks network.egress-guard’s forwarded port every minute while the stack runs, at start and after a reconnect the doctor observes, and acts when the answer differs from the last; nothing is mounted into the guard and no endpoint is opened. ARCH-R251 is amended in place ADR-0050, ARCH-R251
A capability whose contract says its fillers listen on nothing may declare a probe that is a run of the filler image’s own program, ADR-0048’s bounded run judged on exit and standard output’s start; quality.sync is that capability, and Recyclarr, Unpackerr, Homepage and Caddy each provide a capability. ARCH-R283, ARCH-R109 and ARCH-R136 are amended in place to bind a run probe with run ADR-0050, ARCH-R309, ARCH-R109, ARCH-R136, ARCH-R283
archive.extract’s watch takes, per curator by the media type it files, its address, key and download folder, which is whom a finished extraction is handed back to; working answers what it is extracting now ADR-0050, ARCH-R306
invite.decline’s hold takes the media server key it switches an invitation off with, held answers that key’s fingerprint, and apply the invitations it may decline takes each token’s hash, the account and when it lapses; the decline address stays the filler’s own page ADR-0050, ARCH-R307
dashboard.show’s panel list is groups of entries (name, icon, link, description, an optional reachability check, an optional widget); widgets are ones the core names, page widgets (machine resources, a web search box) and per-entry widgets, none reading a credential ADR-0050, ARCH-R308
ARCH-R303 to ARCH-R309 ship in 0.18.0 with F14-R8, the goal they close 0.18.0
The forwarded port’s minute is run by two callers of one pass at one interval: the hosted watch loop for as long as the stack runs, and the dashboard’s gather while a surface is open; a stack with neither goes unchecked until something runs. The reconnect the doctor observes is every diagnosis whose VPN check ran against a running stack: the core then runs one pass, comparing the guard’s port with each torrent client’s peer port and setting it where they differ, journalled and said, and the doctor’s separate repair for it is removed. ARCH-R251 is amended in place ARCH-R251
A probe may constrain its answer by the status alone where the answer is a refusal or a redirect to HTTPS; proxy.front’s probe is answered 301 or 308 ADR-0051, capability-vocabulary
lemonfiber plugin record writes a throwaway self-signed certificate and its key where and as the core writes them for a filler of a capability the core hands a certificate (stream.guard); stream.guard’s probe is a refusal the guard makes without asking anything, answered 403. ARCH-R295 is amended in place ADR-0051, ARCH-R295
A request gate applied no routes answers 403 to every call but its health, rather than 503; request.gate’s probe is a call presenting no admitted token, refused 401 or 403. ARCH-R305 is amended in place ADR-0051, ARCH-R305
The core, asking over a capability’s contract, is the asker of quality.sync, archive.extract, stream.guard and invite.decline, and no [[ask]] row stands in for it. F14-R8 is amended in place ADR-0051, F14-R8
ADR-0050’s contracts carry the shapes today’s parts read: the guard’s envelope names where the guard reaches the media server and its filler’s adapter, as the topology fixes them; the declaration gives each byte path the shape an id has and the paths under it that name no item, a shape on each asked parameter, and the query parameters the upstream reads a token from; a request.gate route carries the path segment it answers under and the upstream’s supported majors; invite.decline is applied each invitation’s name and issue time and how the media server tells a claimed account; archive.extract’s watch takes every curator at once in place of those it watches. ARCH-R206, ARCH-R303, ARCH-R305, ARCH-R306 and ARCH-R307 are amended in place ADR-0050, ARCH-R206, ARCH-R303, ARCH-R305, ARCH-R306, ARCH-R307

2026-10-21

Decision Where it lives
Plain Compose operability is held by the project lemonfiber writes rather than by the media stack’s own Compose files: the project names itself and carries the operator’s non-secret values, and a .env beside it holds COMPOSE_PROFILES, the last start’s profiles and every installed plugin’s, and COMPOSE_FILE, the project then the operator’s overlay where one exists, so docker compose up in the project’s directory runs what lemonfiber last started with no binary running. Secrets stay in each service’s own file. F1-R1 is amended in place F1
The media stack’s compose.yml and compose/ go after the core runs its generated project: its CI moves the checks that start the stack and the door onto that project and deletes them, then the core moves its pin lemonfiber-media-stack
Every network of the project names a /29 of 10.80.0.0/16, chosen from its name, so the engine draws none from its own pools plugin-manifest (ARCH-R310)

2026-10-22

Decision Where it lives
Completed downloads go to /data/downloads/complete/<category>, one folder per category both download clients share, so one curator’s downloads land in one folder; each client keeps its own incomplete/, and archive.extract’s watch keeps one folder per curator ADR-0052, ARCH-R306
download.usenet and download.torrent each gain file_under, handing the client each category and its completed folder, and filed, reading that back; the seed asks every client this way, a bundled client through its in-process adapter and a plugin’s over its contract, keeps a folder the operator changed by hand in the client and reports it drifted; the extractor is handed the folder filed reads back. ARCH-R306 is amended in place ADR-0052, ARCH-R311, ARCH-R306
lemonfiber update stack --confirm performs a part’s move in place, rehearsed and shown first, journalled as one operation, undone as one unit and made through the plugin install path; until an update is confirmed, up and the doctor name the move as not yet taken. ARCH-R299 is amended in place ARCH-R299
The data root is an absolute host path, a path relative to the stack directory that stays inside it, or an NFS export, and ./data within the stack directory where it is unset. ARCH-R240 is amended in place ARCH-R240
After a move in place the first-party stand-in holds the reserved id, so a link by name reaches it; the service a link by name never joins is one filling the same capability under another id. ARCH-R242 is amended in place ARCH-R242, ARCH-R301
The core is also the asker of dashboard.show: over the contract where the filler speaks it, and where the filler is the assembled Homepage, through an in-process adapter that writes Homepage’s own files, as a request gate speaking no contract is told through the files it reads; each entry carries an icon, and the web search box takes its address from the core setting LEMONFIBER_DASHBOARD_SEARCH. ARCH-R308 is amended in place ADR-0052, ARCH-R308, settings
An assembled part’s [[secret]] whose of names another service is a crossing credential, read from its holder each time and never copied into the secret store; only a secret the operator enters is held there, as <service id>:<secret id>. ARCH-R275 is amended in place ADR-0052, ARCH-R275, ARCH-R277
At the cutover run, every value an assembled part or a published setting reads is filed once from the stack’s environment file into the stores and never read from it again; the file stays lemonfiber config’s, for lemonfiber’s own keys such as DATA_ROOT and PUID. ARCH-R249 is amended in place ARCH-R249
The core confirms a live-stream pass through its own pass service, the lemonfiber binary run as a service of the project (lemonfiber pass), on one network it shares with the guard alone and holding the pass key read-only, so the key never leaves lemonfiber’s own processes; apply a guard hands the guard the service’s address. ARCH-R291, ARCH-R303, ARCH-R241 and ARCH-R263 are amended in place ADR-0052, ARCH-R291, ARCH-R303, ARCH-R312, ARCH-R241, ARCH-R263
One machine runs one lemonfiber stack: a network’s subnet is derived from its name alone, so two projects on one machine collide, a deliberate limit. ARCH-R310 is amended in place ADR-0052, ARCH-R310, F1
ARCH-R311 and ARCH-R312 ship in 0.18.0, joining its goals under goals-change review 0.18.0

2026-10-23

Decision Where it lives
A [[wiring]] may name dashboard_description and dashboard_icon, the line and the icon beside its service’s dashboard entry; without them the entry shows the manifest’s description and <service id>.png. The bundle’s manifests name them, so the panel list the core applies to dashboard.show shows the stack’s parts as the page does today ARCH-R313
ARCH-R313 ships in 0.18.0, joining its goals under goals-change review 0.18.0
Every service with a port has a dashboard entry unless its [[wiring]] sets dashboard_hidden; the bundle hides the parts the page leaves out today ARCH-R313
The media server’s entry links through the stream guard where one runs, where the household reaches it, and the guard has no entry of its own ARCH-R313
The pass service ships in 0.18.0 with the network and the guard address that need it; issuing a pass for a live stream stays in 0.19.0 0.18.0
A pass is an HMAC-SHA256 under the core’s key over the live stream, the member and when it lapses, five minutes after issue, carried in lfpass; the guard asks GET /pass and 204 confirms ARCH-R314
The pass service runs the release’s own image, pinned by digest when the project is written; a build with no published image runs without it and says so ARCH-R314
The media server’s declaration carries the live-stream paths, where each places the live stream’s id; the core hands them to the pass service, which asks about the stream they place in a request’s path ARCH-R314
lemonfiber mints the credential of an upstream an adapter fronts: a [[secret]] with minted = true is made at install as 32 lowercase hex characters and held as the plugin’s; a part moved in place files the credential its service already answers to and mints nothing ARCH-R315
A minted secret reaches the upstream through its env or a starting file’s {{secret:<id>}}, with a pbkdf2 form for a service that keeps only a hash; such a file is 0600 and shown with the placeholder ARCH-R316
An adapter’s upstream names each upstream.json field and the secret it holds; lemonfiber hands a fronted upstream’s credential to nothing else of its own, and ARCH-R302 is amended in place to say so ARCH-R317, ARCH-R302
Replacing a minted secret writes it everywhere it was written and restarts what reads it ARCH-R318
ARCH-R315 to ARCH-R318 ship in 0.18.0 with the moves that need them 0.18.0
request.intake@1 gains establish, carrying the identity source, asked before anything else each time the request service’s identity is set up and as the first conformance case, so a fresh request service holds an owner before it is asked with its key; adding an operation is a minor change ARCH-R319
The client the Sonarr, Radarr, Lidarr and Prowlarr adapters share lives once, in adapter-kit-servarr, a Rust library each adapter takes by commit as it takes lemonfiber-adapter-kit; nothing is published to a registry REPO-R99, REPO-R100
request.intake@1’s conformance cases name their member by 32 lowercase hexadecimal digits, the shape a media server’s account id takes capability-contracts.md
ARCH-R319 ships in 0.18.0 with the request service’s move 0.18.0
A minted secret’s of names the plugin’s own service it is the credential of, as every secret the plugin holds names its owner; a placeholder and an upstream field name such a secret ARCH-R315, ARCH-R316, ARCH-R317
A replaced minted secret is written into a seeded starting file in place of the value on the line the kept template’s text around its placeholder finds, keeping everything the service wrote there; a file where that line is gone is refused by name and the old value stays in force. ARCH-R318 is amended in place ARCH-R318
A third-party plugin’s [[secret]] whose of names another service declares for, the capability one of its services provides that the credential is needed to fill, and at most one names each capability ARCH-R322
A grant is named in the install or update offer as <setting>@<plugin> and taken as an approval; every update asks for each grant again, and one left out stops crossing ARCH-R323
Revoking a grant rewrites the plugin’s entries and connections without the credential and restarts what read it, journalled as one operation undo reverses; the plugin stays installed and what the credential was for is reported unfilled ARCH-R324
The credentials read lists every grant with its plugin, capability and when it was approved; credentials-revoke is a web write over a grant, while revealing and rotating a credential stay at the terminal ARCH-R325, ARCH-R324
ARCH-R322 to ARCH-R325 ship in 0.18.0 with F14-R20 0.18.0
A curator tests a download client before it saves one and refuses one that does not answer, so lemonfiber plugin record answers the register case’s client itself, in-process, reachable from the upstream’s container alone; ARCH-R321 is amended in place ARCH-R321
A service’s starting files hold at most 16 placeholders in all, and a file holding one is written only where no link stands on the way to it beneath the stack directory; ARCH-R316 is amended in place ARCH-R316
Q-R83 is amended in place: every label event runs pin-only’s job and none skips it, in the pull request’s one group, since branch protection reads the check from the newest run of the workflow on the head and a skipped run hid the pass before it. The user chose this over leaving the trigger to workflows-approved alone or re-running the check from a second workflow Q-R83
library.curate@1’s kind-bound cases are published once per media type and asked of an adapter for the media types its upstream files; adding cases is a minor change ARCH-R320
register_download_client saves without reaching the client, and its case names the client in the bundled download filler’s native API, sabnzbd, so the cases after it find a held client ARCH-R321
ARCH-R320 and ARCH-R321 ship in 0.18.0 with the curators’ moves 0.18.0
A plugin’s pull request proves the adapter it built with the core’s recorder alone: lemonfiber plugin record --check --adapter <image> takes every recording again from an image the engine holds, writes nothing and fails naming each recording that differs or that no case answers, an identifier or moment the service makes afresh held to its shape alone; recordings are written only from pins; ARCH-R285 and ARCH-R296 are amended in place ARCH-R285, ARCH-R296
lemonfiber plugin record stands in for every party the cases point the upstream at, the identity source as jellyfin and the curator as the curator of series, each in its native API on a port of its own, so a later case reads the state install makes and no plugin seeds an upstream for its recordings ARCH-R326
The identity source’s stand-in signs in the owner whatever password a case minted, since each case mints its own, and lists a key under the name a request service asked to make it for, since a request service finds its key by that name; ARCH-R326 is amended in place ARCH-R326
ARCH-R326 ships in 0.18.0 with the adapters’ moves 0.18.0
An operation whose answer holds a credential the upstream issues names where as JSON pointers, published as x-lemonfiber-secret, and the recorder scrubs the value at each, so a token no later case reads is redacted; a contract is asked after those its placeholders read, and a case the first run already asked is recorded from the first run’s answer; it ships in 0.18.0 ARCH-R327
A part claimed first is claimed over owner.claim@1 and a curator’s indexer source is told it over indexer.pull@1, both facets the core asks of every provider and proved by conformance alone; an operation an upstream cannot perform answers unsupported; they ship in 0.18.0 with the moves ADR-0053
A case whose placeholder reads an answer that was unsupported, or a case of a contract the adapter does not speak, is recorded as answered unsupported and not asked live, so a media server that is not the household’s identity source answers its member-scoped cases unsupported ARCH-R334
lemonfiber plugin record --adapter <image> writes as well as checks, so a pull request that changes what its adapter answers records from the adapter it built; the pull request moving the adapter’s pin to the published image holds every recording to that pin before it ships; ARCH-R285 is amended in place ARCH-R285

2026-10-24

Decision Where it lives
The operator’s web console has a feature of its own, G11, shaped as G10 is for the household’s side; its first rows give the console a third-party plugin’s grants as the companion has them: a switch per grant on install and update, off until turned on and carrying the reading’s own line; the grants each plugin holds and every grant on the credentials panel; a revoke read before it runs; all of it shown to the operator alone G11-R1 to G11-R5
G11-R1 to G11-R5 ship in 0.18.0 with ARCH-R322 to ARCH-R325 0.18.0

Requirements

ID Requirement
GOV-R49 A decision the maintainer makes MUST be recorded in this specification on the day it is made: as a requirement or an ADR where it decides what is built, and as a dated row in this log, pointing at that requirement or ADR or standing as the record itself, where it decides how the work is done. A decision recorded only outside a repository MUST NOT be acted on as settled.