Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules/
dist/
.DS_Store
*.log
.env
.env.local

# Internal specifications — contain thresholds, weights, heuristics, and
# anti-abuse thresholds that must not be public to prevent reverse-engineering
# by bad actors. Team access is managed outside of git.
.kiro/internal/
64 changes: 64 additions & 0 deletions .kiro/steering/abuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
inclusion: always
---

# TruthLayer — Abuse-Resistance Invariants (Internal-Safe Summary)

Concrete detection rules, thresholds, and network-topology heuristics live in
`.kiro/internal/abuse-defenses.md`. This file lists only the invariants that
must be respected by any code that touches claims, scoring, or the identity
graph.

## Defense in depth

- Every attack family must be countered by at least two independent signals.
A code change that collapses two signals into one, or that removes an
independent layer, is rejected in review.
- No single heuristic is load-bearing. If one rule is bypassed, at least one
other rule must still block.

## Determinism where the attacker sees, non-determinism where they cannot

- The mapping from evidence to tier is deterministic and versioned.
- The cadence of re-checks, cohort recomputation, and decay is not fixed. Do
not hard-code verification timestamps or jobs that run on predictable
minutes. Use jittered schedulers.

## Rank, not raw

- Publicly-displayed confidence is always a cohort-relative rank label
("top quintile"), never a percentage or decimal. Partner endpoints may
expose raw values under contract only.

## Decay over punish

- Compromise and suspected gaming are handled by score decay, not by bans.
- No code path emits an error message that identifies which rule triggered
a decay. Error strings are category-level, not rule-level.

## Cooldowns on structural mutations

- Detaching and re-attaching wallets, changing primary wallet, changing
labels, and similar actions take effect after a bounded internal delay.
The delay value is internal; code must read it from config, not hard-code.

## Refuse over approximate

- If any defense layer reports low confidence, the system returns
`insufficient data`. It does not return a best-effort number with a caveat.

## Claim flow invariants

- A-tier promotion from a fresh claim requires at least one out-of-band
confirmation in addition to the signature. The channel and trigger are
internal.
- Claims for smart-contract wallets must be re-verified at a later block
height before producing a public-facing badge.
- Rate limits on the claim endpoint are enforced at the IP and fingerprint
level and are read from config.

## Public-doc rule

- `docs/abuse-resistance.md` describes *what* the product defends against and
the principles. It must not describe any specific rule, threshold, or
channel. Review rejects PRs that leak specifics into it.
80 changes: 80 additions & 0 deletions .kiro/steering/claim-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
inclusion: always
---

# TruthLayer — Claim Flow and Session Invariants

Concrete windows, cooldowns, and rate-limit values live in
`.kiro/internal/abuse-defenses.md` and `.kiro/internal/identity-graph.md`.
Code reads them from config; it does not hard-code values.

## Canonical message

- The signed message is constructed ONLY via `buildClaimMessage` from
`@truthlayer/shared`. Clients must not template the message locally.
- The server builds the message when issuing a nonce, stores it, and
requires an exact equality check on submit. Any drift — extra
whitespace, reordered fields, different ISO timestamp format — is a
rejected claim.
- Changes to the message format require bumping `SCORING_VERSION` and
coordinating an extension release. Old extensions must keep working
for a bounded deprecation window.

## Nonce lifecycle

- Nonces are single-use, bounded-TTL, and scoped to the triple
`(handle, chain, address)`.
- Issuing a fresh nonce for a triple that already has a live nonce
invalidates the older nonce. Only the latest is valid.
- A submit that consumes a nonce marks it used atomically; replays are
rejected with the same category-level error as any other bad claim.

## Signature verification

- EVM chains: EIP-191 `personal_sign`, signature recovers to the
declared address (lowercased comparison).
- Solana: Ed25519 over UTF-8 bytes of the message; signature encoded as
base58.
- Smart-contract wallets (non-65-byte EVM signatures) enter a separate
delayed-challenge path. They do not get A-tier on first submit.

## Session model

- Extension obtains an anonymous JWT on install and on startup. No user
action required. Refreshed when less than one day remains.
- JWT is stored in `chrome.storage.local` under `tl_session`.
- Every request from the background worker attaches
`Authorization: Bearer <token>`. The content script never touches the
token.
- Web `/claim` page keeps the token in `localStorage` under the same
key. When a user claims on the web, the token upgrades from anonymous
to account-linked; no new JWT is minted just for that transition (the
same `sub` remains, the `plan` shifts on the server side).

## Error responses

- Claim failures return HTTP 403 with a category-level message
("rejected", "unauthenticated"). The server MUST NOT reveal which
individual check failed; defense-in-depth implies silence per
`.kiro/steering/abuse.md`.
- The web `/claim` page shows a neutral toast and keeps the user on the
step; no retry hints that would let an attacker iterate.

## What never leaks via this flow

- No nonces observable to parties other than the requesting session.
- No enumeration of existing handles. A request for a nonce against a
handle the user does not own proceeds normally; only the submit step
checks handle ownership via Twitter session.
- No PII. The server only stores (handle, chain, address, tier, source,
timestamps, proof reference). Real-world names, emails, IPs are not
joined to the identity graph.

