hyperscale-trust
Trust center for Hyperscale Django apps: a public /trust page of
policy-level security claims generated from the app's data classification and
the platform's published controls, a questionnaire CSV export, and a private
evidence pack for auditors and buyers under NDA. Imported as
hyperscale.trust.
Every claim on the page comes from the host app's data classification, this
package's control catalogue, or the platform's published document — not from
prose someone typed into a form — so the page cannot go stale because
someone forgot to edit it. The only hand-written fields carried through
trust.json are the operator name and the security contact.
Install
uv add hyperscale-trust
INSTALLED_APPS = [
# ...
"hyperscale.trust",
]
urlpatterns = [
# ...
path("trust/", include("hyperscale.trust.urls")),
]
hyperscale/trust/urls.py sets app_name = "hyperscale_trust", so including
the module resolves URLs as hyperscale_trust:page etc. without an explicit
namespace= (passing one is harmless).
The evidence pack's key inventory (keyset_status.json,
key_rotation_events.csv) comes from hyperscale.crypto. Install it with the
crypto extra:
uv add "hyperscale-trust[crypto]"
Without it — or before its tables are migrated — the pack still builds; those two files just report that the key inventory is unavailable rather than being omitted (see The evidence pack).
Settings
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
HYPERSCALE_TRUST = {
"trust_json_path": BASE_DIR / "trust.json", # required
"snapshot_path": BASE_DIR / "platform-controls.json", # required
}
| Key | Default | Meaning |
|---|---|---|
trust_json_path |
none (required) | path to the app's trust.json |
snapshot_path |
none (required) | path to the build-time platform-document snapshot |
document_url |
"" |
URL to fetch the live platform document from; empty means never fetch — always serve the snapshot |
cache_seconds |
86400 |
how long a successful platform-document fetch is cached |
evidence_permission |
"hyperscale.trust.permissions.superuser_only" |
dotted path to a Callable[[HttpRequest], bool] deciding who may open the evidence pack |
An unknown key, a missing trust_json_path/snapshot_path, a non-positive
cache_seconds, or an evidence_permission path that doesn't import to a
callable all raise ImproperlyConfigured. This is checked in AppConfig.ready(),
so a bad setting fails at Django startup, not on the first request.
The default evidence_permission,
hyperscale.trust.permissions.superuser_only, is True only for an
authenticated Django superuser. Supply your own dotted path to gate the
evidence pack on an app-specific owner or admin role instead.
URLs
| Name | Path | Access |
|---|---|---|
hyperscale_trust:page |
/trust/ |
public |
hyperscale_trust:questionnaire |
/trust/questionnaire.csv |
public |
hyperscale_trust:evidence |
/trust/evidence/ |
login + evidence_permission |
hyperscale_trust:evidence_zip |
/trust/evidence.zip |
login + evidence_permission |
(Paths shown are the conventional mount point above; the host app chooses
where to include() the URLconf.)
When trust.json's app.enabled is false (or trust.json itself is
missing or malformed), the public routes 404, and the evidence routes 404 once
the caller is logged in (anonymous callers are redirected to LOGIN_URL
first) — a disabled trust center gives no sign of what it would otherwise
contain. The evidence routes are checked in this order: an unauthenticated
request is redirected to LOGIN_URL first; an authenticated request against
a disabled trust center 404s; only then is evidence_permission consulted,
raising PermissionDenied (403) if it returns False. This ordering means a
disabled center never reveals whether the caller would have been authorised.
What the page shows, and what it deliberately does not
GET /trust/ renders, in order: an overview (app name and description,
operator and security contact — each labelled "Declared by the operator",
since they're the only founder-supplied inputs — region, and the date the
page's source was generated); the data categories processed and how each is
handled, plus a negative claim for every category the app doesn't process;
the controls that protect that data (app-specific and platform, each with an
ISO 27001:2022 Annex A reference, labelled "Enforced by the platform"); the
cryptography policy in prose (algorithms, key custody, rotation, transport);
the subprocessor list; and the operator's own responsibilities (such as a
DPIA) that the data categories imply.
It deliberately carries no category or control ids, no row or attribute
counts, no key ids or rotation dates, no deployment identifiers, and no
canary results — only two ISO dates ever appear: trust.json's
generated_at (as "last updated on …") and, whenever the page is rendered
from the bundled snapshot rather than a live fetch (the default when
document_url is unset, and also the fallback after a failed fetch), that
snapshot's published_at, shown as a "Platform information as of …" badge so
a stale or never-fetched document is visible rather than served as current
without comment. GET /trust/questionnaire.csv
carries the same restriction — it is the same catalogue, exported as
question,answer,annex_a,source rows, one per control and per data-category
question plus rows for region, operator, contact and subprocessors.
Templates
trust.html and evidence.html both {% extends "base_anonymous.html" %}
and use only the title and content blocks — the host app must supply
base_anonymous.html. Markup uses DaisyUI class names (card, badge,
table, btn) but renders sensibly without DaisyUI or Tailwind loaded.
trust.json
Produced by the host app (App Studio's build_bundle, for a generated app)
and shipped next to the app. Full shape and the rules behind it are in the
design spec:
docs/superpowers/specs/2026-09-24-trust-center-design.md.
A worked example is in
tests/fixtures/trust.full.json. Excerpt:
{
"generated_at": "2026-09-24T12:00:00Z",
"app": {"name": "Clinic Notes", "operator_name": "Acme Health Ltd", "enabled": true},
"data": {
"categories": [
{"id": "health", "label": "Health", "definition": "…", "level": "restricted", "handling": "…", "attribute_count": 2, "models": ["Patient"]}
],
"not_processed": [{"id": "payment_card", "label": "Payment card"}],
"free_text_attribute_count": 3
},
"controls": [{"id": "app.encryption.restricted", "annex_a": "A.8.24"}],
"levels": {"restricted": "Encrypted by the application before storage, …"}
}
Two things worth calling out because they're easy to get wrong:
- Each category may carry its own
definitionandhandlingprose, and the top-levellevelsmap gives each sensitivity level a plain description — this is the host app's classification vocabulary, copied in so the package owns no category wording.not_processedis a list of{id, label}objects, not a list of ids. - The package's catalogue (
hyperscale.trust.catalogue.APP_CONTROLS) is authoritative for a control's Annex A reference, title, statement, question and answer, looked up byid. Theannex_avalue on a control entry intrust.jsonitself is informational only and is not rendered.
The platform document and the snapshot
hyperscale.trust.documents.PlatformDocument — version, published_at,
region, hosting_provider, email_provider and a list of controls (id,
title, statement, annex_a) — either fetched live from
HYPERSCALE_TRUST["document_url"] or loaded from the on-disk
snapshot_path. See
tests/fixtures/platform-controls.json
for a complete example.
document_urlunset (the default): the snapshot atsnapshot_pathis loaded fresh on every request; nothing is fetched over the network.document_urlset: a successful fetch (5-second timeout) is cached under a fixed key forcache_seconds(default 24 hours) before it is fetched again.- A failed fetch — network error, non-200 status, invalid JSON, or a
document missing a required field — falls back to the on-disk snapshot.
That fallback is itself cached, but only for
min(300, cache_seconds)seconds: long enough that a deaddocument_urldoesn't pay the fetch timeout on every request, short enough to retry soon after the upstream recovers. - A document served from the snapshot (whether because
document_urlis unset, or as a fallback) is marked stale. The public page then shows a "Platform information as of<published_at>" badge, and the evidence pack'splatform-controls.jsoncarries"stale_snapshot": trueand an explanatorynote.
The evidence pack
GET /trust/evidence/ (an HTML summary) and GET /trust/evidence.zip (the
download) are gated on login plus evidence_permission — the app's owner,
admin, or whoever the host app's callable decides; by default, a Django
superuser. The zip contains:
| File | Contents |
|---|---|
README.md |
index of the other files and which Annex A control each evidences |
cryptography-policy.md |
the cryptography policy rendered as a document, with the operator name and date |
keyset_status.json |
hyperscale.crypto's live key inventory (statuses, last rotation, canary result), or {"unavailable": "<reason>"} if hyperscale.crypto isn't installed or its tables aren't migrated |
key_rotation_events.csv |
every recorded KeyRotationEvent, or a single row explaining why none could be read |
platform-controls.json |
the whole platform document as fetched or loaded, plus stale_snapshot and, when stale, a note |
classification.json |
Data categories processed, with their sensitivity levels and the level definitions |
deployment.json |
GIT_SHA, DEPLOYED_AT, and installed versions of hyperscale-trust, hyperscale-crypto and django |
The spec also lists a per-attribute classification export; that is not yet
part of the trust.json contract, so classification.json carries category
aggregates until App Studio exports it.
keyset_status.json and key_rotation_events.csv never disappear from the
pack just because hyperscale.crypto is absent or not yet migrated — the
failure is recorded in the file instead, so an auditor sees why evidence is
missing rather than a shorter zip.
Environment
GIT_SHA and DEPLOYED_AT are read from the process environment
(os.environ) when the evidence pack is built and written into
deployment.json; they default to an empty string when unset. Neither ever
appears on the public page — only inside the evidence pack.
Wording policy
Control titles, statements, questions, answers (in
hyperscale.trust.catalogue) and the cryptography-policy paragraphs are
vendor-neutral: a reviewer reads "FIPS 140-3 validated key management
service", never a product name. tests/test_catalogue.py asserts none of
that prose contains a name from catalogue.VENDOR_DENY_LIST (currently AWS,
Amazon, KMS, Secrets Manager, RDS, CloudFront, S3, Stripe, Anthropic). The
one place a vendor is named is the subprocessor list, resolved from the
platform document's hosting_provider/email_provider or from names the
host app declares in trust.json — subprocessor lists must name who they
are.
Development
Requires uv and Python 3.14+.
uv sync # create the venv and install dependencies
uv run pytest # run the tests
uv run pre-commit run --all-files # lint, format and lockfile checks
See CONTRIBUTING.md for the full set of checks.
Releasing
Bump version in pyproject.toml and __version__ in
src/hyperscale/trust/__init__.py, update CHANGELOG.md, then publish a
GitHub release tagged v<version>. The publish workflow checks the tag
matches the package version, builds, runs twine check and publishes to PyPI
with trusted publishing.
Before the first release, register the project on PyPI with this repository
and publish.yml as a trusted publisher, and create a pypi environment in
the repository settings.
Design
Design spec: docs/superpowers/specs/2026-09-24-trust-center-design.md
Implementation plan: docs/superpowers/plans/2026-09-25-hyperscale-trust.md
License
MIT. See LICENSE.
Release files for hyperscale-trust 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hyperscale_trust-0.1.0.tar.gz | 18.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hyperscale_trust-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 43.1 kB
Release files / hyperscale_trust-0.1.0.tar.gz
| Download URL | hyperscale_trust-0.1.0.tar.gz |
|---|---|
| Size | 18.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
97cc1a3510baf108899fa874337082cfb1bb51c15a2172ee0e7bb76507a1ab52
|
|
BLAKE2b-256 checksum How to use checksums |
1359e3e0fc533c56a26644f0390e4fa1fae4b51722c8984f5884201b293683db
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / hyperscale_trust-0.1.0-py3-none-any.whl
| Download URL | hyperscale_trust-0.1.0-py3-none-any.whl |
|---|---|
| Size | 24.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
778b2cf8a72826eab51cb6a7790e3b56e6ad55ee7fa8d697f60d67ec38799afe
|
|
BLAKE2b-256 checksum How to use checksums |
fd9a1f99df8cf53be423ad4a5bf6e58299ec293ce99c142d35705535a8b1e872
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log