Skip to main content

scalio-platform

Shared building blocks of the Scalio apps (Nexus, Compass, Citadel, Foundry, Rosetta, Pulse, Beacon, Cockpit, ID, Monitor). Before this package each app kept its own copy of these files (architecture review C2/C4, ADR 0058 in scalio-docs). Nothing in this package is secret.

Module What it does
scalio_platform.job_lease Runs a background loop on one instance at a time (lease in db.job_leases). During an update two instances run side by side; without the lease both would act.
scalio_platform.migrations Schema migrations for MongoDB: ordered steps, each applied once, version in db.meta (schema_version).
scalio_platform.health The answer of GET /api/health that Scalio Monitor reads (ok / degraded / down, leases of the loops).
scalio_platform.error_report Sends unexpected errors to Scalio Monitor, only when ERROR_REPORT_URL and ERROR_REPORT_TOKEN are set.
scalio_platform.app_links Links and features of the product switcher (SCALIO_APP_LINKS, SCALIO_STAGE, SAAS_MODE).
scalio_platform.roles Permission catalog of the app, built-in and own roles (app_roles), require_perm for routes, shared role API (roles_router) for RoleAdmin in scalio-ui.
scalio_platform.tenant_jobs Runs one job per tenant side by side (for_each_tenant): at most TENANT_JOBS_PARALLEL at once (default 4), a time budget per tenant (TENANT_JOBS_TIMEOUT, default 900 s), a failing tenant never stops the others; for MSP instances with many customers (ADR 0063 mandant/M11).
scalio_platform.setup_wizard State of the Einrichtungsassistent (GET/PUT /api/setup/wizard, ADR 0060): wizard_state, save_wizard, parse_update.
scalio_platform.audit Protokoll → Aktionen (ADR 0070): one format in db.audit_log (record, target, diff), TTL 365 days, audit_router (GET /api/audit with filters, cursor and counts per chip, GET /api/audit/export.csv), migrate for old formats, uncovered for the route guard test.
scalio_platform.jobs Protokoll → Jobs (ADR 0070): Registry of the background jobs, run records each run (db.job_runs, TTL 90 days; a job returning None, 0, False, an empty list or a dict without summary and counts only updates db.job_state), «Jetzt ausführen» through db.job_requests and serve_requests in the worker, QueueSource for the app's queue, jobs_router.
scalio_platform.mail E-Mail (ADR 0069 W5): sending through SMTP or Microsoft 365 (secrets sealed by the app, never returned), outbox mail_outbox with retries (flush in the worker), templates per mail kind and language with placeholders (MailKind, own texts in settings {id: "mail_templates"}), router for «E-Mail-Versand» and «E-Mail-Vorlagen» in scalio-ui 0.29.0; free mails queue and the provider tenant (graph_token) since 0.10.0; since 0.11.0 (N22) no SMTP user name or password without encryption (security: none), also not when sending an older setting.

Use in an app

scalio-platform==0.1.0            # backend/requirements.txt, exact version
from scalio_platform import error_report, health, job_lease
from scalio_platform.migrations import BASELINE, run_migrations

MIGRATIONS = BASELINE                      # append (2, migrate_002_...) and so on, never edit past steps

async def startup():
    await run_migrations(db, MIGRATIONS)   # before the background loops
    for name, loop in LOOPS.items():
        asyncio.create_task(job_lease.run_exclusive(db, name, loop))

error_report.install(app, "citadel", build_info)
code, body = await health.health_report(db, build_info(), lease_jobs=sorted(LOOPS))

# probe of the provisioning engine (SaaS on the Docker engine), header X-Provision-Health-Secret
code, body = await health.provision_report(db, build_info(), secret=os.environ.get("PROVISION_HEALTH_SECRET", ""),
                                           provided=request.headers.get("x-provision-health-secret", ""))
# 0.9.0 (AR5): busy=<async () -> int> reports running work an update would break; the engine waits while > 0