## Never-merge rules

- Do not accept signatures built against a client-templated message.
Always echo back the server's `message` field.
- Do not expose nonce TTL, replay window, or rate-limit values in error
responses or logs. Those are internal.
- Do not upgrade a session from anonymous to account-linked without a
successful signature verification. No "trust me" paths.
56 changes: 33 additions & 23 deletions .kiro/steering/identity-graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,54 @@
inclusion: always
---

# TruthLayer — Identity Graph Rules
# TruthLayer — Identity Graph Rules (Internal-Safe Summary)

When working on anything related to linking social handles to on-chain addresses, follow these invariants.
Concrete thresholds live in `.kiro/internal/identity-graph.md` (not in the
public repository). Code must read values from that spec, not hard-code them.

## Trust tiers

Always present one of four tiers in any UI or API response:
Always surface one of four tiers: A, B, B+, C. A is the strongest. The
rendering rules are:

- **A (verified)** — wallet-signed message + Twitter OAuth session match, OR ENS/SNS equivalent.
- **B (public proof)** — KOL themselves posted a tx link / address screenshot / portfolio link.
- **B+ (likely, N%)** — context-match from NLP + on-chain co-activity; always display the confidence percentage.
- **C (unverified)** — community-submitted, no proof yet; always de-emphasized in UI.

Never present a lower tier as if it were a higher tier. Context-match (B+) is never silently promoted to A.
- **A, B, B+** are shown in their respective styles; B+ additionally carries
a cohort-relative rank label.
- **C** is always de-emphasized and is never used as a basis for metrics.
- Never silently upgrade a lower tier to a higher one in the response.

## Growth order (strict)

Features should be built in this order; do not skip ahead without explicit approval:
Do not reorder without explicit approval.

1. Seed curated list (`data/kol-seed.json`).
2. Public-proof scraper (tx links + ENS/SNS in bios).
3. **Self-onboarding for ANY user** (SIWE / SIWS + X OAuth). This is the mass-scale hook — the product must not be limited to influencers.
4. Context-match model for B+ inference.
5. Community submissions with upvotes.
1. Seed — hand-curated list.
2. Public-proof indexing.
3. Self-onboarding (the mass-scale hook). The product must never be limited
to curated influencers.
4. Context-match inference for B+.
5. Community submissions.

## Data model invariants

- `(identity_id, wallet_id)` is unique in the `links` table.
- `confidence` is required for B+ links (0..1), null for A and B.
- Every link must record a `source` and a `proof_url` (or proof blob).
- Addresses are always stored lowercase for EVM chains, base58 as-is for Solana/TON.
- `(identity_id, wallet_id)` is unique in `links`.
- `confidence` is stored for B+, null for A and B.
- Every link records a `source`. Proof material is stored in a separate
store and referenced by ID, not inlined.
- EVM addresses stored lowercase; Solana and TON in native form.

## Privacy

- Never link a wallet to a real-world identity. Only to a platform handle.
- Do not attempt to de-anonymize anons who have not self-revealed.
- Self-onboarding is fully opt-in. A user can detach a link at any time; detachment removes it from future overlay responses within 1 hour.
- Detachment propagates into the overlay within the bounded window specified
internally.
- Self-onboarding is opt-in; bulk imports of third parties are not permitted
into any tier above C.

## Chains in scope

Ethereum, Solana, Base, Arbitrum, BNB Chain, TON. New chains require a
dedicated address-format validator and RPC provider configuration.

## Chains in scope (ordered by priority)
## Public-doc rule

Ethereum, Solana, Base, Arbitrum, BNB Chain, TON. Each new chain requires its own address-format validator and RPC provider in config.
- `docs/identity-graph.md` must not specify thresholds, windows, or the
content of signal calculations. Any PR that leaks specifics is rejected.
70 changes: 44 additions & 26 deletions .kiro/steering/monetization.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,64 @@
inclusion: always
---

# TruthLayer — Monetization & Auth Rules
# TruthLayer — Monetization & Auth Rules (Internal-Safe Summary)

Product is two-sided: freemium consumer extension + metered B2B API. Both share one identity service and one identity graph.
Concrete per-plan numeric ceilings (daily query budgets, overage rates,
billing cadences) live in `.kiro/internal/monetization.md`. Code reads from
config populated from that spec; it does not hard-code per-plan numbers.

## Consumer tiers (do not ship features to the wrong tier)
## Consumer plans

- **Anonymous** (default after install, no login): 100 lookups/day, overlay only.
- **Free account** (X OAuth or SIWE): 500 lookups/day, cloud-synced watchlists.
- **Pro** ($15/mo, Stripe): unlimited, custom alerts, Co-Signer rules, full B+ breakdown, export.
- **Anonymous** (default after install, no login): overlay only, bounded
daily query limit.
- **Free account** (X OAuth or wallet signature): higher daily limit, cloud
preferences, ability to submit community links.
- **Pro** ($15/mo via Stripe): unlimited queries, custom alerts, transaction
co-signer rules, priority support, data export.

