Audit Trail on Tawa
import { Septor } from '@insureco/septor'
Ruleconfigure
About
import { Septor } from '@insureco/septor'
Skill Content
This is the raw markdown that gets installed as a Claude Code rule.
# Audit Trail on Tawa
## The Short Version
```typescript
import { Septor } from '@insureco/septor'
const septor = new Septor({
apiUrl: process.env.SEPTOR_URL,
namespace: process.env.SERVICE_NAME || 'my-service',
})
// Fire-and-forget — NEVER await septor on the critical path
septor.emit('payment.created', {
entityId: orgSlug,
data: { amount, referenceId: paymentId },
metadata: { who: userId },
}).catch((err) => logger.error({ err }, 'Septor emit failed — audit event lost'))
```
## Setup
`SEPTOR_URL` is auto-injected by the builder for every deployment, but it is a
**direct** in-cluster URL and Septor's NetworkPolicy only admits namespaces that
carry the `tawa.pro/direct-dep.septor=true` label. The builder applies that label
from your declared dependencies, so you **MUST declare septor in
`internalDependencies`**:
```yaml
spec:
internalDependencies:
- service: septor
```
Without this, the namespace is never labeled, every Septor call is dropped by the
NetworkPolicy and hangs until it times out (`connect ETIMEDOUT`), and the audit
trail is silently lost (emits are fire-and-forget). The builder may warn
"septor is auto-injected — remove it"; **ignore that warning and keep the
declaration** — it is what applies the NetworkPolicy label.
## Authentication (REQUIRED — SDK ≥ 1.4.0)
Septor verifies a **Bio-ID token** on every emit/query. The SDK (≥ 1.4.0) handles
this automatically: when no `apiKey` is passed, it mints a `client_credentials`
token from `BIO_CLIENT_ID` / `BIO_CLIENT_SECRET` / `BIO_ID_URL` and refreshes it
ahead of expiry. **No per-app token code is needed.**
**This means your service MUST have Bio-ID credentials.** Declare `spec.auth` in
`catalog-info.yaml` so the builder injects `BIO_CLIENT_ID` / `BIO_CLIENT_SECRET`:
```yaml
spec:
auth:
mode: sso # or: service-only (no user login, creds still injected)
```
Without `spec.auth`, those vars are absent and **every Septor emit fails with
`Authorization header required`** (the audit trail is silently lost since emits
are fire-and-forget). See https://tawa.insureco.io/reference/convention-septor
> Pin `@insureco/septor` to `^1.4.0`. Earlier versions do not authenticate and
> will be rejected by Septor.
## Fire-and-Forget (REQUIRED)
A Septor outage must NEVER break your service. Always fire-and-forget:
```typescript
// ✅ CORRECT
septor.emit('payment.created', { ... })
.catch((err) => logger.error({ err }, 'Septor emit failed — audit event lost'))
// ❌ WRONG: blocking your user on an audit write
await septor.emit('payment.created', { ... })
```
## What MUST Be Septor-Wired
| Category | Required Events |
|----------|----------------|
| Payments | `payment.created`, `payment.completed`, `payment.failed`, `payment.refunded` |
| Policies | `policy.bound`, `policy.endorsed`, `policy.cancelled`, `policy.renewed` |
| Compliance | `ofac.cleared`, `ofac.flagged`, `kyc.verified`, `kyc.failed` |
| Authorization | `user.login`, `permission.granted`, `permission.revoked` |
| Data Access | `record.accessed`, `report.exported`, `data.modified` |
**Automatically written by the platform (no code needed):**
- Deploy events (builder writes these)
- Gas events (Janus writes these)
- Job/cron events (iec-queue/iec-cron write these)
- Credential rotations (builder writes these)
## Event Naming
Use `{resource}.{action}` dot notation, lowercase:
```
payment.created ✅
policy.bound ✅
ofac.screen.cleared ✅ (nested resource.sub.action is OK)
payment-created ❌ (hyphens — use dots)
createPayment ❌ (camelCase)
PAYMENT_CREATED ❌ (uppercase)
```
## Querying the Audit Trail
```typescript
const { data } = await septor.query({
entityId: orgSlug,
eventType: 'payment.created', // optional filter
from: '2024-01-01',
to: '2024-01-31',
limit: '100',
})
for (const event of data.events) {
console.log(event.eventType, event.createdAt, event.metadata.who)
}
```
## Verifying Chain Integrity
```typescript
// Run in background jobs, not request handlers — this is O(n)
const { data } = await septor.verify(orgSlug)
if (!data.valid) {
logger.error({ brokenAt: data.brokenAt }, 'Audit chain broken!')
}
```
## Key Facts
- Each event gets a cryptographic hash linked to the previous — tamper-evident chain
- `entityId` is the primary index — use a stable identifier (`orgSlug` or `userId`)
- Events are immutable — once written, cannot be modified or deleted
- Each service sees only its own events (namespace scoping)
- Missing `.catch()` is a compliance gap — failures must be logged
## What NOT to Do
```typescript
// ❌ WRONG: blocking on audit write
await septor.emit(...)
// ❌ WRONG: silent failure
septor.emit(...).catch(() => {}) // swallows the error — compliance gap
// ❌ WRONG: vague event type
septor.emit('event.happened', ...)
// ✅ CORRECT
septor.emit('payment.refunded', {
entityId: orgSlug,
data: { amount, reason, refundId },
metadata: { who: userId, why: 'Customer requested refund' },
}).catch((err) => logger.error({ err }, 'Septor emit failed — audit event lost'))
```
Install
Copy the skill content and save it to:
~/.claude/rules/tawa-septor-audit.mdComing soon via CLI:
tawa chaac install tawa-septor-auditDetails
- Format
- Rule
- Category
- configure
- Version
- 1.0.92990
- Tokens
- ~1,274
- Updated
- 2026-06-24
platformauditcomplianceseptorhash-chainimmutable