A migration step must be safe to run twice: two instances can start at the same moment.

Zurücksetzen (factory reset, ADR 0064 / 0069)

from scalio_platform.factory_reset import factory_reset_router

async def after_wipe(remove_license: bool):
    if remove_license:
        await licensing.runtime.refresh(db)      # free edition right away
    await login_policy.announce_setup_code()     # new setup code in the log

app.include_router(factory_reset_router(
    get_db=lambda: db, current_user=require_admin,
    password_hash=lambda u: u.get("password_hash"), verify_password=auth.verify_password,
    has_second_factor=lambda u: bool(u.get("mfa_enabled")), verify_second_factor=check_mfa_code,
    keep_meta=(f"{PRODUCT.code}_installation", "schema_version"), license_meta=(PRODUCT.meta_id,),
    saas_mode=saas_mode, after_wipe=after_wipe))

GET /api/admin/factory-reset answers {available, password, second_factor} (SaaS: not available, the customer portal resets it); POST takes {confirm: "RESET", password, code, remove_license}. Since 0.11.0 (N21, security review 2026-10-27) the second factor is required: has_second_factor(user) and verify_second_factor(user, code) are mandatory, verify_second_factor checks a TOTP or backup code and consumes it (last step stored, backup code removed). An admin without a second factor gets second_factor_missing, a wrong code second_factor_wrong; the password is checked as well when the account has one. allowed(user) narrows who may (Foundry: platform owner), error(code) maps the codes reset_in_portal, reset_confirm_wrong, current_password_wrong, second_factor_missing, second_factor_wrong to the app's own errors. The frontend is FactoryReset from @scalio-cloud/ui (0.32.0 with the code field).

Develop and release

  • pip install -e ".[dev]", then pytest -q and ruff check . (both run in CI).
  • Release: bump pyproject.toml and __version__, open a scalio-docs PR with python3 tools/changelog.py --repo scalio-platform release <version> (changelog in scalio-docs changelog/scalio-platform/, ADR 0066), merge, then «Freigeben» in the Scalio Cockpit (workflow publish.yml: PyPI with trusted publishing, tag and GitHub release). Tags without v.
  • A change of behaviour changes every app on its next upgrade: say so in the PR and the CHANGELOG.

Protokoll: Aktionen und Jobs (ADR 0070)

from scalio_platform import audit, jobs
from scalio_platform.audit import Category

CATEGORIES = (Category("access", "Zugriff", "Access", ("user.", "role.")),
              Category("license", "Lizenz", "License", ("license.",), always=True))

await audit.record(db, "user.create", user, category="access", target=audit.target("user", u["id"], u["email"]),
                   detail=f"Konto angelegt · {u['email']}", request=request)

app.include_router(audit.audit_router(get_db=lambda: db, viewer=require_perm(CATALOG, me, get_db, "audit.view"),
                                      categories=CATEGORIES, tenant_scope=lambda u: None), prefix="/api")

# worker
REGISTRY = jobs.Registry([jobs.Job("mail_intake", "Mail-Eingang", "Mail intake", "Jede Minute", "Every minute")])
await jobs.run(db, "mail_intake", mail_intake)            # inside the loop; return None when nothing happened
await jobs.serve_requests(db, REGISTRY, HANDLERS)         # own loop every few seconds («Jetzt ausführen»)

# API
app.include_router(jobs.jobs_router(registry=REGISTRY, get_db=lambda: db, viewer=view, runner=run_perm,
                                    queue=MyQueue(), audit=log), prefix="/api")

Call audit.ensure_indexes(db) and jobs.ensure_indexes(db) at startup. Never write passwords, tokens or contents into detail, changes or a job summary (changes drops fields named like pass/secret/token/key).

E-Mail (ADR 0069 W5)

from scalio_platform import mail

