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
**Declare nothing.** `SEPTOR_URL` is auto-injected on every deploy, and since
iec-builder [`9e0a2d0`](https://git.tawa.pro) (2026-06-22) the builder also applies the
`tawa.pro/direct-dep.septor=true` NetworkPolicy label on **every** deploy, whether or
not you declare septor. The same holds for `iec-queue`.
In the builder, `ALWAYS_DIRECT_DEP_SERVICES = ['septor', 'iec-queue']` is unioned into
the namespace's direct-dep labels before the catalog is even consulted, so
`computeDirectDepServices(undefined)` still returns both.
If you declare septor anyway, the builder warns and **ignores the entry**:
> `septor is an auto-injected platform service — SEPTOR_URL and its NetworkPolicy
> direct-dep label are applied on every deploy. Declaring it in spec.dependencies is
> harmless but unnecessary; the entry will be ignored.`
That warning is correct — act on it.
> **Superseded guidance.** Before `9e0a2d0` this page said you MUST declare septor in
> `internalDependencies` or lose the audit trail, and told you to ignore the builder's
> warning. That was true until 2026-06-22 and is now wrong on both counts. The
> declaration is dead weight.
>
> **One caveat:** the label is applied *at deploy time*. A service that never declared
> septor and has not deployed since 2026-06-22 does not carry the label yet — its next
> deploy adds it.
## 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.43050
- Tokens
- ~1,418
- Updated
- 2026-08-28
platformauditcomplianceseptorhash-chainimmutable