Skip to content

documenso 1.0.0 — self-hosted document signing - #526

Open
jacobecox wants to merge 5 commits into
mainfrom
claude/documenso
Open

documenso 1.0.0 — self-hosted document signing#526
jacobecox wants to merge 5 commits into
mainfrom
claude/documenso

Conversation

@jacobecox

Copy link
Copy Markdown
Contributor

Documenso — self-hosted document signing with a real cryptographic signature, backed by Postgres and either database, S3, or S3-compatible storage.

Testing

Full matrix on test-gvc-3, 37+ rows, every one passing except a single README sentence (fixed here). Report highlights:

  • A document signed end to end, and the signature verified cryptographically against the supplied .p12 — the headline row, not a "a file came back" check.
  • The keyless-S3 presign item cleared. This was the spec's most-doubted [UNPROVEN] assumption: a presigned PUT signed by an identity's temporary credentials. Upload returns 200, object lands in the bucket, presigned GET reads it back.
  • SMTP with real delivery; both access toggles enforced with real protocol bytes; rolling-restart and replica-down continuity; postgresHA.enabled on a fresh install.
  • The Next.js HOSTNAME bind trap that made langfuse unreachable through a port-forward does not apply at this tag.

Cleanup was verified by diffing the GVC spec against a recorded before-state, not by trusting an exit code.

Two findings worth reading

The published image ships live PostHog telemetry credentials (NEXT_PRIVATE_TELEMETRY_KEY, NEXT_PRIVATE_TELEMETRY_HOST). The Dockerfile's ARG defaults are empty, so upstream release CI sets them — telemetry is on by default upstream. The chart ships telemetry.enabled: false and injects DOCUMENSO_DISABLE_TELEMETRY.

Three separate certificate defects all present as a fully green deployment. Neither /api/health, /api/certificate-status, nor cpln logs reports them. The README now names the only surface that does — a BackgroundJob query — and drops a previous claim that the failure appears in the workload log, which testing disproved.

The README's -legacy clause was the round's only FAIL: it repeated upstream's OpenSSL instruction, which at v2.17.0 yields a silently unusable install. Corrected, with the failure mode named.

Known, and not this template's to fix

render-vs-stored reports 0 differences for every resource this chart authors. Nine differences remain inside the vendored postgres 3.4.1 workload, which sends a partial defaultOptions block and lets the API backfill the rest. A parent chart cannot template a subchart's workload spec.

The same nine appeared on calcom this week. 13 shipped templates pin postgres 3.4.1, and that subchart is internally inconsistent — its own workload-backup.yaml already declares the full block. The drift gate passes (two no-op upgrades come back Unchanged), so this is render-vs-stored only — the class the deployment UI surfaces. Flagging for a ruling: a postgres 3.4.2 would fix it for every vendoring template.

🤖 Generated with Claude Code

jacobecox and others added 3 commits August 31, 2026 09:28
Documenso 1.0.0 — open-source e-signature (v2.17.0) on a standard workload with
a bundled postgres (single-instance default, postgres-highly-available behind a
flag). No volumeset: the app tier is stateless.

Build-time probes against the pinned image and tag corrected four things:

- The published image sets no NODE_ENV, so the chart sets it. Without it the
  session cookie loses its __Secure- prefix on an HTTPS endpoint, the language
  cookie loses `secure`, and embedding presign tokens accept expiresIn: 0.
- cert-status.ts returns {isAvailable:true} for any NEXT_PRIVATE_SIGNING_TRANSPORT
  that is not exactly `local`, INCLUDING unset — measured green with no
  certificate present at all. The chart declares it explicitly.
- Telemetry is live in the published image (real PostHog key + host baked in,
  startup event plus hourly heartbeat), not inert as the Dockerfile ARGs suggest.
  Added a telemetry.enabled knob, default off.
- `-legacy` is not required for the .p12 at this tag: legacy, OpenSSL-3-default
  and password-less files all parse through P12Signer.create.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… align private app URL

Three review findings, no blockers.

1. The "Backing up the database" section deferred bucket, cloud-account and IAM
   setup to the postgres template README via a filesystem-relative link
   (../../../postgres), which resolves to nothing when this README renders
   standalone in the marketplace. CLAUDE.md requires cloud-resource setup to be
   written out. Replaced with per-provider steps: the bucket-scoped AWS IAM
   policy JSON (the full action list the backup image needs, including the
   multipart actions), the GCP roles, and the MinIO create-dictionary command —
   mirroring the shape calcom uses for the same pass-through. Prerequisites now
   lists the backup bucket/cloud account as its own optional item rather than
   naming a cloud account only for document storage.

2. A user setting postgres.internalAccess.type to workload-list without listing
   this release's own workload cuts the app off from its own database, and it
   fails as a boot hang rather than an error. A parent cannot inject into a
   subchart's list, so the chart now hard-fails at render, naming the exact link
   to add — mirroring the guard wordpress already has for its mariadb
   pass-through. type: none is refused outright. postgres-highly-available
   exposes no internalAccess knob, so that path needs no guard.

