Attributes are written by whoever creates the entity. Anyone can create an entity with project = arkiv-sql-indexer-demo, so a dashboard that trusts that attribute counts forged rows as its own. User attributes alone are not an identity boundary.
The usual answer is a hosted indexer or a subgraph, which asks every user to trust whoever runs it, and which typically keeps rows after the data was meant to expire.
README.md, SPECS.mdWhat changes from day one
- Rows are scoped by $creator. Set
INDEX_EXPRESSION to $creator = addr(…) and only that wallet's entities are indexed, including tickets later handed to another owner. Startup refuses to reuse a store built for a different expression.README.md, packages/core/src/query.ts, apps/indexer/src/store.ts - Any row can be checked against the chain. Verify re-reads key, updated_at, expires_at and owner from the public RPC and labels the row ok, stale, extended, expired or deleted. It is a consistency check against an independent RPC, not a cryptographic proof.apps/web/lib/verify.ts, apps/web/components/dash/ResultsGrid.tsx
- SQL runs in your tab. There is no server SQL endpoint. No API key reaches the browser, and by default the indexer never sends one either.apps/indexer/src/main.ts, apps/indexer/test/keyless.test.ts
- Expired rows leave every layer. Snapshots, the indexer store and the browser views all drop entities past their expiry block.apps/indexer/src/snapshot.ts, apps/indexer/src/store.ts, packages/core/src/views.ts
Build on it without asking
The data contract is two SQL views, entities and attributes, documented column by column in arkiv/schema.md. Snapshots are plain Parquet files listed in /data/manifest.json, readable by any Parquet tool. The code is MIT licensed, and the whole stack deploys with Docker Compose.
arkiv/schema.md, apps/web/lib/disk-data.ts, LICENSE, docker-compose.yml