You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The gate installs the base's environment when the lock differs. A
pull request that changes uv.lock / pnpm-lock.yaml used to leave the
base worktree without a .venv / node_modules; uv run --no-sync
then created an empty venv and introspection died with ModuleNotFoundError: No module named 'django' — a tool error that
failed the whole gate. The base's environment is now built from its own
lock (uv sync --frozen --no-dev, pnpm install --frozen-lockfile,
...) inside the worktree; only when that is impossible does the base
run approximate, with a warning.
Storage moves from YAML to SQLite. Every declaration — the app, the
parties, data overrides and reviews, touchpoint declarations, stores,
activities, threat stamps, the findings register, custom vocabularies
and actors — lives in one compliance.db at the repository root; the compliance/ folders are gone and snow.yml's compliance: block only
names the discovery engine (dir is dropped). The database is tuned to
diff well (WAL, no auto-vacuum, a checkpoint on close) and init adds
its transient files to .gitignore. Cross-references (hosts, data refs,
transfers, recipients, stamps, locks) are normalised tables queried with
SQL rather than loaded and joined in Python. No migration from the YAML
layout: it is a new system.
The CODEOWNERS step of init (and its check diagnostic and flags)
is removed.
Marker subjects are <record>#<field> (parties/acme#address, activities/ordering#retention, app#description) instead of file
names.
The library takes the repository from a process-wide container set
once per command (--root); no root= / shared= arguments.
Duplicate parties are refused.party_add (and init) compare the
new party with every declared one — normalised name (case, accents,
punctuation, legal forms and a typo or an extra word ignored), shared
registrable domain or setting name of the website / hosts, respelled
id — and fail with the matching id when one looks the same. A party
that names a store of the project (Sentry, "SMTP mail server", a
host of EMAIL_HOST) is refused too: that is a store write. The
refusal is final for agents; distinct_from is set by a human only. check reports coexisting lookalikes as a party-duplicate error.
Duplicate stores are refused the same way: store_add compares the
new store with the unit's visible ones (shared host / settings name,
alike name, slug or config backend) and fails with the slug to reuse; check reports coexisting lookalikes as store-duplicate.
parties CLI: list, show, add, set, remove [--force], merge ... --into, to-store <id> <unit:slug> (a party that was
infrastructure becomes store writes), to-flow <id> (a party that was
the project itself: its transfers go, the derived call stays), distinct, duplicates.
Reach: who the code lets in, apart from who it serves. A finding's
likelihood used to come from the touchpoint's scope, so a system
webhook listing whose get_permissions() returned [] on the leader —
readable by anyone on the internet — scored info (system, 0.01)
instead of high (anonymous). Touchpoints now carry a reach
(anonymous | subject | staff | system), inferred from the auth facts
alone (no auth is anonymous whatever the scope says) and declared by the
reviewer through touchpoint_set_data(reach=). The Django introspection
reports auth_custom — DRF views overriding get_permissions, get_authenticators, dispatch… or using a hand-written authentication /
permission class — and such a touchpoint is pending until a reviewer
reads the override and declares its reach; the declaration is refused
without it. Schema v4 adds touchpoints.reach.
Severity buckets rebalanced.critical is an open buffet — anyone
(or any account onto every other account's data) takes personal data in
bulk, a confidential listing or a special-category record; high is
what takes some work, or an account, to get at what is not yours; medium is what someone could do that they should not. Concretely: the bulk degree weighs 2 (each degree doubles the last), critical starts
at 4, high at 2, medium at 1; every reviewable threat in _mapping.yaml
declares an effort (open walk in ×1, work a script or payload ×¾, chain another flaw or a victim ×½) that scales the likelihood, so a
brute force on a login or a stored XSS is high, not critical; the
ownership threats (AA03, AC01, AC07, AC12, DS05) are horizontal — a
subject who can read every other subject's rows weighs like an anonymous
caller; the oracle threats (DS01, INP18) are capped at one attribute
and fingerprinting (DS03, HA03) at existence, whatever the touchpoint
lists; escalation weighs like a disclosure of the data the touchpoint
handles (floor 2) instead of a flat 4, so an unauthenticated webhook that
creates one record is high and an unauthenticated confidential listing critical; a tampering on a touchpoint that only creates is one record; denial of service is a flat 1 whatever the data behind the
endpoint (an outage is an incident, not a breach), so an unthrottled
public endpoint is medium, not high. A touchpoint's write into an internal store of the project
(own_store_write: database, files, cache, queue, search, realtime) no
longer carries the leak cells DS06/DR01 — the response to the caller and
the mail / monitoring / external flows keep them.
Reach is derived through the front. A SvelteKit route with no auth
of its own takes the reach of the api touchpoints it calls (the
cookie is forwarded: whoever the api lets in) and of a parent +layout.server.ts that redirects unauthenticated callers to login (the
introspection reports such a load as a login guard auth fact). The api.bizneo(fetch).opId() client-factory call style is now recognised,
and DRF routes carry drf-spectacular's operation ids, so front routes
link to the DRF endpoints they proxy.
auto-review narrates each write once: the line comes from the activity
log the MCP server appends to, no longer also from the tool-call stream
(activities, parties and declarations were printed twice). Stores,
undeclared-flow reports and challenges get a line too.
A reported sink that names a store the way the party guard sees it
(sdk-sentry-sdk, "Sentry error tracker", "the mail server") resolves
to that store, so declaring the store write closes the report. Before,
the sink stayed an unresolvable transfer: party_add refused the party
(it IS the store) and the touchpoint could never leave pending.
A relative fetch("/api/x") in a SvelteKit route is linked to the
route of the project that serves it (a calls edge, like a generated
client call) and leaves the outbound fetches: the project's own
endpoints never show up as an unknown host to declare a party for. stores gains add, set, remove, merge, distinct.
Outgoing email and error monitoring are built-in stores. Every
Django unit has a mail-default store (type mail) claiming EMAIL_BACKEND / EMAIL_HOST, and an errors-sentry store (type monitoring) claiming sentry_sdk / SENTRY_DSN wherever the SDK is
installed; a send_mail / EmailMessage / email_user call, or a capture_exception, is a write to them. How mail is sent and where
events land (cloud, self-hosted) is infrastructure, so no distinction is
made between backends: no more smtp-* stores or Sentry / mail provider
parties declared by agents.
load_declarations reads the parties even when the app row is
missing, so parties list works before init.
Fix: the unit's introspection no longer inherits VIRTUAL_ENV / PYTHONPATH / uv and poetry variables from the process running
model-wtf. Under uv run model-wtf, poetry run in the unit honoured
our VIRTUAL_ENV and imported the project in model-wtf's venv
(No module named 'celery'), and uv run created a stray .venv and uv.lock in the unit.
Schema migrations: compliance.db carries its schema version and is
upgraded step by step (model_wtf.compliance.migrations) when a newer
release opens it; a file from a newer release is refused.