Metadata-Version: 2.4
Name: scalio-catalog
Version: 0.5.0
Summary: Scalio Blueprint governance catalog (scalio.catalog/v2) and Pulse app catalog (scalio.apps/v1): checks, drift, signed packs
License: Proprietary
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: cryptography>=42
Requires-Dist: jsonschema>=4.20
Requires-Dist: pyyaml>=6
Provides-Extra: source
Requires-Dist: httpx>=0.27; extra == "source"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Requires-Dist: httpx>=0.27; extra == "dev"
Requires-Dist: mongomock-motor>=0.0.29; extra == "dev"

# scalio-catalog

Shared code for the Scalio Blueprint governance catalog: one desired state per tenant, used by Foundry (roll
out), Citadel (keep, drift) and Rosetta (document). Concept and decisions BP1-BP12:
https://claude.ai/artifact/8PYfvHPm17KsooerLU95Ar (scalio-docs `concepts/governance-catalog.md`, ADR 0027).
Format contract: scalio-docs `interfaces/catalog-format.md`.

Nothing in this package is secret. The catalog content lives in the private repo `scalio_blueprint`; the signing
seed stays in 1Password.

## Documents

| Kind | What | Owner |
| --- | --- | --- |
| `ResourceType` | Something in a tenant (name as in Microsoft365DSC/UTCM, e.g. `AADConditionalAccessPolicy`): properties, read/write endpoint, permission | Scalio |
| `Control` | One security statement: match, desired values, parameters, severity, texts DE/EN, frameworks | Scalio, MSP, customer |
| `Baseline` | Versioned bundle of controls with default values | Scalio, MSP |
| `Blueprint` | Building plan of a whole environment: variables, nodes in catalog resource types with `dependsOn`, at most one baseline (ADR 0035) | Scalio (templates), customer (in Foundry) |
| `ControlObjective` | What a tenant must reach in one area (control matrix of the Rosetta audit report), with the controls that prove it | Scalio, MSP |
| `Assignment` | Tenant -> baseline@version, mode, channel, values, exceptions, own controls | customer |
| `Pack` | Header of a signed package | Scalio, MSP |

References: `id` (highest), `id@1.2.3` (exact), `id@^1.2` (same major, at least 1.2). Assignments name an exact
version.

## Use in an app

```python
from scalio_catalog import Catalog, compare, effective, plan, plan_upgrade, summary, verify

catalog = verify(package_bytes, TRUSTED_CATALOG_KEYS)        # signed pack from Compass or the image
eff = effective(catalog, assignment)                         # baseline + customer layer, master untouched
results = compare(eff, {c.id: read_resource(c) for c in eff.controls})   # app reads the tenant
print(summary(results))                                      # {"counts": ..., "score": 92}
plan = plan_upgrade(catalog, assignment, "scalio.kmu-standard@1.5.0")    # three-way, conflicts listed

rollout = plan(catalog, "scalio.project-workspace@1.0.0", {"project": "Apollo"}, tenant="contoso.ch")
for node in rollout.nodes:                                   # build order; ${node} refs via resolve_refs()
    ...
assignment = rollout.assignment                              # baseline of the blueprint, for Citadel/Rosetta
```

Packs and catalog source in an app (`pip install "scalio-catalog[source]"`): `CatalogStore` loads the packs of the
image, the catalog source and the uploads (MongoDB `catalog_source_packs`, `catalog_packs`) and keeps the merged
catalog in memory; `CatalogSource` fetches the pack of the stage from the GitHub releases of scalio_blueprint.

```python
from scalio_catalog.source import CatalogSource
from scalio_catalog.store import TRUSTED_KEYS, CatalogStore

store = CatalogStore(get_db=lambda: db, pack_dir=lambda: PACK_DIR, trusted_keys=lambda: TRUSTED_KEYS)
source = CatalogSource("citadel", get_db=lambda: db, stage=stage, trusted_keys=store.trusted_keys,
                       on_change=store.load)
await store.load(); await source.scheduled()      # worker: every source.INTERVAL_MINUTES
```

Control objectives: `evaluate_objectives(catalog, results)` gives per objective `met`, `partly`, `not_met` or
`not_checked` from the control results (`compare` output or a mapping control id -> status). `ok` and `excepted`
pass, `drift` and `missing` fail, everything else is not checked.

`compare` statuses: `ok`, `drift`, `missing`, `excepted` (all deviations covered by an active exception),
`not_read`, `removed` (customer removed it), `incomplete` (a value is missing).

Values (later wins): control default, baseline `parameters`, value on the baseline entry, assignment `values`,
assignment `controlValues.<control>`. Exceptions need a reason, end date and approver; expired ones count as
deviations again and are listed in `problems`; at most 366 days when `approvedAt` is given.

## Command line

```
scalio-catalog validate <folders...>          check structure, references, texts
scalio-catalog effective <assignment> <folders...> [--today 2026-11-01]
scalio-catalog plan <blueprint> <folders...> --set project=Apollo --tenant contoso.ch
scalio-catalog pack <folder> -o dist          sign (CATALOG_SIGNING_KEY or --key-file), --base for other packs
scalio-catalog verify <file> --key <public>   check a package
scalio-catalog keygen                         new key: seed to 1Password, public key into the apps
scalio-catalog schema > catalog-v2.schema.json
```

## App catalog of Scalio Pulse (`scalio.apps/v1`)

Recipes for third-party apps (contract: scalio-docs `interfaces/apps-format.md`). Scalio ships recipes, never
installers: the Pulse instance downloads the installer from the vendor, checks the SHA-256 and builds the
`.intunewin` itself. The recipes live in the private repo `scalio-pulse-catalog`.

```python
from scalio_catalog import verify_apps, is_newer

apps = verify_apps(package_bytes, TRUSTED_APPS_KEYS)        # signed .scalio-apps package
v = apps.latest("7zip.7zip")                                 # newest version: installer, install, detection ...
app = apps.find("7-Zip 24.08 (x64 edition)")                 # match a detected app of Intune
outdated = is_newer(apps.latest(app["id"])["version"], "24.08")
```

```
scalio-catalog apps winget 7zip.7zip [24.09]  draft a recipe from winget-pkgs (review: German text, TODO places)
scalio-catalog apps validate <folders...>
scalio-catalog apps pack <folder> -o dist     sign (APPS_SIGNING_KEY or --key-file)
scalio-catalog apps verify <file> --key <public>
scalio-catalog apps schema > apps-v1.schema.json
```

## Editing in VS Code

Install the YAML extension (Red Hat) and add to the workspace settings of `scalio_blueprint`:

```json
{ "yaml.schemas": { "./schema/catalog-v2.schema.json": ["controls/**/*.yaml", "baselines/**/*.yaml",
  "resource-types/**/*.yaml", "blueprints/**/*.yaml", "objectives/**/*.yaml", "pack.yaml"] } }
```

(`scalio-catalog schema > schema/catalog-v2.schema.json` writes the file.)

## Release

Bump `pyproject.toml` and `__version__`, CHANGELOG line, merge, then "Freigeben" in the Scalio Dev cockpit (or
Actions -> Publish with the version). Publishes to PyPI via trusted publishing (no token).
