Registration creates a new account plus its first user; login
authenticates an existing one; both set a deklarix_session cookie
(HttpOnly, SameSite=Strict, Secure only when the request itself came
over TLS — hardcoding Secure=true would break local http://localhost
development, since browsers won't store a Secure cookie over plaintext).
requirePage protects full-page GETs (redirects to /login); requireAPI
protects the htmx/download endpoints (401, since those are only ever
called from an already-authenticated page — an unauthenticated hit
there is the exception, e.g. a session expiring mid-use).
handleCheck now creates submissions under the current account.
handleArchive and handleDossierDownload compare the submission's
account against the caller's and return 404 on mismatch — not 403,
which would confirm the ID exists to a different tenant. Login failure
uses the same message for "no such email" and "wrong password" to avoid
account enumeration.
Restructured templates along the way: layout.html now only holds
reusable fragments ("head", "nav"); each full page (index/login/register)
is its own top-level named template. The previous layout+content nesting
would have broken the moment a second page defined "content" — Go's
html/template keys blocks by name across the whole parsed set, not per
file, so two pages both defining "content" would silently overwrite each
other.
Verified against a real running instance (not just Go's test recorder):
started the compiled binary against a fresh Postgres and drove the whole
flow with curl — anonymous redirect, registration setting a real cookie,
authenticated page load, logout clearing both the cookie and the
server-side session row, and being locked out again afterward.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This is the piece that turns a Pre-Publish-Prüfung into an actual
archived, provable record instead of a one-off form response.
extract.Client.Extract now returns Result{Facts, RawJSON} instead of
just Facts — RawJSON is the model's exact, unmodified JSON, which is
what belongs in extraction.payload (the audit trail), not a re-encoded
view through our own Facts struct. extract.ParsePayload reconstructs
Facts from a stored payload later, reusing the same parsing/validation
path Extract uses (including the enum guard), so a previously-saved
extraction can be read back exactly as it would have been the first
time.
internal/dossier.BuildContent no longer requires AssetHash: most checks
right now are caption-only (no image/video upload wired yet), and
inventing a placeholder hash for a nonexistent asset would itself be an
integrity problem in an evidence tool. Content shows "kein Asset
hinterlegt" instead.
internal/web gains a narrow Store interface (mirroring the Extractor
pattern — only the methods these handlers use, not the full
*store.Store) so its test suite stays network/DB-free via an in-memory
fake:
- POST /pruefen persists submission + extraction + findings and marks
the submission "checked". A needsClarification result persists the
extraction (there's something worth keeping) but no findings and no
status change, and the template omits the archive option entirely.
- POST /veroeffentlichen re-derives Facts from the stored payload, hashes
the canonical submission+facts+findings metadata, gets an RFC-3161
timestamp, generates the PDF dossier to disk, and persists the
evidence_package row before marking the submission "published".
- GET /dossier/{id} serves the generated PDF.
Tested end-to-end offline: a fake Timestamper builds a real, structurally
valid self-signed RFC-3161 response so the full check→archive→download
flow runs against an in-memory store, verifying the downloaded bytes are
an actual PDF and the dossier file lands on disk — without hitting a
real database, TSA, or the Claude API.
cmd/deklarix/main.go now wires store.Store, evidence.NewHTTPTimestamper
(TSA_URL, default FreeTSA), and DOSSIER_DIR (default "dossiers") into
web.NewServer alongside the extractor and rule set.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Routing, html/template layout/content pattern, and the Pre-Publish
check flow: POST /pruefen runs extraction (Stufe 1) then rules.Evaluate
(Stufe 2) and renders the result as an htmx fragment. Nothing is
persisted yet — that's the next step (wiring internal/store in).
The needsClarification case is rendered explicitly as a request for
more information rather than "no findings", matching the core
principle. Every result carries the legal-advice disclaimer required by
CLAUDE.md's guardrails.
Server depends on a narrow Extractor interface rather than *extract.
Client directly, so tests inject a fake instead of calling the real API
— internal/web's test suite never touches the network. htmx is vendored
locally (internal/web/static/htmx.min.js) instead of loaded from a CDN,
keeping the UI usable without runtime internet access.
cmd/deklarix/main.go now wires all of this together: reads
ANTHROPIC_API_KEY (required) and RULES_DIR (default "rules"), builds
the extract client and loads the rule set, and serves web.Server instead
of the old inline health-only mux.
This exposed the same crash-loop risk fixed earlier for DATABASE_URL:
postinst's start guard only checked DATABASE_URL, so a fresh install
would now crash-loop on a missing ANTHROPIC_API_KEY instead. The guard
checks both. scripts/build.sh also now ships rules/*.yaml into the .deb
under /usr/share/deklarix/rules (not a conffile — rules are updated via
the release pipeline, never hand-edited on a server), and
deklarix.env.example points RULES_DIR there by default.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>