KINDS = (mail.MailKind("resolved", ("Ticket erledigt", "Ticket done"),
                       ("An die meldende Person, wenn ein Ticket erledigt ist", "To the reporter when a ticket is done"),
                       subject={"de": "[{key}] Erledigt: {title}", "en": "[{key}] Done: {title}"},
                       body={"de": "Guten Tag {name}\n\n...\n\n{sender}", "en": "Hello {name}\n\n...\n\n{sender}"},
                       tokens=("key", "title", "name", "link"), sample={"key": "IT-12", "title": "Drucker", ...},
                       switchable=True),)

mailer = mail.Mailer(get_db=lambda: db, app_name="Scalio Beacon", kinds=KINDS,
                     seal=secret_box.seal, unseal=secret_box.unseal,      # the app's own encryption
                     collection="meta",                                   # default "settings"
                     override=scalio_selftest.mail.smtp_settings,         # test mailbox on test instances
                     allowed=scalio_selftest.mail.allowed, base_url=lambda: os.environ.get("APP_PUBLIC_URL", ""))
app.include_router(mailer.router(admin=require_admin, audit=log), prefix="/api")   # /api/mail/...

await mailer.send(person["email"], "resolved", {"key": t["key"], ...}, lang=person.get("language"), ref=t["id"])
await jobs.run(db, "mail_outbox", mailer.flush)        # worker, every 30 seconds; call mailer.ensure_indexes() once

Every template knows {app_name}, {app_url} and {sender} (sender name of the settings, else the app name). A template may be plain text or HTML (from the editor); in HTML the values are escaped. The built-in kind test is the test mail. send returns False when mail is off, the kind is switched off, or allowed refuses the address. check(subject, html) may refuse a mail with ValueError (Foundry: no forms, only https links); it runs on saving or testing a template and before a mail goes into the outbox.

Since 0.10.0: queue(to, subject, body, ref=...) puts a free mail into the same outbox (own subject and text, plain text or HTML, no template), for mails whose text the customer writes elsewhere (Nexus workflow steps); it returns False like send and runs check. An app with its own connection to the customer's tenant passes graph_token= (async, returns a Graph token or raises MailError): the settings then offer the provider tenant («Microsoft 365 über die Tenant-Verbindung», scalio-ui 0.31.0), which needs only the sending mailbox graph.sender and no second app secret. settings() lists the offered providers in providers.

Metadata

Release files for scalio-platform 0.11.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for scalio-platform 0.11.0
File Size Uploaded Provenance Actions
scalio_platform-0.11.0.tar.gz 67.6 kB With provenance Details for scalio_platform-0.11.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for scalio-platform 0.11.0
File Interpreter ABI Platform Actions
scalio_platform-0.11.0-py3-none-any.whl Python 3 none any Details for scalio_platform-0.11.0-py3-none-any.whl

Total release size: 117.2 kB

Release files / scalio_platform-0.11.0.tar.gz

Download URL scalio_platform-0.11.0.tar.gz
Size 67.6 kB
Tags Source
SHA-256 checksum
How to use checksums
0a60cbf932193b20074c7d8ca8b5b22a4c966fb3d511bbdb1eef935b4167ea92
BLAKE2b-256 checksum
How to use checksums
b4067df87ab38fb9b41a82c9cc991d3e348da60d19485ed69d3f901ebfbd8e78
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 2, 2026.

Transparency log

Release files / scalio_platform-0.11.0-py3-none-any.whl

Download URL scalio_platform-0.11.0-py3-none-any.whl
Size 49.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
77d5ed2e3e4934eb8e9351f4e14334f0217e9200913dea2a5a064193dd1066f4
BLAKE2b-256 checksum
How to use checksums
346907bd9463087c4944f71a8d86ac49aaf455cefcea3b21e8b051b9cae09eac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 2, 2026.

Transparency log

Security for scalio-platform 0.11.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for scalio_platform-0.11.0.tar.gz

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 Nov 2, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page