Changelog
All notable changes to Security Flare are recorded here. Format roughly follows Keep a Changelog; the project is pre-1.0 so version numbers track the slice + PR sequence rather than semver.
Unreleased — audit-grade policy templates + brand-aware tables
The 14 master policy templates were thin summaries; an ISO 27001 auditor needs controlled documents. Rebuilt all 14 to a documented authoring standard.
- Policy authoring standard (
docs/policy-authoring-standard.md): every policy now has a document-control header (POL-XX-NNN, version, owner, approver, dates), a fixed section skeleton (Purpose → Scope → Definitions → specific policy statements → Roles → Compliance → Exceptions → References → Version history), and specific testable requirements. Templates grew from ~250 to ~900–1,500 words; the Risk Management Policy now carries the full Clause 6.1.2/6.1.3 methodology — defined likelihood/impact/evaluation scales, risk appetite, treatment→SoA, and residual-risk approval. Still paraphrases (never quotes) ISO, keeps every scoping conditional + merge field. - Brand-aware tables: the branded
.docxrenderer now themes Markdown tables (risk scales, document control, roles, version history) from the customer'sbrand.primaryColour— a light brand-hued header band with deep brand-hued header text and a hairline border (brandTableTheme()), derived so any palette stays coherent and readable. Plain exports get neutral tables. - Template tests strengthened to enforce the standard (document control, required sections, depth floor, unique ids, risk methodology).
Unreleased — production hardening: evidence automation + Object Lock + headers
- Microsoft Graph evidence auto-pull enabled — Workers Paid confirmed active;
provisioned the
security-flare-evidencequeue +-evidence-dlqdead-letter queue, and enabled the daily[triggers]cron (14:00 UTC) +[[queues.*]]producer/consumer inwrangler.toml. Thescheduled()handler now fans one job per (org, control) onto the queue each day; thequeue()consumer runs the evidence-pull dispatcher with retries → DLQ. - R2 Object Lock live — the three
-prodbuckets (evidence/policies/exports) now carry a 7-year bucket lock rule, and the Worker binds them (was-dev, no lock). Evidence/policy/export objects are now WORM for 7 years.
Unreleased — production hardening: security headers
Global security response headers on every Worker egress — the app previously sent none.
withSecurityHeaders(apps/web/src/lib/security-headers.ts) applied at the single Workerfetchegress inserver.ts, so the API, auth, evidence, and SSR document surfaces are all covered: HSTS (1yr),X-Content-Type-Options: nosniff,X-Frame-Options: DENY+ CSPframe-ancestors 'none',Referrer-Policy,Permissions-Policy, and a CSP scoped to non-breaking directives (base-uri/object-src/upgrade-insecure-requests) that leaves the SSR/Vite inline script+style model intact. HSTSpreloadis a deliberate separate opt-in (the apex carries live M365 mail).- ADR-0003 corrected: the R2 Object Lock ritual was written against a
non-existent API (no
--object-lock-*flags onwrangler r2 bucket create; noobjectLockModeon the WorkersR2Bucket.put()binding). Replaced with the real mechanism — a bucket-levelwrangler r2 bucket lock addretention rule that locks every written object automatically, no app-code change.
Unreleased — compliance heat map (PRs #154–#157)
A dashboard heat map showing how close each area is to full control implementation, so you can see at a glance where you're furthest from compliant.
- By capability (#154): the 93 controls grouped by ISO 27002 operational
capability, each tile scored
implemented / applicable(N/A excluded; in-progress surfaced), red→amber→green, worst areas first.compliance-heatmap.tspure engine;GET /api/dashboard/heatmap. - Clickable tiles (#155): each tile deep-links to
/controls?capability=<area>via a new comma-boundary capability filter on the catalogue. - Annex A theme axis (#156): a compact 4-tile top row (Organizational / People / Physical / Technological), each linking to the theme-filtered controls.
- Trend over time (#157): a daily
compliance_snapshots(migration 0025, captured on dashboard view — no cron needed) + an overall-score sparkline with the delta since the first snapshot.
Unreleased — branded, tailored policy generator (PRs #144–#150)
Shipping to production 2026-07-04. An MSP configures its branding once and Security Flare generates the whole ISO 27001 policy set as branded, tailored, auditor-ready
.docxdocuments.
Four phases, each independently shippable (design spec):
- Phase 1 — branding foundation (#145–#147):
brand_profilestable (migration 0022) with MSP-default + per-customer-override resolution; a branded.docxrenderer modelled on the reference proposal (dark hero cover with logo + cyan eyebrow + title, PREPARED-FOR block, running header/footer, brand theme, Calibri); merge-field engine ({{org.name}},{{policy.owner}}, …);/api/brand-profileCRUD + logo upload to R2; a/brandingsettings page with live cover preview. A policy's Export .docx is now branded + auto-filled. - Phase 2 — scoping (#148):
policy_scoping_answers(migration 0023) + an 8-question questionnaire and a fail-closed{{#if flag}} … {{/if}}conditional engine, so generated policies only include the clauses that fit the business. - Phase 3 — AI tailoring (#149): a "Tailor with AI" action drafts a
Claude-tailored policy (prose paraphrased, never ISO verbatim; Australian
spelling). The draft always lands needs-review — approval is cleared and a
human re-approves; every run writes a
policy_ai_generationsprovenance row (model, prompt version, inputs hash, tokens, actor). Gated onANTHROPIC_API_KEY— absent, the feature simply hides. - Phase 4 — bulk pack (#150): one Generate policy pack (.zip) button on
/policiesproduces the whole set — every one of the 14 templates rendered as a branded, scoped, merged.docx(the org's edited clone wins where it exists), plus amanifest.json, in a single download. The "produce all policies for a business in one click" payoff. - Phase 2b — template enrichment (#151): the 14 seed policy templates now
carry real
{{#if flag}}scoping clauses (M365, remote work, BYOD, cloud, sensitive data, physical office, outsourced IT, software development), so the questionnaire visibly tailors the stock wording. Conditionals resolve at clone time (the editor stays clean) and at pack/export time. CD now runs an idempotentseed-policiesstep so the reference templates always match the repo; a schema test guards balanced markers + known flag keys.
Unreleased — detail-route fix (PR #138)
Deployed to production 2026-07-03, same day it was found — by an in-browser walkthrough of the live deploy.
Fixed
| PR | Fix | Why it matters |
|---|---|---|
| #138 | /controls/$code, /policies/$id, /risks/$id were unreachable — the detail files used TanStack Router's flat-file nesting convention under list routes that render no <Outlet/>, so every detail URL silently re-rendered its parent list. Renamed to the trailing-underscore un-nesting convention (controls_.$code.tsx etc.); public paths unchanged |
Clicking any catalogue row, policy card, or risk row navigated but visibly did nothing — the single most load-bearing click on each register |
Unreleased — public surfaces + §4-6 audit foundation (PRs #87–#133)
Deployed to production 2026-07-03 via the new CD pipeline — migrations 0019/0020/0021 applied, Worker live, post-deploy smoke tests green across all 13 public surfaces + 5 export endpoints.
After the operational push, the §4-6 audit-foundation gap closed in one
turn: three new clauses-aware registers shipped end-to-end (schema → API →
UI → dashboard tile → audit_log trail → Cmd+K discovery → MSP rollup →
operator triage signal → XLSX export → ADR → runbook), alongside the
public marketing/legal surface getting promoted from "coming soon" to
real content across all four /trust /privacy /terms /status pages,
plus discovery surfaces (security.txt, robots.txt, sitemap.xml,
openapi.json), plus per-page Open Graph + Twitter meta overrides
so social-shared links unfurl with the right content, plus a
single-click operator audit-handover .zip bundle that wires every
register XLSX + a manifest into one download — backed by pure
helpers + boundary-pinning tests so the operator zip can't drift
from the customer's UI.
Thirty-nine PRs landed, 684 tests passing throughout.
Added — public surfaces
| PR | Surface | Why it matters |
|---|---|---|
| #87 | /changelog route renders the repo's CHANGELOG.md via marked (?raw import, prerendered) |
Marketing-grade transparency — what a prospect's procurement team looks for before signing |
| #88 | /api-docs public reference for the JSON API (replaces the raw /api/controls link in the footer) |
Documents the two open endpoints in full + lists the cookie-bound authenticated surface + versioning contract |
Added — operator surfaces
| PR | Surface | Why it matters |
|---|---|---|
| #89 | /admin audit-log gains organisation + action verb filters (server-validated; invalid filter → 400, no silent ignore) |
Recent Activity used to dump 50 rows globally; filters fix that without rebuilding the surface |
Added — §4-6 audit-foundation registers
| PR | Clause | Surface | Migration |
|---|---|---|---|
| #90 | §6.2 Information security objectives | /objectives register with lifecycle: planned → in_progress → achieved / missed / cancelled. Terminal-state reopen returns 409. |
0019 |
| #91 | §4.2 Interested parties | /interested-parties register with the addressed enum mapping directly to §4.2(c): fully / partially / not_yet / not_applicable. Soft-retire preserves audit trail. |
0020 |
| #92 | §4.3 ISMS scope statement | /isms-scope — single record per org with append-only version history. Editing publishes version+1, never mutates in place. |
0021 |
Added — surfacing the new registers
| PR | Surface | Why it matters |
|---|---|---|
| #93 | Dashboard rollup tiles for objectives, interested parties, ISMS scope | Without this, the §4-6 surfaces would be invisible from /dashboard (the primary surface a tenant returns to) |
| #94 | audit_log writes for every §4-6 mutation; verbs humanised in /audit-log and dashboard Recent Activity |
audit_log is what the ISO certifier inspects to see "who changed what". Status-change PATCHes get lifecycle-specific verbs (e.g. objective.achieved). |
| #95 | Cmd+K palette extended with Objectives (§6.2) and Interested parties (§4.2) groups | Discovery — the new registers are now searchable from anywhere via ⌘K |
Added — auditor handoff, MSP triage, test coverage, docs
| PR | Surface | Why it matters |
|---|---|---|
| #97 | XLSX export for §6.2 objectives, §4.2 interested parties, §4.3 ISMS scope (full version history) | Auditors expect spreadsheet handoffs; matches existing risks + SoA pattern. Scope export carries every revision, not just current. |
| #98 | Dashboard Getting Started checklist reordered: §4.3 → §4.2 → §6.2 inserted between verify and first-control, each tagged with its clause | Auditor walks foundations before controls; the checklist now matches that order. 5 steps → 8 steps. |
| #99 | MSP portfolio §4-6 gap chip per child org with foundationGaps count + tooltip listing missing surfaces |
Customer at 95% control-readiness without scope/parties/objectives still fails cert; chip surfaces the gap directly. |
| #100 | +38 unit tests for iso-objective-helpers (26) + interested-party-helpers (12) covering the "no reopen" lifecycle invariant, enum rejection, null-clears |
Locks in the 409 / 400 behaviour the API depends on. |
| #101 | ADR-0016 captures the design decisions; README ISMS-coverage table updated with the new surfaces at the top in auditor walk order | Closes the documentation loop — design rationale + surface tally both reflect the new work. |
| #103 | /iso clause-reference page points §4.1 / §4.2 / §4.3 / §6.2 at the new dedicated surfaces (was /documents + /tasks) |
New customers visiting /iso to map the standard to the product land on the right register instead of the controlled-documents catch-all. |
| #104 | MSP portfolio gains a "§4-6 gaps" filter chip that hides every fully-grounded customer | Triage tool — stop scrolling, see only the customers who need foundation help before their audit window. |
| #105 | /api-docs documents the §4-6 endpoints + new Spreadsheet exports (XLSX) section listing all five export.xlsx paths with curl example and response-header reference |
Customer engineers + auditors who want programmatic spreadsheet retrieval can find it in the docs surface, not just the codebase. |
| #106 | Operator console All Orgs table gains a §4-6 indicator column (S/P/O dots, emerald = present, hollow-red = missing, muted for non-active orgs) | Same triage signal MSPs have for their portfolio, but for SF staff across every tenant. |
| #107 | +10 unit tests lock in the /iso clause-map § 4-6 pointers from #103 so a future refactor can't silently revert them |
554 → 564 tests. |
| #108 | RUNBOOK §4.1 adds a CSM follow-up cadence (Day 0 / 3 / 14 / 30) for §4-6 foundation work + pointers to the /admin/audit-log filter chips for week-on-week tracking |
Converts "we have the signal" into "here's the playbook for acting on it". |
| #110 | apps/web/src/lib/audit-log-verbs.ts consolidates the machine-verb → human-phrase map; /audit-log + /dashboard now both import the same helper instead of carrying duplicate (and drifting) copies |
Closes the drift risk that recently bit the §4-6 verbs ('a party' vs 'an interested party' between the two pages) — single source of truth + 29 new tests covering every lifecycle / enum verb. |
| #111 | /admin Recent Activity panel humanises the verb too, with the raw verb as a small muted suffix so operators filtering still see the literal |
Operator console reader gets the same readability the tenant gets, without losing the verb-as-literal needed for the adjacent filter bar (#89). |
| #112 | +22 parametrised tests for activity-route.ts covering every entityType the audit_log API currently emits — §4-6 + Graph + Stripe entity types and their aliases |
Catches the silent regression mode where removing a case from the helper makes activity rows non-clickable for that entity type. 593 → 615 tests. |
| #114 | /trust renders the existing docs/security-whitepaper.md via the /changelog ?raw + marked pattern |
First page a prospect's procurement team hits — no more "coming soon"; one source of truth between the whitepaper file the README points at and the public URL |
| #115 | /privacy real content sourced from new docs/privacy.md (v1, pending external legal review) covering APP 12/13 commitments, NDB scheme procedure, sub-processor list, Object Lock retention carve-outs |
Australian SaaS bound by Privacy Act 1988 + the NDB scheme cannot ship a "coming soon" privacy page |
| #116 | /terms real content from new docs/terms.md (v1, pending external legal review) covering NSW law, payment-failure auto-suspend tied to #86, 99.5% availability target + service-credit remedy, Object-Lock 7-year carve-out on close, ACL warranty + 12-month liability cap |
Procurement matrix needs a real terms-of-service link, not a stub |
| #117 | /status polls /healthz every 30s and shows live green/red badge + ms-latency + "verify yourself" curl command + no-incidents commitment |
The only one of the four placeholders that genuinely needed runtime; pairs with the SLA in /terms §6 |
| #118 | Cleanup: remove unused ComingSoonRoute component (−85 lines) |
All four pages were the only callers; dead code now |
| #120 | /.well-known/security.txt (RFC 9116), /robots.txt, /sitemap.xml — three Hono-served discovery surfaces with 11 new format-invariant tests |
Security researchers + procurement teams expect a coordinated-disclosure signal; SEO crawlers need an allow-list + sitemap; deny-list explicitly closes every tenant-bound surface to crawlers as a belt-and-braces alongside the <meta name=robots> in __root.tsx. 615 → 626 tests. |
| #122 | Per-page Open Graph + Twitter overrides for the 6 public pages via new lib/page-meta.ts helper + Route.head() on each route |
Sharing a link to /privacy in Slack used to unfurl as the marketing landing-page card; now each page gets its own title + description + canonical URL. +10 unit tests for the helper. 626 → 636 tests. |
| #124 | Operator audit-handover .zip bundle at GET /admin/orgs/:id/export.zip — bundles risks + SoA + §6.2 objectives + §4.2 parties + §4.3 scope (full version history) + a manifest.json into one cleanly-named zip; writes org.exported_bundle to the tenant's audit_log |
Terms §7.1 + Privacy §4 commit to handing the customer their data on account closure. Now a single URL instead of six curl commands. |
| #125 | "Bundle" button in the /admin All Orgs table actions column wires #124 into the UI with per-row spinner + Content-Disposition filename parsing |
Operator doesn't have to type the URL by hand or copy the org id. |
| #127 | OpenAPI 3.1 spec at /openapi.json (Hono-served, 12-hour edge cache) covering the 13 documented endpoints — catalogue + exports + authenticated registers — with schemas, security schemes, and tag groups |
Machine-readable companion to /api-docs. Procurement scanners + Postman + Swagger UI + Redoc consume it directly. +22 tests pin the shape so a new endpoint can't silently miss the spec. 636 → 660 tests. |
| #128 | /api-docs header gains a pointer to /openapi.json |
Developers reading the page can decide between the human-readable docs (this page) and the machine-readable spec without having to guess that one exists. |
| #130 | Operator bundle refactor: extract sanitiseOrgNameForFilename, bundleFilename, buildBundleManifest as exported pure helpers + consolidate canonical ratingFor in lib/risk-export.ts; fixes the score-5 rating drift between /api/risks (low) and the operator bundle (was medium) |
A customer with a 1×5 risk would have appeared medium in the operator-zipped XLSX and low in their own UI — fixed by consolidation. +24 boundary-pinning tests cover the canonical bucketing + sanitisation edge cases + manifest stability. 660 → 684 tests. |
| #132 | Continuous deployment — .github/workflows/deploy.yml: push to main → frozen install → full tests → prod migrations → wrangler deploy → live smoke test (/healthz + /openapi.json + /robots.txt). Skips with a notice until the two repo secrets exist; RUNBOOK §3.0 documents the flow |
Deploys stop depending on a laptop with fresh wrangler OAuth. The first run applied migrations 0019/0020/0021 and shipped this entire push to production in one pass. |
| #133 | Fix: export routes were shadowed by /:id — /api/{risks,objectives,interested-parties}/export.xlsx returned 400 invalid … id for every caller because Hono matches in registration order and GET /:id was registered first. Routes moved above /:id with a ⚠ ROUTE ORDER comment; +10 dispatch-order regression tests |
Found by the CD pipeline's post-deploy smoke test on day one — the risks Export button had been silently broken since the export first shipped. 684 → 694 tests. |
Migration sequence (this push)
0019 iso_objectives §6.2 objectives register
0020 interested_parties §4.2 stakeholder register
0021 isms_scope_versions §4.3 scope, append-only versions
No new secrets, no new bindings. Just pnpm --filter @sf/db migrate against
your Neon branch, then redeploy.
v0.3 — operational layer push (PRs #70–#82)
After the register sweep landed, this push hardens the operational layer needed to bill a customer for it. Eight production features + five new ADRs, all shipped autonomously over two days. Frame: Lens 2 in the "how close are we to billing customers" review — see ADR-0013 for the security-review artefact prospects will ask for first.
Added — UX
| PR | Surface | Why it matters |
|---|---|---|
| #71 | Nav redesign — 4 primary + Registers ▾ + Programme ▾ + user chip + mobile sheet | 14 inline links overflowed the bar; UX was unusable on the dashboard |
| #72 | Dashboard register rollup (8 new tiles, attention-coloured) | Programme registers were invisible without leaving the dashboard |
| #73 | Org name in the user chip + MSP-gated Portfolio | Orientation across multiple customer orgs in the same session |
| #74 | Clickable Recent-activity rows | One-click jump from activity to the entity that changed |
| #75 | Cmd+K global search palette across every register | Vanta-class navigation; ⌘K + type + ↑↓ + ↵ to land on any control / policy / risk |
| #76 | ? shortcut → keyboard-shortcuts help dialog + dashboard "View all" → /audit-log |
Discoverability for the palette |
| #77 | MSP Portfolio: sort (worst-first / best-first / A-Z) + status filter chips | Triage flow for MSPs juggling many customer orgs |
Added — features
| PR | Surface | Why it matters |
|---|---|---|
| #79 | Microsoft Graph admin-consent flow + /integrations page |
Unblocks the cron evidence pipeline for real customer tenants — three-check invariant on the callback (HMAC signature, persisted-state match, Entra tenant id match) |
| #82 | Stripe billing MVP — webhook receiver + /billing page + 11 signature-verifier tests |
Operator-managed billing flow (Stripe-as-source-of-truth pattern); ready to charge a customer today |
Added — observability + security
| PR | Surface | Why it matters |
|---|---|---|
| #78 | ADR-0013 — withServiceRole BYPASSRLS call-site audit |
Sales-grade security review; every bypass site documented with trust root + isolation invariant + failure mode |
| #80 | ADR-0014 observability strategy + RUNBOOK §9 (Logpush) + §10 (DR) | Three-layer story (L1 Logpush→R2, L2 Analytics Engine, L3 audit_log), DR procedures (Neon PITR, wrangler rollback, quarterly drill) |
| #81 | Per-tenant access log on requireSession / maybeSession |
Forensic access trail — single JSON line per authenticated request, ready for Logpush ingestion |
Added — chores
| PR | Surface | Why it matters |
|---|---|---|
| #70 | biome.json $schema 2.0.0 → 2.4.15 + ADR-0012 + RUNBOOK refresh |
Cleared the only lint info; documented why Queues are off (Workers Paid prereq) |
Migration sequence
0017 graph_consent_state (#79)
0018 subscriptions (#82)
0017 adds three columns to organisations for tracking Microsoft Graph
admin-consent state (graph_consent_granted_at,
graph_consent_granted_by_user_id, graph_consent_state).
0018 adds five columns + two partial indexes for Stripe subscription
mirroring.
Operator setup to use this push
- Apply migrations
0017+0018to prod Neon. - Add
https://<host>/auth/adminconsent/callbackto the Entra app registration's Redirect URIs (Microsoft Graph admin-consent flow). wrangler secret put STRIPE_WEBHOOK_SECRETand add a Stripe webhook endpoint pointing athttps://<host>/auth/stripe/webhookfor the six event types listed in ADR-0015.- (Once on Workers Paid) Provision the Logpush job per RUNBOOK §9.1 and the error-rate alert per §9.2.
Test coverage
507 unit tests by the end of the push (up from 422 pre-push). The 85 new tests cover: the Cmd+K search-route mapping (29), the Stripe webhook signature verifier (11), the activity-route helper (29), the dashboard register-rollup (16).
Added — documentation
- ADR-0012 — Queues + cron bindings disabled until Workers Paid.
- ADR-0013 —
withServiceRolecall-site audit (15 sites in #78 + 3 more in #79 + 3 more in #82 = 21 total at end of push). - ADR-0014 — Three-layer observability strategy.
- ADR-0015 — Stripe billing MVP: Stripe-as-source-of-truth pattern, no Checkout yet, operator-managed onboarding for first cohort.
v0.2 — ISMS register sweep (PRs #31–#45)
The post-Slice-9 build-out of every remaining ISO/IEC 27001:2022 register the ISMS needs for certification. Each register follows the same architecture (tenant table + Drizzle schema + migration + Hono API + pure-helper validators
- TanStack Start route + 15–30 unit tests pinning the security-critical invariants). See ADR-0011.
Added — features
| PR | Surface | ISO ref |
|---|---|---|
| #31 | Manual evidence delete (soft-delete, R2-Object-Lock-safe) | A.5.36 |
| #32 | Recent activity panel on org dashboard | A.5.36 / 8.15 |
| #33 | Per-control bulk status update (PATCH /api/controls/state/bulk) |
A.5.36 |
| #34 | Per-policy editor refinements (Cmd+S, dirty guard, approval lock, outline, word count) | A.5.1 |
| #35 | Per-user invitations + /team page |
A.6.1 |
| #37 | Asset inventory | A.5.9, A.5.12 |
| #38 | Task tracker UI (existing schema, new API + route) | (operational layer) |
| #39 | Incident register | A.5.24–A.5.27 |
| #40 | Supplier register | A.5.19–A.5.23 |
| #41 | Audits + management reviews | §9.2 + §9.3 |
| #42 | Findings + corrective action (stacked on #41) | §10.1, §10.2 |
| #43 | Training register (catalogue + completions, validity auto-compute) | A.6.3 |
| #44 | Tenant-side /audit-log viewer (filterable, cursor-paginated) |
A.8.15 |
| #45 | Document register (controlled documents, §7.5) | §7.5 |
Added — fixes
- #36 —
fix(db): grantapp_servicecross-tenant reads. Repairs the live/api/msp500 (operator role had no SELECT onorganisation_controls) + the silent operator-audit-log gap (app_servicehad no INSERT onaudit_log). DB-only migration0009, idempotent, no Worker redeploy.
Added — documentation
- ADR-0010 — Per-user invitations security model: same-tenant only in v1, email-match (not token) binds invites in bootstrap, no silent demotions.
- ADR-0011 — Register architecture pattern: the standard shape every register PR in this sweep follows (table + helpers + tests + route).
Test coverage
Roughly 250 unit tests across the codebase by the end of the sweep (up from 41 at PR #30). Every register's pure helpers are exercised parametrically — enum whitelists, length caps, UUID guards, status-transition matrices, soft-delete invariants, and audit-trail preservation rules.
Migration sequence
Migrations land in the order PRs merge to slice-5c-3-prerender. Numbers
0008–0016 are reserved by this sweep:
0008 invitations (#35)
0009 app_service grants (#36 — DB-only, run on prod first)
0010 assets (#37)
0011 incidents (#39)
0012 suppliers (#40)
0013 audits (#41)
0014 findings (#42 — depends on #41 for audit_id FK)
0015 training (#43)
0016 documents (#45)
PR #44 (audit-log viewer) has no schema changes — it reads the existing
audit_log table.
Known limitations
feat-audit-log-writesis a parallel branch that writesaudit_logrows on tenant actions (control state, policy approve, risk, evidence). Today most rows only come from operator activations. The/audit-logviewer fills in immediately once that branch lands.- Dashboard rollup cards for the new registers are not yet stitched together —
each register page has its own count chip, but
/dashboardstill surfaces only the original (controls / policies / risks / evidence) summary.
Slices 1–9 (v0.1 baseline pre-sweep)
The original build plan from CLAUDE.md §5, plus operator console + audit
log writes. Recorded for completeness; full commit history is on the merged
slice branches.
- Slice 1 — Monorepo + auth + RLS scaffold. Hello-world Entra login, RLS coverage test, CI green.
- Slice 2 — Core data model. 93 controls seeded from xlsx; all tenant tables RLS-enabled.
- Slice 3 — Org provisioning with out-of-band verification (ADR-0002).
- Slice 3B —
app_userNOBYPASSRLS hot-fix (migration 0006). - Slice 4 — 10-control Microsoft Graph evidence wedge.
- Slice 4B — Cron + Queues orchestration for evidence collection (ADR-0009).
- Slice 5 — Control catalogue UI; per-control state.
- Slice 5C — TanStack Start SSR foundation (ADR-0006), prerendered public pages (ADR-0008).
- Slice 6 — Policy library: 14 templates clone + edit + docx export.
- Slice 7 — Risk register + SoA. Per-risk → Annex A control linkage, xlsx SoA export.
- Slice 8 — MSP cross-tenant readiness view.
- Slice 9 — ISMS dashboard at-a-glance summary.
- Operator console —
/admincross-tenant view for SF operators (verify orgs, suspend, audit log). BYPASSRLS bounded to listed call sites.