fd4fd60c99
93 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| fd4fd60c99 |
#223: dedup window for the read trail
One event per (session, page, channel) within an aligned window of readTrail.dedupWindowMinutes (default 5): buckets are floor(epoch / windowSeconds), and a unique (dedup_key, window_bucket) pair collapses concurrent duplicates race-free at insert time — the first access in a window is always recorded, a later duplicate lands on the unique violation and is skipped quietly (a skipped duplicate is not a gap; only real write failures still abort the read). Each row carries windowSeconds, so the evidence states it represents a window, never a request count. Reconnects within a window stay one event; a new session records again even for the same user; channels never collapse into each other; the page-less attachment key uses the documented `-` placeholder. Load evidence: 30 collab-token renewals inside one window produce exactly one event (test), bounding a live editing session at ~12 events/hour/page. Window semantics documented in ADR 0023, the VS-NfD security documentation (#228) and as a hardening-guide line for the new setting (care rule: same PR). Refs #223. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUtYMxwTCMHG9mVHnwbFg8 |
|||
| 05a979bac3 |
#222: read-access trail for classified pages
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m25s
CI / Build container images (pull_request) Successful in 2m58s
CI / Auth e2e pack (pull_request) Successful in 8m35s
CI / Import/export fidelity gate (pull_request) Successful in 1m7s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CI / Lint, typecheck, test (push) Has been cancelled
CD / Build and push images (push) Has been cancelled
Instrument every full-content read channel for pages with classification = vs_nfd (ADR 0023, variant A): SPA state fetch and read rendering, public JSON content, no-JS shell, expanded embeds, public API GET (incl. the MCP read_page path and write echoes), attachment download under the #212 effective classification, all export shapes (markdown, pond ZIP, account data export, queued docx/odt/pdf at enqueue), and collab-token issuance as the api-side proxy for the WS join. Events land in the new read_events table (no FKs — evidence survives page purges and hard user deletions) with actor, session key (session:/token:/job:/anon), page, pond, channel and the classification at read time. Recording failures are NOT swallowed: a failed write aborts the read (hard failure, the deliberate contrast to AuditService — decision recorded in ADR 0023 and security.md §Logging, together with the recorded residuals: content fragments and feeds). One e2e test per channel proves both the event and its absence for unclassified pages, plus the hard-failure semantics. Refs #222. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUtYMxwTCMHG9mVHnwbFg8 |
|||
| 919201d9b0 |
#230: IT-Grundschutz mapping for APP.3.1 and CON.11.1
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 6m56s
CI / Build container images (pull_request) Successful in 1m23s
CI / Auth e2e pack (pull_request) Successful in 8m57s
CI / Import/export fidelity gate (pull_request) Successful in 1m1s
CD / Build and push images (push) Successful in 22s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 1m17s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 5m48s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 8m13s
CI / Import/export fidelity gate (push) Successful in 58s
docs/vs-nfd/80-grundschutz-mapping.md against the Edition 2023 texts of both building blocks (fetched from the BSI single PDFs, edition and retrieval date stated; the dropped requirements of APP.3.1 are listed as such, CON.11.1's 18 requirements are all Basis). Every requirement classified as product / operator / n.a.: product rows point at code, configuration and tests (auth+rate limits, upload controls, security headers with the honest HSTS-at-the-proxy split, marking = the whole of M26 under CON.11.1.A7 incl. the answered does-the-marking-carry-a- security-function question); operator rows say what we hand over (copy list, procedures, network plan, SBOMs); n.a. rows are argued via the delimitation statement (no §52 security functions, no built-in remote maintenance). Open requirements point at their closing issues (M27/M28/M29/M32), so the document doubles as the gap list; the never-scheduled external pentest is stated honestly. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com> |
|||
| 87c1c5ee88 |
#231: residual-risk list
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 7m1s
CI / Build container images (pull_request) Successful in 1m16s
CI / Auth e2e pack (pull_request) Successful in 8m48s
CI / Import/export fidelity gate (pull_request) Successful in 59s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
docs/vs-nfd/90-restrisiken.md: nine entries, each with risk, why it is accepted, compensating control and decider — unmarked attachment content (#212), local auth not yet switchable incl. the open runtime- flippability question (#216), deferred plugin hash pinning (#232), the one-time git-history secret check with its pattern caveat (#198), digest-mail titles (I-23, revisit M32), page_links slug residue (I-24), the IndexedDB endpoint copy (I-25), deliberately unscheduled features, and the Site-Admin read bypass. Binding same-PR maintenance rule stated; referenced from the delimitation statement and consumed by the Grundschutz mapping. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com> |
|||
| 2c6eff85f3 |
#227: hardening guide with the VS-NfD reference configuration
docs/vs-nfd/50-haertungsleitfaden.md: one adoptable profile — every entry with the exact switch name, value, default and the reason, split into instance settings (registration closed, api/mcp off, feeds off, plugins off, classification defaults vs_nfd + upload block, svg reject, minimal extension list) and deploy-level configuration (empty BACKUP_ALLOWED_TARGETS enforces backup-local-only outside Site-Admin reach; tightened session hours; SMTP deliberately unconfigured with the consequence stated honestly). auth.local.enabled is listed as the one pending row (#216) with its compensation until then; the guide states the binding updated-in-same-PR rule for every future switch. Includes an operator verification checklist (four unauthenticated 404 curls + readyz + admin spot checks). Cross-referenced from the delimitation statement (file names made concrete) and consumed by the Grundschutz mapping (#230). Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com> |
|||
| 040f3fbeae |
#229: operations manual (install, update, backup/restore, deletion, roles)
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 7m1s
CI / Build container images (pull_request) Successful in 1m21s
CI / Auth e2e pack (pull_request) Successful in 8m47s
CI / Import/export fidelity gate (pull_request) Successful in 58s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
docs/vs-nfd/70-betriebshandbuch.md: installation as run on the real stages (airgap variant explicitly pending #218-#221 with what already exists as groundwork), update/rollback incl. the no-down-migrations caveat, backup/restore with the ADR-0026 target allowlist and the rehearsed monthly restore drill (evidence: logs on #98), the full scheduler-job table (cadences verified against code), the deletion-and- destruction chapter built on the #228 copy list (per content type: what deletion reaches, what remains, immediate-destruction path, decommissioning), and role separation incl. the deliberate limits of a Site Admin and the honest note that Site Admin read-bypass makes the content/platform split non-absolute app-side. Every procedure carries its evidence level (erprobt / nicht geprobt / offen) — nothing claimed above what was actually executed. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com> |
|||
| f0c6af4412 |
#228: security documentation (architecture, data flows, network plan)
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 7m0s
CI / Build container images (pull_request) Successful in 1m22s
CI / Auth e2e pack (pull_request) Successful in 9m2s
CI / Import/export fidelity gate (pull_request) Successful in 1m2s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
docs/vs-nfd/60-sicherheitsdokumentation.md: component diagram with per- service purpose and privileges, network plan digit-exact against the deploy compose (127.0.0.1-only app bindings, internal-only data zone), data-flow diagrams (auth, realtime editing incl. LISTEN/NOTIFY and the 60s collab token, export via the pinned sidecars, backup incl. the ADR-0026 allowlist, and every read channel), named trust boundaries (reverse proxy, plugin sandbox, outbound SMTP/mirror), and the complete list of content copies — in-database, on-volume and outside the instance — that the deletion concept in #229 builds on. Mermaid only, German (assessor audience), with the maintained-in-same-PR rule stated. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com> |
|||
| 868b79c8bc |
#213: warn on uploads to classified pages; instance policy can block
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 6m15s
CI / Build container images (pull_request) Successful in 4m27s
CI / Auth e2e pack (pull_request) Successful in 9m10s
CI / Import/export fidelity gate (pull_request) Successful in 53s
CD / Build and push images (push) Successful in 17s
CD / Deploy to Test (push) Successful in 15s
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 20s
CI / Lint, typecheck, test (push) Successful in 5m47s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 8m26s
CI / Import/export fidelity gate (push) Successful in 1m0s
The attachments panel of a classified page shows a persistent notice naming the consequence (de+en): the file inherits the page's classification but its content carries no marking (#212). The new instance setting classification.uploadPolicy (default warn, documented; the VS-NfD reference configuration blocks, #227) hardens the warning into a server-side rejection (403 classified_upload_blocked) — enforced in the upload service, not only in the UI. Tests: warning visible in the local attachments pack; block enforced server-side with warn/block both ways and open pages unaffected. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com> |
|||
| e505fc74dc |
#212: mark attachment downloads by filename prefix and companion file
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m34s
CI / Build container images (pull_request) Successful in 14s
CI / Auth e2e pack (pull_request) Successful in 9m36s
CI / Import/export fidelity gate (pull_request) Successful in 57s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
Downloads whose effective classification is vs_nfd carry the documented VS-NfD_ filename prefix (single source classificationFilenamePrefix() in shared; ADR 0022 records the short form for file names). Effective classification: the linked page's level; an attachment with unset pageId (paste-then-insert, pond-level) FAILS CLOSED to the highest level of any live page in its pond. The pond export ZIP adds a sibling <file>.classification.txt companion with the full marking for classified media, next to the manifest entry (#210). Documented in operations.md, incl. the deliberate residual risk: the file's own content carries no marking (recorded on #231, not hidden). Tests: prefixed classified download, unchanged open download, fail-closed orphan both ways, ZIP companion + manifest level. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com> |
|||
| 521ea514b4 |
#211: classification through feeds, public API, search and the no-JS shell
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m38s
CI / Build container images (pull_request) Successful in 4m14s
CI / Auth e2e pack (pull_request) Successful in 9m7s
CI / Import/export fidelity gate (pull_request) Successful in 1m6s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
Feeds: classified entries carry a standard Atom <category> (term=level, scheme=urn:dorfteich:classification, label=the fixed wording); the feed document states the highest contained level once; all-open feeds carry none. Public API: page representations (list+get) gain the classification field, OpenAPI + public-api.md documented. Search: every hit carries the level and the palette renders the marking with the snippet (compact form of the banner, text token only). No-JS shell: banner above and below the content, own markup for the separate render path; unclassified pages unchanged everywhere. One test per channel (feed categories + count, public API list/get with the switch on, search hit levels, shell top+bottom). Also: fidelity CI sidecars get per-job container names — the fixed names collided across parallel runs on the shared host (run 547's red fidelity job; a fixed-name cleanup could even kill a sibling's live sidecars). Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com> |
|||
| 68497046e9 |
#210: mark the Markdown ZIP export with frontmatter, imprint and manifest
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m1s
CI / Build container images (pull_request) Successful in 2m58s
CI / Auth e2e pack (pull_request) Successful in 9m6s
CI / Import/export fidelity gate (pull_request) Successful in 1m8s
CI / Import/export fidelity gate (push) Blocked by required conditions
CD / Build and push images (push) Successful in 24s
CI / Lint, typecheck, test (push) Successful in 6m37s
CD / Deploy to Test (push) Successful in 12s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m25s
CD / Promote to Int (push) Successful in 13s
CI / Auth e2e pack (push) Has been cancelled
A classified page's .md carries the level in YAML frontmatter AND the marking line at top and bottom; unclassified files are byte-identical to before. Every pond archive (incl. the per-pond folders of the account data export) ships a manifest.json listing each file with its level and stating the highest level once at archive level — media inherits the highest classification among the readable pages referencing it (fail-closed). Round trip: the importer recognizes exactly our frontmatter block, strips it plus the imprint lines, and creates the page at least at the imported level (content must not escape its marking by traveling through a ZIP) — pinned by unit and e2e round-trip tests. Foreign frontmatter passes through unchanged; the Obsidian vault import keeps its own frontmatter modes. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com> |
|||
| 74a9e495e4 |
#209: pandoc reference documents carry the VS-NfD marking for DOCX/ODT
reference-vs-nfd.docx/.odt ship as derived binaries: the pinned pandoc's
default reference documents plus a header and footer with the marking —
part of the document's page setup, so it repeats on every page in Word
and LibreOffice and is not deletable body text. Source of truth is
scripts/gen-classified-reference-docs.mjs (wording from shared
classificationMarking(); maintenance documented in assets/README.md).
The converter passes reference docs to pandoc-server via in-request
files + reference-doc; the worker attaches them for marked docx/odt jobs
(job option {marking}, as in #208). Unclassified exports pass nothing
and are unchanged (pinned by fake-converter test). Fidelity suite
asserts against real pandoc 3.6 that marked outputs carry the
header/footer parts and unmarked ones do not; per-page repetition
verified via LibreOffice 25.8 headless PDF (5/5 pages, 2 markings each,
both formats). Word: quick manual look pending (sample files in the
workspace), procedure documented in assets/README.md.
Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
|
|||
| c2df7c0c23 |
#208: VS-NfD marking in the Gotenberg per-page header and footer
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m16s
CI / Build container images (pull_request) Successful in 3m0s
CI / Auth e2e pack (pull_request) Successful in 8m54s
CI / Import/export fidelity gate (pull_request) Successful in 57s
CD / Promote to Int (push) Blocked by required conditions
CD / Build and push images (push) Successful in 24s
CD / Deploy to Test (push) Successful in 11s
CD / Smoke tests against Test (push) Successful in 1m27s
CI / Lint, typecheck, test (push) Failing after 6m24s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
A classified page's PDF export carries its marking as a job option; the renderer hands it to Gotenberg's Chromium header/footer templates, so it repeats on every page — bold centered in the running header and next to the existing page numbers in the footer. Unclassified pages send exactly the pre-#208 forms (unchanged PDF, asserted by the fidelity smoke and a lastMarking=null check). New real-Gotenberg fidelity test asserts the marking appears twice on EVERY page of a multi-page render while the document-level header keeps working. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com> |
|||
| 809e071f14 |
#207: print stylesheet with the classification on every printed sheet
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 5m56s
CI / Build container images (pull_request) Successful in 1m27s
CI / Auth e2e pack (pull_request) Successful in 9m18s
CI / Import/export fidelity gate (pull_request) Successful in 1m6s
CI / Import/export fidelity gate (push) Blocked by required conditions
CD / Build and push images (push) Successful in 20s
CD / Deploy to Test (push) Successful in 11s
CD / Smoke tests against Test (push) Successful in 1m25s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 6m5s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Has been cancelled
First @media print support at all: page size/margins, navigation and interactive chrome suppressed, break behaviour for headings, tables, code blocks, figures and plugin blocks. The VS-NfD marking runs as header AND footer on every sheet via a real-table PrintFrame whose thead/tfoot browsers repeat per page — @page margin boxes are unimplemented and position:fixed places unreliably in both engines (verified empirically); on screen the table chain renders as plain blocks, so nothing changes visually. Verified as PDF-from-browser in Chromium 140 and Firefox 153 (2 markings on every page of a multi-page document); the repeatable procedure is documented in apps/web/e2e/README.md. Unclassified pages print without a marking. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com> |
|||
| adceca7358 |
#206: show the VS-NfD marking in web view header and footer
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m24s
CI / Build container images (pull_request) Successful in 4m24s
CI / Auth e2e pack (pull_request) Successful in 8m44s
CI / Import/export fidelity gate (pull_request) Successful in 59s
CD / Build and push images (push) Successful in 26s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 1m30s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 6m10s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 8m55s
CI / Import/export fidelity gate (push) Failing after 50s
ClassificationBanner renders the fixed ADR-0022 wording above and below the content in reading view, editor and public page view; unclassified pages show nothing. Announced to assistive tech via a localized hidden prefix (de+en); styled from the plain text token only, so contrast holds in both themes and under every accent with no new color pair. Public content endpoint now carries the classification. New seed fixture classified-note; a11y pack asserts banner top+bottom and axe-clean in light and dark. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com> |
|||
| 488d0d06f1 |
#205: classification inherits down the tree; lowering is a guarded, audited act
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 5m40s
CI / Build container images (pull_request) Successful in 4m34s
CI / Auth e2e pack (pull_request) Successful in 9m7s
CI / Import/export fidelity gate (pull_request) Successful in 1m0s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
New pages take max(instance default, parent level); moving a subtree under a higher-classified parent raises every member below that level. No move-like path (reposition, trash-promote, purge-promote) lowers a level as a side effect — pinned by test. Raising is ordinary editorial work; lowering requires the dedicated capability canLowerClassification (pond-wide Pond Admin) in the central permission model. Both directions are audited (page.classification_raised/_lowered, catalogue v1.1) with old value, new value, actor and page. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com> |
|||
| 183faf7710 |
#204: classification as first-class page metadata (ADR 0022)
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 5m42s
CI / Build container images (pull_request) Successful in 3m56s
CI / Auth e2e pack (pull_request) Successful in 8m17s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 24s
CD / Deploy to Test (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 6m17s
CD / Smoke tests against Test (push) Successful in 3m32s
CI / Build container images (push) Has been skipped
CD / Promote to Int (push) Successful in 13s
CI / Auth e2e pack (push) Successful in 8m20s
CI / Import/export fidelity gate (push) Successful in 55s
Enum field on Page (UNCLASSIFIED default, VS_NFD), migration backfills existing pages. New pages take the instance-wide default from classification.newPageDefault (admin-visible, de+en). The value rides in every PageView, so no channel needs an extra request. The field is a marking, not a protection mechanism: a test pins that permission decisions are unchanged by it. The marking wording is fixed in ADR 0022 and sourced solely from classificationMarking() in @dorfteich/shared. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com> |
|||
| db4f517e44 |
#203: pin all third-party deploy images by digest
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 5m32s
CI / Build container images (pull_request) Successful in 1m13s
CI / Auth e2e pack (pull_request) Successful in 8m22s
CI / Import/export fidelity gate (pull_request) Successful in 57s
CD / Build and push images (push) Successful in 16s
CD / Deploy to Test (push) Successful in 56s
CD / Smoke tests against Test (push) Successful in 1m24s
CD / Promote to Int (push) Successful in 52s
CI / Lint, typecheck, test (push) Successful in 5m36s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 8m3s
CI / Import/export fidelity gate (push) Successful in 57s
The four third-party images in the deploy compose (postgres, pandoc, gotenberg — previously a floating MAJOR tag —, caddy) are now name:tag@sha256 pins; the tag stays for readability, the digest decides what runs. The pinned digests are exactly what the stages already run (verified against the live containers' RepoDigests on ONE), so the next recreation is byte-identical. A new early CI step fails on any third-party compose image without a digest; compose.dev.yml is a local convenience and deliberately exempt (its node helpers now follow the #236 pin). Update + rollout procedure in deploy/stages.md — CD does not sync stage composes, so the hand rollout to test/int/prod is part of this issue's definition of done. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 000d110727 |
#201: stable audit event catalogue for syslog/SIEM export
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 6m9s
CI / Build container images (pull_request) Successful in 3m5s
CI / Auth e2e pack (pull_request) Successful in 8m40s
CI / Import/export fidelity gate (pull_request) Successful in 1m2s
CD / Build and push images (push) Successful in 20s
CD / Deploy to Test (push) Successful in 14s
CD / Smoke tests against Test (push) Successful in 1m18s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 5m40s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 8m10s
CI / Import/export fidelity gate (push) Successful in 57s
The 36 audit action ids become a typed union (AUDIT_EVENTS in audit-actions.ts) — an uncatalogued id is now a compile error; every existing id keeps its name. The published, versioned catalogue (docs/architecture/audit-events.md, v1.0) documents per event: trigger, severity, actor and target semantics, and every field, plus the compatibility promise (ids are never repurposed; retiring keeps the row forever) and the stable stdout field set. audit-catalogue.test.ts is the fence: it parses the document's event tables and fails when ids or severities drift from the code (negative case verified). Audit stdout lines now carry the catalogue severity as a routing hint — pino level stays 30 so transport is unaffected; no DB migration. Forwarding path documented: container stdout -> operator's collector; deliberately no application-side syslog client. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| c4c84b33f9 |
#200: hard instance-wide plugins.enabled kill switch
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 5m38s
CI / Build container images (pull_request) Successful in 4m11s
CI / Auth e2e pack (pull_request) Successful in 8m55s
CI / Import/export fidelity gate (pull_request) Successful in 1m9s
CI / Import/export fidelity gate (push) Blocked by required conditions
CD / Build and push images (push) Successful in 20s
CD / Deploy to Test (push) Failing after 51s
CD / Smoke tests against Test (push) Has been skipped
CD / Promote to Int (push) Has been skipped
CI / Lint, typecheck, test (push) Successful in 5m37s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Has been cancelled
plugins.enabled (instance setting, default on — plugins predate the switch; the VS-NfD reference configuration turns it off) makes every plugin surface answer 404 via a shared guard: Site-Admin install/list/mode, pond activation and plugin list, the sandbox frame and asset routes. The dropzone watcher quarantines drops instead of installing. Deliberately NOT guarded: the authenticated fallback-metadata route — it serves no plugin code and existing plugin_block nodes need it to render their declared fallback (an image fallback degrades to the neutral placeholder while off, because its bytes live on the disabled asset surface). The editor offers no plugin blocks because the pond plugin list is one of the 404ing surfaces. Admin settings panel gets the toggle (i18n de+en) with the documented api-restart note (in-process settings cache). Answers "code execution inside the zone?" with one verifiable off-switch instead of per-plugin trust machinery (#232, ADR 0025). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 74970f6073 |
#199: SHA-256 integrity hashes for attachments
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m12s
CI / Build container images (pull_request) Successful in 3m4s
CI / Auth e2e pack (pull_request) Successful in 8m35s
CI / Import/export fidelity gate (pull_request) Successful in 1m2s
CI / Import/export fidelity gate (push) Blocked by required conditions
CD / Build and push images (push) Successful in 29s
CD / Deploy to Test (push) Successful in 12s
CD / Smoke tests against Test (push) Successful in 1m35s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 6m10s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Has been cancelled
Every upload stores the SHA-256 of its bytes, computed from the in-memory buffer that is written — never by re-reading disk. Every download re-hashes the stored object BEFORE the first byte leaves (memory bounded by the max_file_bytes quota that gated the upload) and fails closed on mismatch with attachment_integrity_failure; the mismatch lands in the audit trail as file.integrity_failed with both hashes. Detection of payload manipulation is the one integrity duty par. 52 VSA leaves with the application — only it knows what the file should be. Pre-#199 rows are hashed by a bounded, idempotent backfill that rides the existing nightly orphan-file-sweep job (no new scheduler job, job fence untouched); unreadable files are logged and retried, never silently skipped, and null-hash rows are served unverified only until the backfill reaches them. Operator runbook note in security.md (restore from backup, re-download, audit entry carries both hashes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| d3289b2167 |
#202: SBOM and license report in CI
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 5m33s
CI / Build container images (pull_request) Successful in 4m38s
CI / Auth e2e pack (pull_request) Successful in 9m14s
CI / Import/export fidelity gate (pull_request) Successful in 1m12s
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Deploy to Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CI / Lint, typecheck, test (push) Waiting to run
CI / Import/export fidelity gate (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
The release run now generates CycloneDX 1.6 SBOMs with a pinned anchore/syft container — one per released image (scanned from the freshly built image tar, OS packages included) and one for the pnpm workspace (from the lockfile) — plus the full pnpm licenses report, and attaches everything as build artefacts BEFORE publishing the release, so a red gate stops the release. Runner constraints dictated the mechanics (documented in the workflow): the job talks to the HOST daemon, so files travel into the syft container via docker cp and images via docker save to a tar copied the same way (syft cannot read a tar from stdin — verified). scripts/check-licenses.mjs is the documented license policy: permissive allowlist, MPL-2.0/CC-BY-4.0 with recorded reasoning, per-package exception table (khroma: MIT text shipped, metadata missing). CI runs the gate on every PR (pnpm licenses:check); positive and negative case tested locally, both SBOM paths tested against real images/lockfile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 6a520e27b1 |
#236: pin the Node version
All checks were successful
.node-version (22.15.1) becomes the single authoritative Node version: CI/CD select Node only via node-version-file, every Dockerfile pins node:22.15.1-alpine, and the engines floor in package.json states the same version (open-ended upwards so a newer local Node keeps working — reproducibility rests on images and CI). An early CI step fails on any drift between those places; update procedure in operations.md (Update strategy). Precondition for the reproducibility claim in #219. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 9a43a2f6bb |
#235: keep page_links rows pointing at purged pages — recorded decision
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 5m53s
CI / Build container images (pull_request) Successful in 1m11s
CI / Auth e2e pack (pull_request) Successful in 8m6s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 14s
CD / Deploy to Test (push) Successful in 16s
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 5m19s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m39s
CI / Import/export fidelity gate (push) Successful in 58s
The row is only the index of a wikilink whose text (slug = title) remains visible in the linking page's own content either way; deleting the index would remove nothing the system still shows while breaking phantom-link re-resolution. Kept as an accepted residue, reasoning in operations.md (deletion/purge section) and recorded on #231. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 69d9072d2c |
#234: retention for mail_outbox
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 5m51s
CI / Build container images (pull_request) Successful in 3m4s
CI / Auth e2e pack (pull_request) Successful in 8m4s
CI / Import/export fidelity gate (pull_request) Successful in 57s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
Sent mails were kept forever, and digest bodies name page titles and actors — an unbounded copy of content-adjacent data. A new daily mail-outbox-retention job deletes SENT rows (by sentAt) and permanently FAILED rows (by nextAttemptAt, the last attempt's stamp) once they pass mail.outboxRetentionDays (instance setting, default 30). PENDING rows — including failed-but-retryable ones — stay the retry loop's alone. Decision recorded (security.md §Privacy, residual-risk note for #231): digest mails keep carrying page titles for now — there is no per-page classification marking yet to key a suppression on (ADR 0022 / M32 revisits), and a VS-NfD reference configuration can leave SMTP unconfigured entirely. Job-count fence in system.spec: 8 -> 9. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| ff505bc752 |
#233: prune conversion job payloads for every job kind
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 5m12s
CI / Build container images (pull_request) Successful in 3m28s
CI / Auth e2e pack (pull_request) Successful in 8m33s
CI / Import/export fidelity gate (pull_request) Successful in 1m2s
CD / Build and push images (push) Successful in 29s
CD / Deploy to Test (push) Successful in 12s
CD / Smoke tests against Test (push) Successful in 1m22s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Failing after 5m9s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
The raw input/result bytes of import/export conversion jobs were kept forever; a deleted classified page could live on inside its last export. A new daily conversion-payload-prune job nulls both once a finished (succeeded or failed) job passes conversion.payloadRetentionDays (instance setting, default 30) — the row survives for status/audit. PENDING and RUNNING rows keep their payload, so the worker's stale-lock recovery path is untouched; a hand-requeued pruned job fails finally via conversionInputOf instead of crashing the worker. The input column becomes nullable; the migration backfills by clearing payloads of jobs already finished longer ago than the default period (recent results stay downloadable until they age out). Job-count fence in system.spec: 7 -> 8 (new scheduler registration). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| ff842f97e2 |
#198: CI fence — no tracked .env or secret material, example is authoritative
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 5m27s
CI / Build container images (pull_request) Successful in 1m16s
CI / Auth e2e pack (pull_request) Successful in 7m49s
CI / Import/export fidelity gate (pull_request) Successful in 57s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 28s
CD / Smoke tests against Test (push) Successful in 1m32s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 5m14s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m41s
CI / Import/export fidelity gate (push) Failing after 10s
Verification result: only deploy/compose/.env.example was ever tracked (full-history check), zero hits for obvious secret patterns across all added lines in history — recorded on issue #231 (residual-risk list). The new CI step in the checks job fails if any .env other than .env.example is tracked or a tracked file matches an obvious secret pattern (private key blocks, AWS/GitHub/GitLab/Slack token shapes). .env.example already documents every variable the compose files reference (verified: comm of compose ${VAR} refs vs example keys is empty). README states the example as the authoritative reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 3c62b7b773 |
#197: security response headers and an explicitly restrictive CORS policy
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 5m15s
CI / Build container images (pull_request) Successful in 1m9s
CI / Auth e2e pack (pull_request) Successful in 7m43s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 12s
CD / Smoke tests against Test (push) Successful in 1m29s
CD / Promote to Int (push) Successful in 14s
CI / Lint, typecheck, test (push) Successful in 5m24s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m35s
CI / Import/export fidelity gate (push) Successful in 55s
Hand-rolled middleware instead of helmet: the header set is small enough to own, every value is a deliberate decision, and the api gains no transitive dependency. HSTS (no includeSubDomains — the api cannot speak for sibling subdomains), nosniff, Referrer-Policy no-referrer, X-Frame-Options SAMEORIGIN (not DENY: the plugin sandbox frame embeds same-origin and its CSP has no frame-ancestors, so this header governs), and a minimal deny-all Permissions-Policy. CORS grants no foreign origin anything; only the APP_BASE_URL origin is ever echoed (where browsers do not consult CORS anyway), with Vary: Origin on every response. No preflight handling — same-origin requests never preflight, and cross-origin API access is cookie-less by design (PAT/Bearer). Wired via the AppModule MiddlewareConsumer so createTestApp boots the identical middleware. Fences: security-headers.e2e.test.ts (header set, foreign origin gets no ACAO) and a frame assertion in plugins.e2e.db.test.ts (framing stays possible). Rationale table in security.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| ed2225bb77 |
#196: audit-trail retention job
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 5m5s
CI / Build container images (pull_request) Successful in 2m48s
CI / Auth e2e pack (pull_request) Successful in 7m50s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 15s
CD / Deploy to Test (push) Successful in 16s
CD / Smoke tests against Test (push) Successful in 1m20s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 5m11s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m38s
CI / Import/export fidelity gate (push) Successful in 56s
audit.retentionDays (instance setting, default 365) bounds the audit_log: the daily audit-retention job deletes entries past the period and records the deletion itself (audit.pruned with count, cutoff and period) so a gap in the trail is always explainable. Lives in its own AuditRetentionService because the settings service audits its writes - folding retention into AuditService would close a constructor cycle. The read-access trail (#222-#225) is deliberately not covered; it gets its own period. security.md gains the Logging section the schema has cited for a while; the maintenance-job fence moves 6 -> 7 (the deliberate new row). Refs #196 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 960a806ee3 |
#195: trashed content leaves the search index itself
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 5m4s
CI / Build container images (pull_request) Successful in 2m47s
CI / Auth e2e pack (pull_request) Successful in 7m44s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 1m22s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 5m9s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m53s
CI / Import/export fidelity gate (push) Successful in 53s
Trashing a page (promote and subtree modes) clears the affected search vectors, restoring rebuilds them; pond trash clears every page vector of the pond, pond restore reindexes only the live pages (pages trashed inside stay out); the GDPR pseudonymization's personal-pond trash does the same. reindexAll now converges to the invariant (clears trashed, rebuilds live), and a one-off migration backfills vectors of already-trashed content. The query-side deleted_at guards stay untouched as the independent second layer - the test proves both layers separately, including writing a vector back onto a trashed page (simulating a future path that forgot the clear) and asserting the query still hides it. New provider methods removePond/reindexPond behind the SearchProvider seam. Refs #195 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 0bc36aa58c |
#194: orphan-file sweep, drop the unused Attachment.deletedAt
Nightly sweep with two directions: attachments still unclaimed (pageId null) after a 24 h grace period - claimed by no collab persist, page upload, or import - are reclaimed (row, file, quota released); files on the uploads volume without a database row (drift after a crashed upload) are removed once older than the grace period. The grace period protects the paste-then-insert window. Deliberate deviation from the issue's content-reference idea, documented in schema comment and operations.md: claimed attachments whose page content no longer embeds them are NOT auto-deleted. The page attachments panel lists claimed files as user-managed objects (inserting into the document is optional there), so 'not embedded' is not 'unused' - an auto-delete would destroy panel assets. Humans clean those up in the panel or the pond file manager, which flags orphans already. Attachment.deletedAt is removed by migration - deletion is hard everywhere (sweep, purge, manual), there is no soft-delete state; the never-true deletedAt:null filters in files/export queries went with it. Refs #194 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 402b22e05f |
#193: pond purge — retention job and manual Site-Admin endpoint
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m58s
CI / Build container images (pull_request) Successful in 2m47s
CI / Auth e2e pack (pull_request) Successful in 7m46s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 28s
CD / Smoke tests against Test (push) Successful in 1m20s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 5m4s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m38s
CI / Import/export fidelity gate (push) Successful in 56s
Deletion now actually deletes: a trashed pond past the trash retention (same clock as pages, extended trash-purge job) or purged manually via DELETE /ponds/:id/purge (Site-Admin-only, like pond restore) is removed with everything it holds. Files go first (idempotent rm, resumable on a crash), then one transaction ordered around the FK actions: attachments and labels (Restrict) precede the pond; the page delete cascades versions, comments, content cache incl. the search vector, update log, mentions, label assignments, favorites, outgoing links and open collab sessions; the pond delete cascades grants, usage counters (that is the quota correction), pond-plugin opt-ins and conversion jobs; polymorphic watches and pond quota overrides are deleted explicitly. A purge racing a restore or another purge is a no-op; both paths record a pond.purged audit event. Known residues by design, documented in operations.md: target_slug in other ponds' page links (#235) and backups within their retention. Refs #193 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 394d1c811d |
#192: deploy-level backup target allowlist
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m52s
CI / Build container images (pull_request) Successful in 3m54s
CI / Auth e2e pack (pull_request) Successful in 8m4s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 5m0s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m41s
CI / Import/export fidelity gate (push) Successful in 56s
BACKUP_ALLOWED_TARGETS (comma-separated destination hosts) constrains where backups may go, enforced twice: the api rejects settings writes and connection tests towards non-allowlisted hosts with admin-visible error codes and resolves a non-allowlisted configured target to null, and the sidecar enforces the same policy at the point of egress for the WebDAV upload and the rsync mirror alike (shared policy helpers in packages/shared/src/backup-target-policy.ts). BREAKING: the empty default disables every remote target - backups stay local only, the VS-NfD reference configuration (ADR 0026). Existing deployments with a remote target must list its host or uploads and mirror stop. The admin UI distinguishes unavailable-by-policy from unconfigured (i18n de+en) and shows the permitted hosts. Refs #192 (ADR 0026) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| afef45732a |
#191: feeds.enabled instance switch, feed-token log masking
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m52s
CI / Build container images (pull_request) Successful in 3m55s
CI / Auth e2e pack (pull_request) Successful in 7m52s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 16s
CD / Deploy to Test (push) Successful in 14s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m55s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m34s
CI / Import/export fidelity gate (push) Successful in 58s
Chosen path: an instance master switch following the api.enabled/ mcp.enabled pattern — while off, both feed routes AND the feed-token management answer 404 (existence hidden). Default ON: feeds predate the switch, existing instances and their subscribed readers keep working; the VS-NfD reference configuration (#227) turns it off. Admin UI gets the toggle next to the API/MCP switches (i18n de+en). Moving the token out of the query string is documented as rejected: a path segment lands in the same proxy and request logs, and feed readers cannot send headers — that is why the credential is in the URL at all. What DID leak was our own request log (pino logs req.url): the req serializer now masks ?token= values (common/mask-token-param.ts), so no code path logs the credential. Refs #191 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| db4c5ce9ca |
#190: configurable session lifetime with a server-side idle timeout
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m53s
CI / Build container images (pull_request) Successful in 3m55s
CI / Auth e2e pack (pull_request) Successful in 7m53s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 12s
CD / Smoke tests against Test (push) Successful in 1m17s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m53s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m37s
CI / Import/export fidelity gate (push) Successful in 52s
SESSION_ABSOLUTE_HOURS (default 168 h) caps a session's total lifetime from login: expiresAt is set once at creation and never extended — the old sliding 30-day renewal is gone. SESSION_IDLE_HOURS (default 72 h) ends sessions unused for that long, enforced server-side against lastSeenAt with a write throttle scaled to the idle bound so short idle windows still renew. Expired rows are removed on validation and the session list applies both bounds, so idle-dead sessions never show as active. The cookie maxAge follows the configured absolute bound. Documented in .env.example (with the VS-NfD reference values for the upcoming hardening guide #227), compose passes the variables through, security.md and ADR 0007 record the amendment. Refs #190 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| d32c8c3730 |
#189: make the CSRF origin check fail closed
A cookie-carrying mutation without Origin and Referer (or with an unparsable one) is now rejected with 403 csrf_origin_mismatch instead of passing unchecked. The exception for non-browser clients stays structural: PAT/bearer requests carry no session cookie and never reach the check, and a request that does carry the cookie is always checked. The test harness injects the matching Origin (supertest simulates a browser page of this instance) with an explicit suppression header for the negative cases; the Playwright fixture contexts send the header on their manual seeding calls; release-qa.sh pins APP_BASE_URL and sends the matching Origin. Dedicated spec covers: missing headers 403, mismatch 403, unparsable 403, match passes, GETs untouched, PAT mutation without headers passes, cookie+bearer still checked. Refs #189 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 3d1f4fda53 |
#188: purpose-bound token keys via HKDF, jose replaces the homegrown JWT
All checks were successful
CI / Build container images (pull_request) Successful in 3m51s
CI / Auth e2e pack (pull_request) Successful in 7m49s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CI / Lint, typecheck, test (pull_request) Successful in 4m43s
CD / Build and push images (push) Successful in 20s
CD / Deploy to Test (push) Successful in 16s
CD / Smoke tests against Test (push) Successful in 1m19s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m54s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m39s
CI / Import/export fidelity gate (push) Successful in 59s
COLLAB_TOKEN_SECRET becomes a root key: every purpose derives its own HKDF-SHA-256 subkey (deriveTokenKey), and no code path signs with the root key directly. Collaboration tokens are signed and verified by jose with HS256 as an explicit allowlist; the sign/verify API turns async at its three call sites. Unsubscribe tokens move from a purpose-prefix string to the structural subkey, with a documented dual-verify window (legacy derivation accepted until 2026-11-01, covering the 90-day TTL of links in already-sent mail). The cross-runtime property that justified the homegrown implementation is now proven by a test: the built CJS and ESM dist artefacts round-trip tokens in both directions in child processes (jose v6 reaches CJS via Node's require(esm), pinned Node 22 images). Negative tests cover cross-purpose subkeys, root-key-signed tokens, alg:none and RS256. Refs #188 (ADR 0020) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 3e377aaa57 |
#226: add the §52 VSA delimitation statement
Some checks failed
CI / Build container images (pull_request) Successful in 1m10s
CI / Auth e2e pack (pull_request) Successful in 7m46s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Deploy to Test (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (pull_request) Successful in 4m43s
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CI / Lint, typecheck, test (push) Has been cancelled
Reviewer-facing document derived from ADR 0019: per base function (encryption, media protection, network termination, authentication, integrity) what the application does, what it deliberately does not, and which party provides it — every claim traceable to code via the ist-aufnahme. Includes the operator-duty handover (with the IndexedDB endpoint copy named explicitly, I-25) and the delta list mapping every divergence from the target state to its closing issue. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| fd07f716f6 |
docs: VS-NfD readiness planning (ist-aufnahme, plan, ADRs 0019-0026, issue drafts)
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m42s
CI / Build container images (pull_request) Successful in 1m11s
CI / Auth e2e pack (pull_request) Successful in 7m47s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 18s
CD / Deploy to Test (push) Successful in 14s
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m50s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m38s
CI / Import/export fidelity gate (push) Successful in 56s
Add docs/vs-nfd/: the analysis brief, the as-is assessment (42 findings, all verified against the code), the prioritized action plan rev. 2 with issue references written back to every checkbox, the two-stage issue/ADR brief, and the full reviewed draft used to create the forge state. Add eight proposed ADRs 0019-0026 covering the VS-NfD architecture decisions: no security base functions (par. 52 VSA anchor), HKDF token key separation, external authentication, page classification, read-access audit trail (variant A), reproducible offline deployment, plugin trust model, and backup target restriction. Forge state created alongside this commit: 11 labels, milestones M24-M31, issues #188-#236 (docs-only change, no code touched). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| b5d2a436e0 |
#186: pond accent theming — scoped derivation, cascade pond > user > default
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m44s
CI / Build container images (pull_request) Successful in 4m2s
CI / Auth e2e pack (pull_request) Successful in 10m50s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 17s
CD / Smoke tests against Test (push) Successful in 4m2s
CI / Lint, typecheck, test (push) Successful in 4m47s
CI / Build container images (push) Has been skipped
CD / Promote to Int (push) Successful in 14s
CI / Auth e2e pack (push) Successful in 10m7s
CI / Import/export fidelity gate (push) Successful in 56s
Release / Build release images and notes (push) Successful in 1m11s
Release / Release-candidate operations QA (push) Successful in 1m0s
Prod deploy / Deploy the released images to Prod (push) Successful in 17s
pondSettingsSchema gains theme = { accent: '#rrggbb' | null } (null =
inherit the viewer's theme), exposed as a top-level key of the flat
updatePondInputSchema and included in the PondsService settings merge
(the known silent-no-op pitfall). The server validates only the hex;
conformance arises at render time: PondThemeScope (mounted around the
page content next to PondFontScope) derives the accent pair for the
EFFECTIVE mode via useEffectiveTheme and sets it as inline custom
properties — inline beats both tokens.css and the user-theme <style>,
which IS the cascade precedence pond > user > default.
Pond settings get a PondThemeSection (inherit | presets | custom color
with per-mode preview swatches, explicit save like the font manager);
AccentSwatches extracted for reuse; i18n de+en. The no-JS public shell
stays deliberately un-themed (ADR 0018 amendment).
Tests: pond DB test (theme merge keeps fonts, invalid hex 400), e2e
pond-theme.spec (scope boundary content vs. chrome, per-mode
re-derivation, axe on the pond settings page; resets the fixture pond).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
|
|||
| 83a2fe470e |
#184: user accent theming — presets and free color as one mechanism
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 4m41s
CI / Build container images (pull_request) Successful in 4m4s
CI / Auth e2e pack (pull_request) Successful in 11m40s
CI / Import/export fidelity gate (pull_request) Successful in 52s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
apply-theme.ts derives BOTH modes' accent tokens from the stored choice
(ui.theme.accent: preset id or {custom:'#hex'}) and writes them as
<style id="user-theme"> with :root:root + :root:root[data-theme='dark']
blocks — the doubled :root beats tokens.css regardless of document
order, since theme-init.js injects the ui.theme.css cache during <head>
parsing, before the bundle styles. The default preset means NO override
(hand-tuned tokens.css values stay). main.tsx re-derives from the
choice at startup, healing stale caches after app updates.
Settings: accent radiogroup inside the Appearance section (visible
names, color never the only cue) with per-mode preview swatches on
each mode's canonical background, plus a custom color input; i18n
de+en. The second fieldset made bare .settings-fieldset locators
ambiguous — theme specs now scope via input[name] (fence stays).
Tests: apply-theme unit pack, BASE_PALETTE<->tokens.css drift fence in
theme-contrast.test.ts, e2e theme-accent.spec (instant apply, pre-paint
persistence, default removes override, axe smoke with garish yellow in
both modes). ADR 0018 amendment documents the stage-B details.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
|
|||
| 4e0ad82220 |
#180: dark-mode test fence, both-scheme a11y pack, theme e2e, ADR 0018
theme-contrast.test.ts parses tokens.css and asserts every real UI colour pairing (4.5:1 text, 3:1 UI) for BOTH palettes, so palette drift fails unit tests instead of review. theme.test.ts covers resolve/apply logic (Node >= 22 ships a shadowing undefined localStorage global — the test brings its own in-memory storage). The a11y pack now runs its four scans in light AND dark via emulateMedia; the new theme pack exercises the three-way switch end to end (instant apply, reload persistence, live OS follow in system mode, override beats OS). ADR 0018 records the theming model broadly: modes now, accent themes by derivation later. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX |
|||
| 858909564e |
docs: ADR 0017 — Barrierefreiheit als Standard-Anforderung
All checks were successful
CD / Build and push images (push) Successful in 1m12s
CD / Deploy to Test (push) Successful in 11s
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m45s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m15s
CI / Import/export fidelity gate (push) Successful in 56s
Release / Build release images and notes (push) Successful in 1m11s
Release / Release-candidate operations QA (push) Successful in 44s
Prod deploy / Deploy the released images to Prod (push) Successful in 16s
Jede künftige UI-Änderung entwickelt Barrierefreiheit direkt mit (Stefans Vorgabe nach Abschluss des WCAG-2.1-AA-Audits): verbindliche Checkliste als ADR, Kurzfassung in CLAUDE.md für jede Dev-Session, Eintrag im ADR-Index. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq |
|||
| a879561ec7 |
#155: ChordPro-Plugin — Akkordblätter/Leadsheets als Block
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 5m30s
CI / Build container images (pull_request) Successful in 10s
CI / Auth e2e pack (pull_request) Successful in 7m39s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 17s
CD / Deploy to Test (push) Successful in 14s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 4m46s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m1s
CI / Import/export fidelity gate (push) Successful in 54s
Release / Build release images and notes (push) Successful in 1m11s
Release / Release-candidate operations QA (push) Successful in 44s
Prod deploy / Deploy the released images to Prod (push) Successful in 15s
Neues Referenz-Plugin packages/plugins/chordpro nach dem mermaid-Muster: bewusst ohne Fremdbibliothek (Supply-Chain-Lehre aus #136) — eigener minimaler ChordPro-Parser (Direktiven title/subtitle/ artist/key/capo/tempo/comment, Chorus-Fences, [Akkord]-Marker, #-Kommentare) plus SVG-Formatter mit Monospace-Raster: Akkorde über dem Text, Titelkopf, Chorus-Einrückung, XML-escaped. Persistenz {source, svg} — der Snapshot bedient Lesemodus, Public-Ansicht und Exporte über den PluginFallbackRenderer. Edit-Modus: Textarea + debounced Live-Preview. ZIP 20 KB (Limits 64/256 MiB), 4 Parser-/Formatter-Tests, i18n de+en, Doku-Listen (site-admin en+de, plugin-architecture) ergänzt. Prod-Installation wie üblich per Site-Admin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| e164370691 |
#154: Aufgabenübersicht als Kern-Block (Seite + Unterseiten)
Neuer Block-Atom task_overview (Markdown-Fence dorfteich-tasks, HTML-Placeholder). Shared extractTaskRows liest Task-Zeilen mit Text, Mentions (#150) und Start-/Zieldaten (#152); TasksService sammelt zur Lesezeit den Teilbaum (rekursiv via collectSubtreeIds, canAccessPage- Filter je Quellseite) aus Basis-State + page_updates-Log — KEINE abgeleitete Tabelle nötig (Teilbäume sind klein, kein Drift). Neuer auth-Endpoint GET /read/:pond/:slug/tasks; die öffentliche Ansicht expandiert den Placeholder serverseitig zur statischen Tabelle (Instanz-Sprache). NodeView mit Live-Tabelle und Rückschreib-Checkboxen (optimistisch, Override bis der debounced Collab-Persist nachzieht); Einfügen über die Block-Auswahl (eingebauter Eintrag). Unit- + DB-Tests, neuer CI-Pack tasks.spec (voller Loop inkl. Rückschreiben end-to-end), User-Guide-Doku en+de. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| 7252bd16e0 |
#149: Atom-Feeds für Teiche und Seiten, privat via Feed-Token
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 4m53s
CI / Build container images (pull_request) Successful in 4m1s
CI / Auth e2e pack (pull_request) Successful in 7m12s
CI / Import/export fidelity gate (pull_request) Successful in 1m0s
CD / Build and push images (push) Successful in 14s
CD / Deploy to Test (push) Successful in 16s
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 4m35s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Failing after 5m14s
CI / Import/export fidelity gate (push) Has been skipped
GET /public/:pond/feed.xml (zuletzt geänderte Seiten) und GET /public/:pond/:page/feed.xml (Versions-Historie), @Public mit 404-Semantik; öffentliche Teiche anonym, nicht-öffentliche über neues read-only Feed-Token je Nutzer als ?token=dt_feed_… (neue Tabelle feed_tokens + Migration, Verwaltung in den Nutzer-Einstellungen, FeedTokensSection). Öffentliche HTML-Seiten annoncieren den Teich-Feed per link rel=alternate. DB-Tests (anonym/privat/Token-Lifecycle) und User-Guide-Doku en+de. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| 89ffbc0e4d |
#147: Eigene Identität in der API klar dokumentiert
GET /api/public/v1/me existiert bereits — OpenAPI-Summary nennt jetzt ausdrücklich die User-ID, api-guide (en+de) ebenso. MCP war bereits paritätisch (list_ponds + Token-Identität); kein neuer Endpoint nötig. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| d73b120d06 |
#148: Seitenlisten filtern nach createdSince/updatedSince
Neues pageListQuerySchema (ISO 8601, Kulanz für Datum ohne Zeit), Query-Parameter auf interner und Public-API-Seitenliste, Prisma-where mit gte; neue Indizes (pondId, createdAt)/(pondId, updatedAt) als Migration. OpenAPI-Parameter, MCP-Parität (list_pages created_since/updated_since), Doku (api-guide, mcp-guide, public-api.md), DB-Test inkl. 400 bei ungültigem Datum. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| 8d1172154b |
Tutorial K19: Screenshot von Nadias Bühnenplan (Excalidraw) ergänzt
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m55s
CI / Build container images (pull_request) Successful in 1m5s
CI / Auth e2e pack (pull_request) Successful in 6m52s
CI / Import/export fidelity gate (pull_request) Successful in 53s
CD / Build and push images (push) Successful in 16s
CD / Deploy to Test (push) Successful in 16s
Release / Build release images and notes (push) Successful in 1m17s
CD / Smoke tests against Test (push) Successful in 1m15s
Release / Release-candidate operations QA (push) Successful in 49s
CD / Promote to Int (push) Successful in 11s
Prod deploy / Deploy the released images to Prod (push) Successful in 15s
CI / Lint, typecheck, test (push) Successful in 4m36s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 6m46s
CI / Import/export fidelity gate (push) Successful in 54s
Aufgenommen auf Prod (v0.9.1, Handschrift-Fonts nach dem CSP-Fix aktiv) auf der öffentlichen Seite konzerte-live — vervollständigt die neue Excalidraw-Sektion analog zu den Mermaid-/drawio-Beispielbildern. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| 2fe9374f16 |
Doku: Excalidraw als sechstes Standard-Plugin ergänzt
All checks were successful
CI / Import/export fidelity gate (pull_request) Successful in 54s
CI / Lint, typecheck, test (pull_request) Successful in 4m25s
CD / Build and push images (push) Successful in 17s
Release / Build release images and notes (push) Successful in 1m18s
CD / Smoke tests against Test (push) Successful in 1m18s
CI / Build container images (push) Has been skipped
CI / Import/export fidelity gate (push) Successful in 59s
CI / Build container images (pull_request) Successful in 3m49s
CI / Auth e2e pack (pull_request) Successful in 6m47s
CD / Deploy to Test (push) Successful in 11s
CD / Promote to Int (push) Successful in 11s
Release / Release-candidate operations QA (push) Successful in 45s
Prod deploy / Deploy the released images to Prod (push) Successful in 15s
CI / Lint, typecheck, test (push) Successful in 4m36s
CI / Auth e2e pack (push) Successful in 6m54s
- Tutorial K19: neue Sektion „Excalidraw — Skizzen wie von Hand" mit
Nadias Bühnenplan-Beispiel (konzerte-live); Intro fünf→sechs Plugins.
- Tutorial K18: fünf→sechs Standard-Plugins.
- Site-Admin-Guide (en+de): Referenzliste + Build-Namensliste +
Build-Hinweis (~16-MiB-ZIP aus npm).
- plugin-architecture.md: Referenz-Eintrag excalidraw (npm-Library-
Spielart des Bundled-App-Pfads, {scene, svg}).
- developer/extending (en+de): Excalidraw als zweite Bundled-App-Variante.
Holt die in #136 zugesagten Doku-Ergänzungen nach. Screenshot für K19
folgt nach dem CSP-Deploy (damit die Handschrift korrekt rendert).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
|