For Coding Agents¶
Reference for AI agents that work on iscc-hub or call a hub. Tables and rules first. The code is the source of truth; the IEPs define the protocol.
Architecture map¶
Django 6.1 + django-ninja, Python 3.12+. One process type: Gunicorn sync workers. SQLite (WAL) by default, PostgreSQL optional.
| File | Contents |
|---|---|
iscc_hub/api.py |
Ninja endpoints: /declaration, receipt, delete, /lookup, /search, /health, DID document, JSON resolve |
iscc_hub/validators.py |
All write-path validation of IsccNote and IsccNoteDelete. Pure, no I/O |
iscc_hub/identity.py |
DID verification: fetches and checks the did:web document of the controller (SSRF-guarded, cached per worker) |
iscc_hub/sequencer.py |
Single-writer transaction: ISCC-ID, log record and view row in one commit |
iscc_hub/iscc_id.py |
IsccID: 52-bit µs timestamp + 12-bit hub ID; header from ISCC_HUB_REALM |
iscc_hub/receipt.py |
Builds the IsccReceipt (W3C VC) signed with ISCC_HUB_SECKEY |
iscc_hub/merkle.py |
RFC 6962 / tlog-tiles primitives. Pure |
iscc_hub/checkpoint_note.py |
C2SP signed-note checkpoint codec. Pure, byte-exact with the Go reference |
iscc_hub/log_tree.py |
Live tree over LogRecord, checkpoint cache |
iscc_hub/log_api.py |
/log/checkpoint, /log/tile/... plain Django views |
iscc_hub/search_proxy.py |
/search proxy to iscc-search servers: failover, circuit breaker |
iscc_hub/rerank.py |
Tiered Semantic Ranking of search results |
iscc_hub/middleware.py |
Routes a request to the API, log or HTML URL configuration |
iscc_hub/views.py |
HTML pages: home page, resolve, health |
iscc_hub/admin.py |
Admin: redaction flag, public keys. No add, change or delete of declarations |
iscc_hub/tasks.py |
Hub list download from GitHub (hubs/testnet.yaml, hubs/mainnet.yaml on main) |
iscc_hub/models.py |
User, Hub, PubKey, IsccDeclaration (view), LogState, LogRecord (log) |
iscc_hub/fields.py |
IsccIDField (8-byte binary), HexField, PubkeyField |
iscc_hub/schema.py |
Generated from iscc_hub/static/openapi.yaml. Never edit by hand |
iscc_hub/settings.py |
All environment variables (see Configuration) |
scripts/entrypoint.sh |
Container start: migrate, admin user, hub list sync, background loops, Gunicorn |
hubs/*.yaml |
Hub lists. Hubs download them from the main branch |
specs/ |
Older protocol drafts. The IEPs are authoritative |
Request flow for POST /declaration: middleware → api.declaration → validators.validate_iscc_note → permission
check (PubKey) → identity (DID verification) → sequencer.sequence_iscc_note → 201 {iscc_id, seq}.
Decision dispatch¶
| Task | Where |
|---|---|
| Add or change a validation rule | validators.py, plus tests in tests/test_validators.py or tests/test_write_hardening.py |
| Change an API request or response | iscc_hub/static/openapi.yaml and static/schemas/*.yaml, then uv run poe build-schema |
| Add a setting | settings.py with a default, .env.example, docs/configuration.md |
| Change an HTML page | iscc_hub/templates/, views.py |
| Change the log or checkpoint format | Stop. Load-bearing. Discuss first |
| Change the hub list | hubs/*.yaml on main |
Constraints and invariants¶
| Invariant | Rule |
|---|---|
| Atomic issuance | ISCC-ID, LogRecord and IsccDeclaration row are written in one transaction. Never split it |
| Gapless sequence | LogRecord.index starts at 0 and has no gaps |
| Monotonic timestamps | Each ISCC-ID of a hub has a strictly greater timestamp |
| Single writer | SQLite BEGIN IMMEDIATE (transaction_mode=IMMEDIATE); PostgreSQL row lock on LogState |
| Append-only log | Nothing updates or deletes a LogRecord. Deletion appends a deletion record |
| Log entry envelope | {"$schema", "iscc_id", "note"}, JCS-canonical, framed [u16 BE length][record] in tlog-tiles bundles |
| Signature scope | JCS (RFC 8785) of the note without signature.proof. Byte-exact |
IsccDeclaration |
A view of current state, rebuilt from the log. Admin saves change only redacted |
| Nonce | 32 hex chars; first 12 bits = ISCC_HUB_ID; unique per hub |
| Request size | Body at most 8192 bytes |
| ISCC-UNITs | Each passes iscc_core.iscc_validate(strict=True) and has a 256-bit body |
| ISCC-ID on deletion | Must be the canonical string for the realm of the hub |
| Fixed per hub | ISCC_HUB_ID, ISCC_HUB_REALM, ISCC_HUB_SECKEY, ISCC_HUB_DOMAIN |
Load-bearing (never change without discussion): entry bundle framing, log entry envelope, checkpoint format, RFC 6962
hashing and height-8 tiles, /log/ paths, ISCC-ID encoding, signature scope and JCS.
Side effects¶
| Call | Writes |
|---|---|
POST /declaration |
One LogRecord (declaration), one IsccDeclaration, LogState update |
DELETE /declaration/{iscc_id} |
One LogRecord (deletion), removes the IsccDeclaration row |
| Admin redact or unredact | IsccDeclaration.redacted only |
GET /log/checkpoint |
May rebuild the cached checkpoint |
| DID verification | Outbound HTTPS GET of https://<domain>/.well-known/did.json; per-worker cache |
sync_hubs command |
Creates or updates Hub rows from the hub list; never deletes rows |
Task recipes¶
uv sync # install
uv run poe all # format, lint, types, tests (100% coverage required)
uv run pytest tests/test_x.py --no-cov # one test file
uv run pytest -m slow # slow tests (Go conformance, fuzzing)
uv run poe build-schema # regenerate schema.py from openapi.yaml
uv run poe docs-build && uv run poe docs-check # build and check this site
Tests that touch the sequencer or the API use @pytest.mark.django_db(transaction=True). Without transaction=True,
pytest-django wraps the test in a transaction and BEGIN IMMEDIATE fails.
Sign a note in a client:
import iscc_crypto as icr
key = icr.key_from_secret(secret, controller="did:web:example.com")
note = {
"$schema": "http://purl.org/iscc/schema/iscc-note-0.8.0.json",
"iscc_code": code,
"datahash": datahash,
"nonce": icr.create_nonce(hub_id),
"units": units,
}
signed = icr.sign_json(note, key) # POST as JSON to /declaration
A full, tested client example is on Use the API.
Change playbook¶
| If you change | Also update |
|---|---|
openapi.yaml or static/schemas/ |
uv run poe build-schema; docs/use-the-api.md |
| A setting or its default | .env.example, docs/configuration.md, this page |
| A validation rule | Tests; docs/use-the-api.md rules table; replay the public logs to check that real clients still pass |
scripts/entrypoint.sh |
docs/run-a-hub.md start steps, docs/configuration.md background jobs |
| A dependency floor | pyproject.toml, then uv lock; CI uses uv sync --locked |
| The version | pyproject.toml and iscc_hub/__init__.py; image tags in docs/run-a-hub.md |
Common mistakes¶
NEVER edit iscc_hub/schema.py. ALWAYS change openapi.yaml and run uv run poe build-schema.
NEVER call obj.save() on an IsccDeclaration from code that can run during a deletion. A stale save inserts the
row again. ALWAYS use filter(pk=...).update(...).
NEVER use @pytest.mark.django_db without transaction=True for sequencer or API tests.
NEVER write tests with mocks for crypto. ALWAYS use real Ed25519 keys and the fixtures in tests/conftest.py.
NEVER treat specs/ as authoritative. ALWAYS check the IEPs and the code.
NEVER send a resolve request without Accept: application/json if you want JSON. Without it, GET /{iscc_id}
returns HTML or a redirect.