A source without a scope rule could create any item type, empty, under
its name (Shikigami could declare a host). Fields don't settle it: some
types have none. So whole-system sources get a type list, written as
each one is built (D51); Shikigami's is app, route and bot. Sources
without a list aren't limited yet - none reports anything so far.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MdS1HcT3JpVno881Z46fei
The build job (Meru/sindri 0.2.0: signer account, OpenBao-only token,
in-memory /keys) logs in as meru-thoth-build, writes the two keys to
/keys, revokes its token; sign reads /keys. Same as Meru/echo's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MdS1Hfahkh27nfbtRVZ4CR
deploy/values.yaml for nuwa-app 0.2.0: readiness on /ready, the app as
thoth_app and migrations as the owner (both from CNPG's Secrets via
PGUSER/PGPASSWORD - no password in any URL), Authentik settings from
D57. Starts on the signed image of 78839e5. Main builds now write their
digest here, as Echo's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MdS1Hfahkh27nfbtRVZ4CR
The running app logs in as its own role (D58): SELECT/INSERT/UPDATE on
the live tables, SELECT on history and the migration record. It owns
nothing, so it can't change the schema or switch off a trigger; it has no
DELETE (only Charon's purge deletes). The change-tracking function now
runs as its owner (SECURITY DEFINER, fixed search_path), so it alone
writes history - the app can't forge a version.
Grants are alembic_utils entities (src/thoth/sql/grants.py), columns
from the models, so autogenerate keeps them in step. tests/test_roles.py
holds its own copy of the matrix (a new table fails until it's decided)
and proves the refusals: history insert, delete, trigger switch-off,
drop, function replace, create table.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MdS1Hfahkh27nfbtRVZ4CR
- .gitea/workflows/ci.yaml: `checks` (the whole of scripts/check.sh in a
credential-free pod, Postgres 16.15 as a service container) then
`build` (rootless BuildKit; main also pushes to Zot and signs).
- scripts/tools.py: vacuum and oasdiff as sha256-pinned release binaries,
the same locally and in CI (Sindri has no podman); check.sh uses them.
- tests fail instead of skipping in CI when there's no database.
- Dockerfile: dependencies layer first, the app non-editable, migrations
+ alembic config in the image, uid 10001, no .pyc (read-only root).
- test-db.sh pins Postgres 16.15 by digest (what CNPG runs).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MdS1Hfahkh27nfbtRVZ4CR
Tokens checked per the security standard (issuer, audience, algorithm,
JWKS key, expiry/not-before, required claims); permissions thoth.read,
thoth.report:<source>@<scope> (must match the scan's path), thoth.manual,
thoth.verdict. Changes are recorded under the bot's name. Retiring now needs
the verdict flag the API sets only for thoth.verdict, checked by the
trigger. House rule: every operation but the probes declares security.
API-Break: every route but the probes now needs a token - nothing has called Thoth yet
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MdS1Hfahkh27nfbtRVZ4CR
GET /v1/items/{id}/history lists every version newest first with its
validity window, who and why; GET /v1/items?as_of= returns the inventory as
it was at that moment from the current rows and the history tables.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MdS1Hfahkh27nfbtRVZ4CR
GET /v1/items/{id}/web returns impact and dependencies together (or one
side with ?direction=), cycles from both sides listed once each.
API-Break: /impact and /dependencies merge into /web - nothing has called Thoth yet
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MdS1Hfahkh27nfbtRVZ4CR
GET /v1/items (filters, paged) and /v1/items/{id}; /impact and
/dependencies walk the one-hop links in one recursive query: shortest
distance per item with the link it came by, loops reported as cycles
and not followed, retired links skipped, depth 1-50.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MdS1Hf1rZwApuTroJVE19S
POST /v1/renames: same item, relations and slices, new key; the trigger
keeps the old key in history. Only the first reporter or manual may rename;
refused for unknown, retired or taken keys.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MdS1Hf1rZwApuTroJVE19S
Scans split into items then relations (D54): PUT
/v1/scans/{scan_id}/{source}/{scope}/items and .../relations. Relation
observations per source and scope with the same strict slices, misses and
Anubis queue; a link is in scope if its from end is; links to items that
don't exist yet are skipped and heal on the next scan. Misses count once
per scan id, so resent snapshots change nothing. Devices and attached-to
wait 30 days unseen instead of 3 scans.
API-Break: /v1/snapshots/{source}/{scope} becomes /v1/scans/... - nothing has called Thoth yet
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MdS1Hf1rZwApuTroJVE19S
PUT /v1/snapshots/{source}/{scope}: strict slices (a missing owned field
refuses the whole snapshot), scope rules for host-scan and k8s-scan,
precedence merge across sources and scopes, miss counting with Anubis's
list at 3, a snapshot_run log for Zabbix. The history trigger refuses
status retired unless the actor is anubis.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MdS1Hf1rZwApuTroJVE19S
17 item and 14 relation types as Pydantic classes with per-field owners in
precedence order; slices, merge, catalog.json with a breaking-change check
(Catalog-Break trailer); /ready checks every stored row against the catalog
before syncing it. Client routes under /v1.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MdS1Hf1rZwApuTroJVE19S
Items, relations, per-source observations and the type catalog as SQLModel
tables; history, actor/reason stamping, append-only history and the delete
guard as Postgres triggers managed by alembic_utils. FastAPI skeleton with
RFC 9457 problem details, vacuum house rules and oasdiff breaking checks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MdS1Hf1rZwApuTroJVE19S