Release readiness and the M10 boundary¶
This is the slightly boring document that keeps a release honest. M10 does not turn a fixture into a mail provider by changing the wording around it. It packages the review into a repeatable evidence object, runs the security and tenant-boundary checks, and makes the remaining external work visible to the operator.
The useful result at this point is a V1 contract preview: a clean checkout can be installed, tested, inspected, and used as the application/runtime foundation. A production mail service still needs the provider adapters and a rehearsal against real Postfix, Dovecot, LDAP, PostgreSQL, CalDAV, CardDAV, certificate, scanner, backup, and traffic-switching services. Those are explicit residuals, not hidden assumptions.
What M10 checks¶
The M10 gate covers five evidence domains:
| Domain | What is checked in this checkout | What still needs an external rehearsal |
|---|---|---|
| Security | tenant/RBAC boundaries, session and CSRF contracts, MFA primitives, HTML sanitization, abuse limits, secret-free audit events, and the SHA256 checksum sidecar/aggregate for each package | provider secret rotation, real TLS/LDAP/DB configuration, vulnerability disposition, and package signing (GPG/minisign not built yet — see "Artifact provenance" below) |
| Data | quota allocation, 28-day purge, backup authorization, encrypted archive shape, idempotent lifecycle operations | an actual restore, deletion runbook execution, external-volume snapshots, measured RPO/RTO |
| Interoperability | SMTP/IMAP/IDLE, Sieve, DAV object semantics, discovery, ICS/vCard, and timezone contracts | vendor client matrix and real protocol endpoints |
| Operations | health, metrics, logging, alerts, queue visibility, each target's per-package in-place upgrade script, and the shared read-only scanner-signature boundary | host-side scanner feed publication, a real upgrade/rollback rehearsal on each of the three packaging targets, and incident tabletop |
| Governance | role and delegation policy, default-deny master access, optional Plesk/cPanel tenant-tool boundary, read-only API/MCP, ADRs, and documentation inventory | owner approval of the deployment and disaster-recovery runbooks, package signing/attestation (see "Artifact provenance" below), and any provider-specific panel adapter |
The source of truth for the checklist remains Section 30 of GULOGULO.md.
The repository copy is deliberately an evidence boundary, not a second product
specification.
The root README now tracks repository implementation only: checked entries have
code or a tested contract, while deployment and consumer verification are
listed in INSTALL.md. The release evidence object
continues to distinguish verified, contract, conditional, and deferred
external evidence.
LP8 packaged this boundary in
release/lp8-local-proof-bundle.json
and a doc/lp8-evidence-operator.md operator note. That note has since been
archived to old_docs/lp-proof-records/lp8-evidence-operator.md as part of the
ADR-002 packaging move; the JSON bundle still exists but several of its
indexed doc/... paths (including lp8-evidence-operator.md,
local-proof-scope.md, and local-proof-topology.md) are now stale pointers
into files that no longer live under doc/. Live provider evidence and
provider-specific Plesk/cPanel adapters remain outside this checkout.
Artifact provenance¶
The GHCR/container release lane (container-release.yml, GHCR image
publication, and the numeric-tag automation that went with it) was retired
when the project moved off Docker/OCI — it no longer exists, and the
security principles it used to provide (checksum, provenance, attestation,
least-privilege permissions) were readapted to the current
cPanel/Plesk/standalone packages, not carried over automatically.
Checksums have applied to every package since the tar.gz-based targets
shipped: each of the three .tar.gz files gets its own .sha256 sidecar
(packaging/shared/stage-application.ts's writeChecksumFile), aggregated
into one checksums.txt per release.
Since release 0.1.6, .github/workflows/release.yml's publish-release job
also generates a signed build provenance attestation for all three packages
(actions/attest-build-provenance, GitHub's own Sigstore-backed
attestation) before they are uploaded to the release — verifiable with
gh attestation verify <file> --repo Sythos/GuloGulo. This did not exist for
any earlier tag; there is no attestation for 0.1.5 or earlier. An SBOM is
still not generated — that readaptation has not happened yet.
The current, real CI gates for a release are .github/workflows/quality-gates.yml
(repository entry points, MIT/SPDX headers, and the full test suite) plus the
three packaging workflows, each of which builds, actually installs
(install.sh --non-interactive), and boots the package, polling
/health/ready and / — on cPanel and Plesk the systemd
enable/start step is stubbed since their CI containers have no init system;
see ../install.md for the exact verification depth of each target.
Running the gate¶
From a clean checkout, install the locked dependencies and run the complete suite:
npm ci --ignore-scripts --no-audit --no-fund
npm test
The focused M10 checks can be run while iterating:
npm run test:m10
That command runs src/core/release/release-evidence.test.ts through Node's native
TypeScript stripping mode and then audits the portable example at
release/v1-release-evidence.template.json. The audit
prints only a sanitized decision summary. It never prints credentials, raw
deployment output, mailbox content, or local workstation paths.
Evidence object¶
The canonical validator lives in src/core/release/release-evidence.ts; there
is no .mjs compatibility bridge. Its public API is
small on purpose:
import {
createReleaseEvidence,
evaluateReleaseEvidence,
REQUIRED_SECTION30_ITEMS,
} from './src/core/release/release-evidence.ts';
const evidence = createReleaseEvidence({
evidenceVersion: '1.0',
product: 'Gulo Gulo',
version: '0.1.8',
commitSha: '0123456789abcdef0123456789abcdef01234567',
generatedAt: '2026-08-23T00:00:00Z',
releaseDecision: 'conditional',
section30: [/* every REQUIRED_SECTION30_ITEMS entry */],
securityFindings: [],
tests: [],
artifacts: [{
name: 'operator guide',
path: 'doc/release-readiness.md',
purpose: 'Release boundary and evidence instructions.',
}],
residualRisks: ['External service rehearsal is still required.'],
nextCandidates: ['Wire provider adapters.'],
});
const summary = evaluateReleaseEvidence(evidence);
createReleaseEvidence returns a frozen, normalized object. It enforces the
following rules before a release can be discussed:
- all 46 applicable Section 30 item IDs must occur exactly once;
verifiedandcontractentries must point to repository-relative evidence;deferredandexceptionentries must name an owner, mitigation, rationale, and dated approval;- critical or high findings cannot remain
open; - failed tests block the release;
- artifacts, rationale, commands, and residual risks are scanned for secrets, private keys, tokens, and workstation paths;
- an
approveddecision is rejected when the checklist still contains a deferred or exception item.
There are four checklist statuses:
verifiedmeans the local and CI evidence is sufficient for the stated contract;contractmeans the behavior is specified and tested at the boundary, but a vendor or deployment environment must still prove it;deferredmeans the item is intentionally left for the provider rehearsal;exceptionis the same kind of explicit deferral when the owner wants to call out a formal non-conformance.
evaluateReleaseEvidence reports productionReady: false whenever a contract,
deferred, exception, conditional test, or accepted security finding remains.
That conservative result is intentional: a green repository check is not a
certificate, a live LDAP bind, or a measured zero-downtime cutover.
API and MCP handling¶
The tenant monitoring API and MCP stay read-only in V1. M10 adds a release evidence vocabulary; it does not add a write-capable release endpoint.
The provider/operator plane may expose a sanitized read operation equivalent to the following contract:
| Surface | Operation | Result |
|---|---|---|
| HTTP | GET /provider/release/evidence |
current decision, checklist counts, test statuses, residual risks, and next candidates |
| MCP | gulogulo.release.evidence |
the same read-only object, scoped to the provider deployment |
| HTTP | GET /provider/release/capabilities |
evidence schema version and supported status values |
| MCP | gulogulo.release.capabilities |
the same capability document |
The response must be derived from createReleaseEvidence before publication.
It must contain the release version, commit identifier, Section 30 status
counts, sanitized evidence references, and correlation metadata. It must not
contain passwords, tokens, private keys, mailbox content, raw command output,
Docker socket paths, unrestricted Kubernetes arguments, or absolute local
paths.
The tenant, master, and user audiences may read only the subset allowed by the existing RBAC and log-visibility policy. They cannot approve an exception, change a status, start an upgrade, or mark a live rehearsal complete. A provider approval remains an auditable, separate operation.
Login and browser boundary¶
The current HTML5/TypeScript shell is deliberately honest about its stage. It
uses secure-cookie and CSRF contracts, renders mail/calendar/contact views,
and refuses to treat realtime metadata or message HTML as trusted application
content. The authenticated /login route is wired to the real, provider-
backed identity client (src/runtime/login.ts resolves the configured
GULOGULO_PLATFORM target and calls its PlatformAdapter — LDAP or
DB-backed for standalone, UAPI for cPanel, REST for Plesk) for every login
outside GULOGULO_FIXTURE_MODE=true; this is not faked by the release audit.
That route keeps the Gulo Gulo artwork at the left of the login layout,
scaled to 128×128, with the form on the right, as recorded in the canonical
artwork memory.
External-evidence checklist¶
Before calling a deployment production-ready, an operator should attach these records to the release commit or release system:
The detailed hand-off procedure and the account-deletion, backup/restore, scanner, migration, rollback, and incident/DR runbooks are collected in INSTALL.md.
- Package build evidence per target: each of
package-standalone.yml,package-cpanel.yml, andpackage-plesk.ymlbuilds, installs non-interactively, boots the compiled server, and polls/health/ready//; on cPanel/Plesk the systemd enable/start step is stubbed (no init system in the CI container) — real field evidence for that part, and for the Apache/nginx reverse-proxy wiring, is still outstanding (see../INSTALL.md). - TLS/ACME issuance, renewal, expiry alert, LDAP bind, and PostgreSQL backup evidence.
- Postfix, Rspamd, ClamAV, Dovecot, Sieve, CalDAV, CardDAV, and autodiscovery client results.
- Encrypted backup, tenant/user restore, purge, and account deletion records.
- Each target's real in-place upgrade and rollback rehearsal timings (see
doc/upgrade-and-migration.md), including the pre-upgrade backup restore. - Approved RPO/RTO, incident, and disaster-recovery runbooks.
When these records exist, replace the corresponding deferred or contract
entries in a release evidence object with verified, use the actual release
commit SHA, and rerun npm run test:m10 plus the GitHub Actions quality gates.
Future work deliberately outside M10¶
Shared mailboxes, resource calendars, write-capable tenant API/MCP, assisted IMAP migration, and provider-specific live adapters remain next-version or deployment work. Keeping them out of this gate makes the product smaller and safer to review; it does not prevent adding them later behind a new contract and a new acceptance record.