2.1.0
- 2.1.0
- Fieldset home
All notable changes to this project are documented here. The format follows Keep a Changelog and the project uses Semantic Versioning (0.x: the API and data model may still change). The Fieldset Spec has its own changelog in spec/form-definition/1.0/CHANGELOG.md.
The product is renamed from form-schema-app to Fieldset before its first public release. Apart from the fixes below, nothing changes in behaviour. Breaking for every pre-release user, with no compatibility path: old names are removed, not aliased (ADR 0092).
https://beyerenrico.github.io/fieldset/ with one copy of the guides per minor version (from 2.1), search, a version switcher, the Field App API contract, an interactive OpenAPI reference that sends no requests, the changelog, a home page and Fieldset Spec 1.0 with its meta-schema at the $id URL (ADR 0094). The Docs workflow publishes a release's copy after its Release run succeeded, through the append-only docs-site branch; CI builds the site on every run (RELEASING.md, "Docs site").$id (no semantic change).beyerenrico/fieldset, image ghcr.io/beyerenrico/fieldset, signer identity (https://github.com/beyerenrico/fieldset/.github/workflows/release.yml@refs/tags/v…), release notes URL.fieldset replaces fsa: the shim, the artisan namespace fieldset:*, the installer link and default directories (/opt/fieldset, ~/fieldset), environment variables FIELDSET_* (were FSA_*).fieldset-<slug>-<hex>, labels app.fieldset.*, marker file .fieldset-instance, database and role names fieldset*; backup, audit-export and security-export formats and the backup key derivation use the new name. A backup written by 2.0 pre-releases is rejected as an unknown format.$idhttps://beyerenrico.github.io/fieldset/spec/form-definition/1.0/meta-schema.json and the template registry urn:fieldset:bundled-templates; PHP package fieldset/fieldset-php (namespace Fieldset\), npm workspaces @fieldset/fieldset-js, @fieldset/fieldset-react, @fieldset/field-app, @fieldset/builder-v2._fieldset-verification.<domain> with fieldset-verification=<token>; domains verified with the old record must be verified again.release.json upgrades from 2.1.0-alpha.1; 2.0 pre-release instances are not upgraded. A 1.0 checkout is still adopted with fieldset adopt (its 1.0 project and database defaults are kept).<organization>, which GitHub showed as nothing, are now code spans in the guides, and a guide check rejects raw HTML tags outside code. The install guide says when Docker needs 4 GB of memory (large data).docker compose up --wait on an instance also passes with Compose before v5.small app over its memory limit (ADR 0083).v2.1.0-rc.1 stayed a draft.crypto/x509, high) — the app image builds Compose and age with Go 1.27.3, uses a FrankenPHP base built with Go 1.27.3 and the Docker CLI 29.9.2 (Go 1.26.10); cloudflared moves to 2026.10.2 (Go 1.27.3). The postgres (gosu), Gotenberg, Mailpit and Caddy images have no release with the fixed Go yet: dated scan exceptions until 2026-11-20 (ADR 0093).form-schema-app 2.0 is installed and operated without a clone of the repository: one command installs a signed release, and the fsa tool configures, backs up, restores, upgrades, rolls back, resets and removes the instance, with JSON output and documented exit codes for agents and scripts. This entry lists what changed since 2.0.0-beta.1, including the 2.0.0-rc.1 local pre-release (built 2026-10-20, not published); the pre-release entries below list the rest of 2.0 (signed images and the release pipeline, install.sh and fsa, local, domain and proxy modes, mail capture and SMTP, demo data, phone HTTPS, the resource budget).
Upgrading from 1.0: fsa adopt <checkout> (or install.sh --adopt <checkout>) moves a checkout install into a 2.0 instance with its data; see the install guide. The 1.0 guides stay in docs/guide/archive/.
/platform, operators only and read-only: problems first (a failed or stale backup, low disk space, an available update, backups held only on this host), each with its fsa command; then version and image digests, mode and URL, mail with the catcher link, the backup schedule and last result, and disk usage (ADR 0089).fsa config set update-check on|off (off by default). When on, the app reads the release manifest once a day, sends nothing else, and shows "update available" with the notes link (https only) and fsa upgrade on the instance page (ADR 0089).fsa command and a link to the instance page (checked hourly; R39).php artisan release:check-pg-dump and stops a release whose app image's pg_dump major is below the manifest's Postgres major on either platform, so backups cannot fail on a newer server (R39, ADR 0091).docs/guide/agent-guide.md, also a release asset: recipes for a local demo, sign-in links, SMTP, backup and restore, upgrade, moving servers, a VPS install, phone HTTPS, a domain change and removal, each with the exact commands, the JSON fields to read, the exit codes and the recovery fields of a failed command. Its runnable blocks are executed against a locally built release by the install journey J11.install.sh and fsa; the backup guide explains chain epochs and the ledger, the admin guide the mail capture banner. The 1.0 checkout guides moved to docs/guide/archive/, marked as not for fsa instances. The README quick start is the one-liner. A test checks that every fsa command, flag, environment variable and relative link the guides name exists (ADR 0042).backup service (the app image, container root with four capabilities, no Docker socket, the storage volume read-only) that backs up daily at 03:30 in the instance's time zone and keeps the newest 7 scheduled backups in backups/. A backup is the database dump, the storage volume, the portable settings, the audit chain heads and a manifest, all read from one database snapshot, with SHA256SUMS and an HMAC keyed from APP_KEY; it counts only once the dump reads back. Retention prunes only scheduled backups: manual, kept and safety backups stay (an upgrade's safety backup is replaced by the next committed upgrade's; restore, reset and pre-rollback safety backups stay until you delete them), and a run that would fill the disk is skipped (ADR 0084).fsa backup — a manual backup now, while the instance keeps running; --include-secrets adds APP_KEY and the SMTP account (encrypted backups only), --encrypt turns encryption on first (ADR 0084).fsa config set backup-encryption on --identity-out <path> --passphrase-file <file> (or --passphrase-stdin) creates an age key pair, writes the identity encrypted with your passphrase outside the instance directory and checks that it decrypts; only the public key stays in the instance, and every later backup is one age file (a safety backup is sealed once its operation completed). off switches back (ADR 0084).fsa config set backup-schedule <HH:MM|off> and backup-retention <n> (ADR 0084).fsa status shows the last backup, the schedule and its next run, the backups on disk and the safety backups with their age, and warns when the last backup failed, when a scheduled run was skipped for lack of disk space, when scheduled runs were skipped for more than a day because an operation held the lock, when the backup service cannot start, when no backup succeeded within two hours of a scheduled slot, or when an interrupted backup left a .partial directory; the /platform health page has a backup check with the same alerts, so it also notices a backup service that is not running (ADR 0084).audit:verify --heads reports the chain epoch with the heads (ADR 0084).fsa restore <backup> — restores a backup into the instance after confirmation: the backup is decrypted and checked in a container without network and Docker socket (checksums, an HMAC verified only with APP keys from outside the archive, every archive member and every object of the dump judged before anything is written), the instance is quiesced behind its gate, the final audit heads go to backups/ledger.log, a safety backup is taken, and the backup is restored into a scratch database and a fresh storage volume. Security changes made since the backup (revoked devices, deactivated or erased users, changed passwords, e-mail addresses and MFA, deleted API tokens, SSO identities and providers, revoked operators, roles, group memberships and form access, deleted organizations and webhook endpoints) are re-applied, sessions, reset tokens and sign-in links are cleared, numbering never goes backwards, the audit log starts a new epoch with an instance.restored entry, and only a restored instance that passes its checks behind the gate replaces the old database and volume; otherwise the old instance runs again (exit 5). Only backups of the same instance are restored in place (another instance's go to a new directory with --from-backup); webhooks stay paused afterwards until you run fsa config set webhooks on. When the instance was itself restored after the backup was made (undoing a restore with its safety backup), only changes the audit log records are re-applied and the rest is reported; restoring a deleted form or group brings back its access grants and members. A backup from a newer version is refused with exit 7 and changes nothing (ADR 0085).fsa restore --drill <backup> — restores into a scratch database and reports counts, audit chain heads, missing files, a test render and a decrypt sample without touching the instance (ADR 0085).fsa restore --abandon — cleans up a restore that stopped half-way, also after exit 8, and brings the instance back; when the previous database or storage volume is gone it continues with the restored data instead (ADR 0085).fsa install --from-backup <backup> — moves an instance to another host: settings that belong to the instance come from the backup, host-bound ones from the flags; a domain that does not point here yet is "certificate pending". The moved instance starts with captured mail and paused webhooks until you confirm with fsa config set webhooks on and fsa config set mail smtp …. 1.0 backups from scripts/backup.sh are accepted with --accept-unauthenticated; their unsafe settings (debug mode, insecure SSO, trusted proxies) are never applied; backup recipients are kept only from an authenticated backup (ADR 0085).--app-key-file, --identity-file, --accept-unauthenticated for restore and --from-backup: APP keys of an older or another instance (they become previous keys, so old data still decrypts), the backup identity with --passphrase-file/--passphrase-stdin, and archives without a valid HMAC (ADR 0085).fsa config set webhooks on|paused; a webhook step also waits when the instance's runtime file cannot be read (ADR 0085).fsa upgrade — moves the instance to the release marked latest (the most recently published stable release; --to <version> picks one, pre-releases included, --major crosses a major version), health-gated: the target's signed manifest is downloaded and verified, every image pulled, the app answers 503 behind its gate while the queue drains (--drop-jobs saves and drops what a bounded drain leaves) and a verified safety backup is taken, then the target's own fsa renders its compose and env files and runs its migrations in a container without the Docker socket, its app must pass the smoke test behind the gate, and only then is the upgrade committed and opened. Requests that were already running when the gate rose finish before the safety backup (a stuck one stops the upgrade with exit 9), and the free-space check counts the live database and files. Only the compose, env and service files are taken from the target, and nothing in them may reach past the containers (host paths, capabilities, devices, host networking, images the release does not pin). Any failure from the safety backup until the commit rolls back automatically: the files the target replaced are put back, the safety backup is restored while the previous app answers 503, the cache flushed and the previous release started again (exit 5, with the logs); a rollback that fails stops the instance and names the backup and both image sets (exit 8). The upgrade runs in a background container of the installed release, so a closed SSH session never stops it half-way; fsa status shows its phase and fsa upgrade --resume continues an interrupted one. After a commit the previous pre-upgrade safety backups are removed (never one marked kept); restore, reset and pre-rollback safety backups stay until you delete them; safety backups an upgrade leaves behind on its way back are encrypted when encryption is on (ADR 0086).fsa rollback — returns to the release before the last upgrade from that upgrade's safety backup, after telling you what was written since and will be lost; security changes made since the upgrade (revocations, erasures, changed passwords, deleted tokens) are carried over, numbering never goes backwards, and the audit log starts a new epoch with an instance.rolled_back entry. A rollback that cannot finish goes back to the upgraded release exactly as it was (exit 5; exit 9 when nothing was changed, such as a queue that does not drain); an interrupted rollback continues with fsa rollback, without the question (ADR 0086).fsa restore of an older backup — a backup made by an older release is now restored in place and migrated to the installed release, instead of being refused, the backup fsa adopt made included (with --accept-unauthenticated) (ADR 0085 and 0087 amendments, ADR 0086).fsa adopt <checkout> (install.sh --adopt <checkout>) — turns a clone-based 1.0 install into an instance by copying its data: it stops 1.0's app, scheduler and any other service it added, lets its queue drain, takes a backup in the 1.0 format (without the APP keys: its manifest names them by fingerprint), stops 1.0's Postgres cleanly, copies both volumes into the instance's own and checks the copy against the backup (row counts, audit chain heads, migrations, amcheck on the unique text indexes) before 2.0 starts on postgres:16-alpine; a 1.0 started again in between stops it. It keeps APP_KEY, the database names and the sender, starts with captured mail and names 1.0's SMTP server, infers local or proxy mode from APP_URL and APP_PORT, reads only an allow-list of the 1.0 .env (which must match what 1.0 runs with) and never applies its debug, SSO or trusted-proxy values. A Compose override, an external database or a Postgres other than 16 on Alpine is refused (exit 7). Any failure before 2.0 starts brings 1.0 back unchanged and leaves nothing of 2.0 behind (exit 5); --abandon undoes an adopt that stopped half-way (ADR 0087).fsa reset — returns the instance to a fresh state after confirmation: the organization and owner it was installed with (the owner gets a new one-time sign-in link), and with --demo the demo data (random passwords whenever the instance is reachable beyond loopback). The instance is quiesced behind its gate, the final audit chain heads go to backups/ledger.log, a safety backup is taken (never pruned), then the database is recreated, the audit log starts a new epoch with an instance.reset entry naming the old heads, files go to a fresh storage volume, and the cache, queues and captured mail are emptied. Settings and secrets stay. --no-backup skips the safety backup but still writes the whole audit log to backups/audit-export-reset-<time>.jsonl while a real organization exists. A failure before the database is recreated leaves the instance as it was (exit 5); a reset stopped after that is finished by running fsa reset again, without a question. Row IDs of the old data are never handed out again, so signed links sent before the reset (invitations, data exports) find nothing; leftover restore databases and storage volumes go too (ADR 0088).fsa reset --abandon — ends a reset that stopped before it recreated the database and starts the instance again as it was (ADR 0088).fsa uninstall --keep-backups writes its final backup in the 2.0 backup format (backups/final-<time>, encrypted when backup encryption is on), so fsa install --from-backup and fsa restore read it; the instance is quiesced first, so no write is lost after the snapshot, and a failed backup starts it again. The APP keys go inside an encrypted backup, or beside a plain one (final-<time>.app-key.env, named in a warning). The proxy-mode hint now says how to carry the data over (ADR 0073 amendment).backups/ (a sync tool, say): a backup being written stays root's until it is complete, SHA256SUMS lists what the backup service produced, and a file added or changed meanwhile fails the run instead of being authenticated. The service's settings and every plaintext working copy (the plain set of an encrypted backup, a restore's decrypted set, the rollback's security export, reset's audit export) live in the new private staging/ directory of the instance, never in backups/ (ADR 0084, 0085 amendments).fsa restore, its drill, fsa rollback, an upgrade's way back and fsa install --from-backup copy a plain backup into the instance's private staging/ and check and restore only that copy, so a change in backups/ after the check (a sync tool, say) never reaches the restore; such a restore needs free space for one more copy of the dump and the storage archive (ADR 0085 amendment).fsa uninstall has the backup service remove what a backup run that was cut off left behind (root's partial sets, possibly a plaintext dump), and names any file it still cannot remove instead of reporting data and backups deleted; a failure while it quiesces for the final backup starts the instance again and lowers the gate (ADR 0073 amendment).fsa status and /platform warn when the newest backup misses files the database references, and retention keeps the older backup that missed the fewest files for as long as new ones miss them (ADR 0084 amendments).fsa restore refuses a backup without files unless --without-files (the replaced storage volume is then kept), and refuses a backup renamed to another backup's name; fsa rollback refuses a safety backup that is no longer the set the upgrade verified (ADR 0085, 0086 amendments).fsa install --from-backup lists the backup's encryption recipients and keeps them only with --keep-recipients (ADR 0085 amendment).fsa reset --no-backup encrypts its audit export to the backup recipients when encryption is on (ADR 0088 amendment).no-new-privileges; those that read a backup or a 1.0 install drop all or nearly all capabilities and run on a read-only root file system (ADR 0085, 0087 amendments).fsa uninstall of an adopted instance (also one whose adopt stopped after 2.0 started) takes a final backup by default and keeps backups/; it reports the writes made since the adopt, whether 1.0 ran since then, and how to start 1.0 again (docker compose up -d in its checkout) with the jobs 1.0 will then run; --no-backup skips the final backup (ADR 0087).fsa status and fsa logs list the backup service; the stack file for Portainer and Coolify still has no backup service (ADR 0084).fsa status shows the last restore, the storage volume in use, paused webhooks and an interrupted restore with the commands that resume or abandon it; fsa config show shows the storage volume, the webhook state and the last restore (ADR 0085).fsa status shows the last reset and an interrupted reset with the command that finishes it (ADR 0088).fsa backup and fsa login-link refuse while another operation is unfinished (exit 9), and a restore of a safety backup taken before a reset carries no security state from the reset instance (ADR 0084, 0085, 0088)..DS_Store beside it, and file names in SHA256SUMS must be plain names (ADR 0085).fsa restore refuses a backup older than the installed release's oldest upgrade source (exit 7), as documented; the check never applied before (ADR 0085 amendment).fsa restore --accept-unauthenticated also refuses an archive that lacks objects this release's migrations create at the archive's own migration level, such as the audit log's append-only triggers (ADR 0085 amendment).fsa status shows rollbacks as the last restore and no longer warns about a restore's scratch directory while an upgrade, rollback or reset runs (ADR 0085 amendment).exit_code (also those of install.sh, the shim and the image's fsa), and exit 6 confirmation_required carries the facts its question names under data (a rollback's versions, safety backup and loss counts; restore, reset, adopt, uninstall).release:check-pg-dump) now runs the app image on every platform: it removes the image it pulled after each platform, since Docker's classic image store cannot pull the second platform of the same digest over the first ("cannot overwrite digest").fsa restore <path> with a path outside the instance directory answers exit 2 backup_not_visible (copy the backup into backups/), as documented, instead of backup_not_found: from the ops container such a path never exists.install.sh --from-backup, the fsa config set mail smtp … command in data.confirm_commands carries the old server's encryption and sender address too (host and port already), so a relay set up with --smtp-encryption none no longer fails with exit 5 test_mail_failed; only <account> and <file> (the password file) are left to fill in. data.pending_mail adds encryption and from_address.fsa config set of a key that changes the URL (exit 6 without --yes) carries data.question, like restore, rollback, uninstall and the other confirmations.fsa config set mail smtp (exit 5 data.command) names --password-file <file> when the password came from a file, instead of always --password-stdin.manifest_signature_invalid (install.sh and fsa) no longer says "retry later": stop, never use another install.sh or release URL, never bypass the check; the same command again only when the message shows that Sigstore could not be reached (as the agent guide says).--identity-file of fsa restore or install.sh --from-backup was turned into a root-owned directory at that path by Docker; it is now refused before anything is mounted (exit 2 identity_unreadable, nothing changed).fsa install --from-backup … --dir <new directory> (which the instance's fsa refuses for another directory) now name install.sh --dir <new directory> --from-backup <backup>; fsa restore --abandon hints carry --yes (ADR 0085 amendment).fsa install --from-backup lists <dir>/fsa config set webhooks on among its next commands (it printed fsa twice), and fsa reset names the instance's own <dir>/fsa in its next commands.fsa rollback names fsa rollback to run again, not fsa upgrade; a missing backup service and a backup service that cannot start name fsa config set (which renders the files again) instead of fsa install, which changes nothing on an installed instance.fsa install no longer takes over an unfinished fsa install --from-backup (it skipped the restore steps and marked an empty instance installed): it stops with exit 9 and names the --from-backup command to run.fsa status, exit 9 operation_unfinished) names all the flags the resumed run needs: the backup, --identity-file, --passphrase-file <path> or --passphrase-stdin, --app-key-file, --accept-unauthenticated, --without-files, --keep-recipients, and --yes where the command asks first. A resumed restore, rollback or install from a backup without one of them stops with exit 2 before it changes anything, instead of going back.fsa upgrade --resume with nothing to resume stops with exit 2 nothing_to_resume instead of starting a new upgrade, and a resumed upgrade keeps the --to it began with. The upgrade's result survives a closed terminal: fsa upgrade --follow follows a running upgrade or shows the last result, and fsa status shows it as last_upgrade.fsa status tells a running operation from an interrupted one (operation.state), shows the upgrade supervisor (supervisor) and whether the lock's holder is gone (lock.stale).fsa restore, fsa rollback or fsa adopt stops at the next step boundary and goes back (after adopt started 2.0: stops with the journal open), instead of killing the command half-way.fsa rollback leaves webhooks paused like a restore (runs restored from the safety backup could repeat deliveries); confirm with fsa config set webhooks on.fsa restore --drill refuses while an operation is unfinished; fsa restore, fsa reset and fsa rollback report an unfinished operation before they ask or count anything; fsa login-link, fsa upgrade and the drill report an unfinished install or adopt instead of "not installed"; fsa login-link works while a backup runs.--passphrase-stdin like --password-stdin (its stdin is the script); an empty passphrase is passphrase_missing, not weak_passphrase.Mail, demo data and phones (M11): capture mode is visible and can be switched to a real SMTP server, demo data comes with random passwords, phones get HTTPS through a local CA or a quick tunnel, and the E16 floor is measured.
fsa config set mail smtp command, everyone else "ask your instance owner" (ADR 0080).fsa config set mail smtp|capture — switches to an SMTP server after a test mail and reverts when the server refuses it; the password comes from --password-stdin or --password-file, never from a flag. The mail catcher is kept, receiving nothing, until --remove-catcher; capture switches back. The result counts the invitations and password resets that capture kept from people (ADR 0080).fsa config set operator-email <email> [--revoke-previous] — makes an address the instance operator, creating the account and a sign-in link when needed (ADR 0080).fsa config show --catcher-login — the mail catcher UI's password in local and proxy mode, as a one-time secret (ADR 0080).fsa install --smtp-* — --smtp-host, --smtp-port, --smtp-encryption starttls|tls|none, --smtp-username with --password-file or --password-stdin, --from-address and --from-name install straight into SMTP mode, without a mail catcher. starttls (the default) refuses a server that offers no STARTTLS before any credentials are sent; none is for a relay without TLS (ADR 0080).fsa install --demo adds the demo organization, forms, workflow and users; fsa demo adds them to an installed instance and hands out the demo owner's sign-in link; fsa demo --rotate-passwords gives every demo user a new random password and revokes their sessions and API tokens. Demo passwords are random (listed once) whenever the instance is reachable beyond loopback (ADR 0081).fsa install --phone-https local-ca|tunnel with --lan-address. The local CA serves the instance over HTTPS on the LAN address, with a trust page (/phone-trust: root certificate, SHA-256 fingerprint, iOS and Android steps, QR code, trust check). fsa tunnel start|stop|renew runs a Cloudflare quick tunnel for 4 hours. fsa config set phone-https local-ca|off and fsa config set lan-address <ip> change it later (ADR 0082).fsa install also renders a PDF and sends a test mail (into the catcher, or through the SMTP server) before it reports success (ADR 0072).small memory budget is retuned from a measurement at the 1.0 test volume inside 2 GB and two CPUs, with one default Horizon worker on small. Install, PDF and API targets are met; the admin-page target (p95 < 500 ms) is missed at 100k submissions on a 2 GB host: saved reports, explorer text searches and the reviewer inbox take up to 1.9 s (ADR 0083)./platform instance page and the update check (M13).docs/guide/ still describe the 1.0 checkout install until M13 rewrites them; fsa <command> --help lists each command's flags.Modes (M10): domain mode with automatic HTTPS, proxy mode behind an existing reverse proxy, a stack file for plain Docker Compose, and fsa config to show and change settings.
fsa install --domain <host> runs a Caddy edge that gets its certificate over ACME (Let's Encrypt by default; --acme-staging, --acme-ca and --acme-ca-root-file for another directory), redirects HTTP to HTTPS and is the only service that publishes ports (80, 443, 443/udp). The mail catcher is at /_mail/ for operators signed in to /platform with MFA (ADR 0075).fsa install --behind-proxy traefik|caddy|port --url <origin> puts the app behind an existing Traefik or caddy-docker-proxy (--proxy-network, --trusted-proxy), or on a loopback port for any other proxy. The app trusts exactly one proxy address (ADR 0076).docker compose (R18, ADR 0076; import into Portainer and Coolify is not yet verified).fsa config show — the effective settings with secrets redacted, and what each opt-in feature contacts (ADR 0077).fsa config set <key> <value> — domain, url, port, trusted-proxy, mode local, acme-email and acme-ca, under the instance lock: preflight, apply, verify, and a revert on failure (exit 5 rolled_back; exit 8 when the revert fails too). A URL change asks for confirmation (--yes) and lists the organizations with SSO that it affects (ADRs 0077, 0078).runtime.json, so a URL change reaches requests, queued mails and jobs without a worker restart (ADR 0074).migrate --isolated); invalid indexes left by an interrupted concurrent index build are rebuilt.The first 2.0 pre-release: the app is distributed as signed images with the self-managing fsa command, for a local install with one command. It was built and tested locally against a local registry and release server (E18); publishing it is a human step described in RELEASING.md.
fsa ops roles; every base image pinned by digest (ADR 0065).v* tag builds the image with SBOM and provenance, signs the image, the release manifest and install.sh keylessly with cosign, scans every image with Trivy, and publishes a signed release manifest that pins every service image by digest per platform (release:manifest, release:version; ADR 0067).install.sh and the fsa shim — the installer verifies the signed manifest before it pulls anything, pulls the app image by digest and hands over to fsa install; <dir>/fsa runs every command in the instance's ops container (ADR 0070).fsa commands in local mode — install, doctor, status, logs, version, uninstall and login-link, each with a JSON envelope (--json), NDJSON progress and fixed exit codes. fsa install runs journaled phases, resumes after a failure and changes nothing on an identical re-run (ADR 0072).fsa login-link issues a 15-minute single-use link for an existing admin, stored hashed and rate-limited. A link goes to --secrets-file, to the browser (--open, or a person at a desktop terminal) or into the envelope's secrets (ADR 0056, ADR 0068).app:install — --owner-link, --password-file and --json create the first organization and owner without prompts./horizon opens for platform operators signed in at /platform (ADR 0068).DOCKER_DEFAULT_PLATFORM support — install.sh, the shim and fsa install and run a release of the other architecture under emulation; fsa doctor reports it and memory limits are doubled./platform session: PLATFORM_ADMIN_EMAILS no longer opens Horizon (it still grants /docs/api).fsa instances (ADR 0069).fsa install --domain runs the DNS preflight and stops; added in 2.0.0-alpha.2), and the capture-mode banner, demo data and phone HTTPS (added since, see above). Backups, restore, upgrade and adopt arrive in later milestones.The first stable release: single sign-on and the canvas builder (M7), and hardening (M8) with the audit log, GDPR tools, security headers, integration tokens and the operator panel. Release notes: docs/releases/1.0.0.md. Upgrade: docs/guide/upgrade.md.
audit:seal, audit:verify and audit:prune with 365 days retention by default (AUDIT_RETENTION_DAYS). audit:verify --heads prints the chain heads; shipped regularly to storage outside the database, they provide tamper evidence even against a database owner, who could otherwise recompute the chain./api/v1 route declares its abilities and refuses tokens otherwise; a rate limit per token; creation, revocation and first use are audited (ADR 0043)./platform — its own sign-in with required two-factor authentication; organizations and members; health of queues, Gotenberg, mail, scheduler and disk; impersonation of admin members with a reason, a banner, a time limit and audit entries in the organization's log; every entry written while impersonating records the operator (impersonator) (ADR 0041). Operators are granted with php artisan app:platform-admin.UPLOADS_CLAMAV_*).sentry log channel without an SDK (SENTRY_DSN)..env.production.example, kept production-safe by a test.docs/guide/: install, upgrade, backup and restore (with scripts/backup.sh and scripts/restore-drill.sh), configuration, troubleshooting, admin, Field App and API (ADR 0042)./platform, plus keyboard walkthroughs, in the E2E suite.document_anonymized in the API and on the signed download, and a 410 page in the dashboard; the dashboard and the Field App show a notice and stop polling.API_RATE_LIMIT, 240 per minute) and counted per integration token, otherwise per user.App\Support\Logging\JsonLogFormatter instead of Monolog's JsonFormatter.compose.yaml publishes the app port on 127.0.0.1 only; APP_PORT is a port number, and the reverse proxy runs on the host. .env.production.example trusts the Docker bridge range (TRUSTED_PROXIES=172.16.0.0/12).docs/agent/milestones/evidence/M8/performance.md): submissions (organization_id, updated_at) index serves its order: the reviewer's list 268 → 125 ms.AWS_* lines in .env.example (no S3 adapter is installed; they never had an effect).compose.yaml; only compose.dev.yaml mounts docker/postgres/init.activitylog:clean can no longer delete audit entries (the database refuses it); use audit:prune.Referrer-Policy, Permissions-Policy, framing rules and X-Content-Type-Options on every response; HSTS and Secure cookies behind HTTPS (ADR 0040).config/security.php); the Field App session is unchanged.TRUSTED_PROXIES; empty trusts no proxy) and a host allow-list (TRUSTED_HOSTS): forged Host or X-Forwarded-Host headers get 400, so password-reset and invitation links cannot point to another domain.docs/releases/1.0.0.md and docs/agent/QUESTIONS.md.Reports and dashboards (M5) and offline-first filling (M6). Release notes: docs/releases/0.2.0.md.
client_submitted_at on submit, retired_version_ids in the manifest, idempotent customer send by key (durable beyond the 24 h replay window).The MVP (milestones M0–M4, BUILD_PROMPT §4.1). Release notes: docs/releases/0.1.0.md.
packages/form-schema-php, packages/form-schema-js, MIT)./app) — React PWA: login with device registration and remote wipe, catalog, fill with server autosave and conflict handling, signatures, photos, review and idempotent submit with gap-free numbering, submissions with PDF access and "send to customer", open review tasks with approve/reject, changes-requested resubmit, notification centre and preferences (DE/EN).input_hash and file_sha256./api/v1) — documented in docs/api/field-app-v1.md and as OpenAPI 3.1 at /docs/api.app:install, demo seed, CI with static analysis, coverage gates, conformance, license checks and Playwright journeys.docs/agent/QUESTIONS.md for decisions awaiting human review.