## Partner tiers
## Partner plans

- **Startup** ($99/mo, 10k req/day)
- **Growth** ($499/mo, 100k req/day, webhooks)
- **Enterprise** (custom SLA, white-label)
- **Startup** ($99/mo): bounded daily request volume.
- **Growth** ($499/mo): higher volume, webhook alerts, richer surfaces.
- **Enterprise** (custom): SLA, white-label, access to raw signal values
under NDA.

## Auth invariants

- One identity service, one JWT issuer. Scopes differ per tier.
- Extension must work without login. Never show a login wall before first use.
- Consumer free-account onboarding = identity-graph A-tier claim. Do not build two separate flows.
- Partner dashboard is separate (`partners.truthlayer.app`), email/password + 2FA.
- API keys are hashed at rest; scopes are explicit per key.
- Single identity service; one JWT issuer; scopes differ per tier.
- Extension must work without login. No login wall before first overlay
render.
- Consumer free-account creation IS the A-tier claim flow. Do not build two
separate flows.
- Partner dashboard is separate; email and password with mandatory 2FA.
- API keys are hashed at rest; scopes are explicit per key; keys rotate on
the internal cadence.

## Billing invariants

- Stripe is the only billing provider in v1.
- USD only. No token payments in v1.
- Metered usage batched hourly into Stripe Usage Records.
- Every usage event recorded with `(account_id, endpoint, at, cost_units)` for audit.
- Stripe is the only billing provider in v1. USD only.
- Metered usage batches into Stripe Usage Records on the internal cadence.
- `usage_events` table is append-only. Rate-limit and billing are derived
from it.

## Privacy invariants

- Anonymous mode stores only hashed device fingerprint + rate-limit counter, no PII.
- Wallet detach removes identity-graph link within 1 hour.
- Raw API logs retained max 90 days; aggregates anonymized after 30 days.
- Anonymous mode stores hashed device fingerprint and rate-limit counters
only. No PII.
- Wallet detach removes the identity-graph link within the bounded internal
window.
- Raw API logs are retained for the internal retention period. Aggregates
are anonymized earlier.

## Public-doc rule

- `docs/monetization-and-auth.md` lists prices but not per-plan query
ceilings, rate-limit windows, or overage rates. Those are in the internal
spec.

## Do not

- Do not introduce a project token in v1. Not on the critical path.
- Do not ship affiliate/referral program in v1.
- Do not require account creation to read the overlay. Friction kills the viral loop.
- Do not present B+ (context-match) tier in B2B API responses without the confidence score.
- Do not introduce a project token in v1.
- Do not ship an affiliate program in v1.
- Do not require account creation to read the overlay.
- Do not publish the anti-abuse rate-limit values in any form.
64 changes: 64 additions & 0 deletions .kiro/steering/operations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
inclusion: always
---

# TruthLayer — Operations and Bug Triage (Internal-Safe Summary)

## Logging and correlation

- Every request carries a correlation ID end-to-end (`request_id`, propagated
in headers and logs).
- Logs are structured JSON. Free-text log lines are rejected in review.
- No PII in logs. Wallet addresses are allowed; email, device identifiers,
and raw signatures are not.

## Error tracking

- Any unhandled error, server 5xx, or failed Shadow DOM render emits a
structured event with correlation ID, scoring version, and a minimal
diagnostic bundle.
- Error messages visible to users are category-level. They do not identify
which defense layer fired or which source failed.

## User-reported bugs

- The extension popup exposes a one-click "Report issue" action.
- The report bundle contains: recent overlay responses (last few), current
scoring version, user agent, anonymized session ID.
- The bundle never contains: wallet private-key material, full session
tokens, real name, email unless the user explicitly provides it.

## Triage SLAs

- **P0** — security or data integrity issue (wrong tier, accepted invalid
signature, authorized mutation without consent): one business day.
Public blameless postmortem after resolution, with internal defense
specifics redacted.
- **P1** — scoring visibly wrong on high-traffic content: a few business
days.
- **P2** — UI glitch or non-data-bearing bug: next scheduled release.
- **P3** — nice to have: backlog.

## Graceful degradation

- Upstream data provider unavailable, cache miss, or worker pool exhausted:
the API returns `insufficient data` for the affected metric. It never
returns an invented value or a stale value labelled as fresh.

## Deployment

- Extension releases are versioned. Scoring responses carry the scoring
version separately from the release version. Clients key caches by the
scoring version.
- Database migrations are forward-only. Rollback is done by forward-migrate
with a compensating change, never by restoring an old binary to a new
schema.

## Observability minimums

- Latency percentiles (p50, p95, p99) per endpoint.
- Cache hit ratio per endpoint.
- External provider error rate per provider.
- Claim success / failure counts, broken down by failure category.
- Alert thresholds are defined in `.kiro/internal/operations.md` and are not
published.
Loading