3. With publicAccess off and no publicUrl, NEXT_PUBLIC_WEBAPP_URL was the
   internal service-DNS name while the README tells users to reach the app over
   cpln port-forward at http://localhost:3000. That value drives the session
   cookie domain, so the two origins have to agree. Adopted calcom's choice and
   rationale (calcom/_helpers.tpl "calcom.appUrl"): the private branch is now
   http://localhost:3000.

Verified: bare `helm template --set global.cpln.gvc=test-gvc` renders clean and
is byte-identical to before apart from the changed comment block; both guard
arms proven (bad list and type: none both fail with the new message, a list
including the app still renders); the added create-dictionary command was
executed against the org and the secret verified then deleted; every README URL
resolves; the publicUrl README block still matches values.yaml byte for byte;
lint clean. Briefing updated for the new default and the new guard.
…fuse the boot warning

Documentation-only fixes from the live test round; no chart file changed and the
default render is byte-identical.

- The README claimed both `.p12` forms parse at v2.17.0. Measured false: a
  `-legacy` certificate leaves `Seal Document` FAILED (retried 3) with the
  document PENDING, while the OpenSSL-3 default completes in 20 s — same key,
  same certificate, same passphrase, same release. Following upstream's
  `-legacy` instruction produces a fully green, silently unusable install, so
  the README now says explicitly not to add it.
- Rotation guidance now states the consequence, not just the command: a
  `cpln://` reference resolves at replica start and is never re-resolved.
  Measured 8.5 min with the old value live and `ready: true` throughout, so
  replacing a compromised signing certificate changes nothing without
  `cpln workload force-redeployment`.
- Named the only surface that reports a bad certificate — the `Seal Document`
  row of `BackgroundJob`. The health endpoints and `cpln logs` show nothing.
- Documented `⚠️ Certificate not found or not readable` as expected: upstream's
  start.sh probes for a certificate file while this chart supplies contents.

Briefing updated with all four.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jacobecox

Copy link
Copy Markdown
Contributor Author

Holding this — a latent defect surfaced after it was opened.

workload-documenso.yaml:229 renders a bare minScale with no localOptions and no staticPlacement, so on a GVC with more than one location a default install starts one Documenso replica per location, each bound by service DNS to its own local bundled Postgres. Separate databases, invisible to each other.

The test round could not have caught it: test-gvc-3 is single-location. The identical shape was measured on calcom this week in a 3-location GVC — three app replicas, three independent databases, a table created in one location absent from the other two, every status surface green throughout.

Documenso also has neither protection layer today. Open Question 6 in its test report records that it makes no GVC API call and ships no targetKind: gvc policy, so there is no runtime check either.

A fix adding the structural confinement (defaultOptions.minScale/maxScale: 0 with localOptions supplying the real per-location counts) is in progress and will land on this branch. Everything the round did prove stands — this is an untested axis, not a contradicted result.

Please don't merge until the fix lands and it re-tests in a multi-location GVC.

templates/workload-documenso.yaml rendered a bare `minScale: {{ replicas }}`
with no localOptions and no staticPlacement. A workload runs in EVERY location
its GVC has, so a default install into a multi-location GVC started one
Documenso per location, each bound by service DNS to its own local Postgres —
separate databases, invisible to each other, every status surface green. The
identical shape was measured on calcom in a 3-location GVC; documenso's test
round ran in single-location test-gvc-3 and could not have observed it.

Apply the CLAUDE.md structural confinement: `defaultOptions.minScale/maxScale:
0` with a single `localOptions` entry supplying the real per-location count, so
an undeclared location gets `desiredScale: 0` and starts nothing by
construction (`This workload location is deactivated because maxScale is set to
0`). Shape copied from plane 1.0.0 / calcom.

New `location` value, hard-required at render: empty, non-string and a plural
`locations` key each fail with the fix in the message.

The bundled postgres subchart is NOT covered — neither postgres nor
postgres-highly-available exposes a placement knob and a parent cannot template
a subchart's workload spec — so an extra GVC location still starts an idle
database there on its own empty volume. Said plainly in the README and briefing
rather than claiming the problem is solved.

No boot-time GVC read: the reasons, and the conditions for revisiting, are
recorded in briefings/documenso.md.
… extra one

Round 2 FAIL: validateLocation checked empty/non-string/plural but never the
string's SHAPE. Both '//location/aws-us-east-1' and a comma-separated list
were accepted by Helm AND by the API, which stored a placement matching no
real location -- so nothing ran anywhere, every location reported
'deactivated because maxScale is set to 0', and the logs were silent.

The link form is the copy-paste the values comment invites: it tells the
user to read spec.staticPlacement.locationLinks, whose entries ARE links.
The chart is the only layer that can catch this.

Doc corrections from the same round: an extra GVC location costs a LIVE
idle Postgres (ready 1/1, minCpu 250m / minMemory 512Mi / 10GiB volume each),
not 'a volumeset and nothing else'; and desiredScale is ABSENT on a
deactivated location rather than present-and-0, so the message is the
signal.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant