Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
cf7fcb0
feat(core): add role helpers and export from barrel
ItsThompson Jul 4, 2026
74809f0
test(core): cover role helpers truth table
ItsThompson Jul 4, 2026
61ff027
feat(cm): scaffold change-management layout
ItsThompson Jul 4, 2026
d774f62
feat(cm): add 000-template description.md
ItsThompson Jul 4, 2026
557ada4
feat(cm): add 000-template preflight and steps
ItsThompson Jul 4, 2026
0617d64
feat(change-mgmt): add admin finance cleanup SQL assets
ItsThompson Jul 4, 2026
b191c1b
feat(gateway): add access model types
ItsThompson Jul 4, 2026
cb5ec1e
feat(gateway): add canonical default policy table
ItsThompson Jul 4, 2026
f4f28c9
feat(gateway): add pure access resolver
ItsThompson Jul 4, 2026
952ab4a
test(gateway): add table-driven resolver tests
ItsThompson Jul 4, 2026
7714ba4
style(cm): drop em-dashes from template titles
ItsThompson Jul 4, 2026
e834516
refactor(gateway): rename resolver local var to avoid shadowing packa…
ItsThompson Jul 4, 2026
6abf009
fix(dbmigrate): strip golang-migrate x- params from raw schema connec…
ItsThompson Jul 4, 2026
426d8e9
test(dbmigrate): add admin finance cleanup safety test
ItsThompson Jul 4, 2026
624de10
feat(cm): add change-management project skill
ItsThompson Jul 4, 2026
f2e2bc3
feat(shell): land admin on /admin after login via getLandingPath
ItsThompson Jul 4, 2026
aa5cf41
test(shell): assert login navigation for admin vs user
ItsThompson Jul 4, 2026
ddd7003
feat(settings): derive tabs and sections from role
ItsThompson Jul 4, 2026
6354d17
test(gateway): add route coverage guardrail test
ItsThompson Jul 4, 2026
b0595e0
test(settings): cover role-derived admin composition
ItsThompson Jul 4, 2026
eb0b169
feat(cm): add execution-log generator
ItsThompson Jul 4, 2026
2d67943
test(cm): cover execution-log generator
ItsThompson Jul 4, 2026
f21d995
chore(cm): ignore python cache in .tools
ItsThompson Jul 4, 2026
97713af
style(cm): drop em-dashes from change-management skill headings
ItsThompson Jul 4, 2026
aa765dc
feat(gateway): relocate TokenValidator to access package
ItsThompson Jul 4, 2026
f759866
feat(shell): add direct-admin route guard and role-derived nav
ItsThompson Jul 4, 2026
b16473d
test(shell): cover admin guard precedence, operator nav, and assumed …
ItsThompson Jul 4, 2026
216075d
test(gateway): add AccessControl matrix and behavior tests
ItsThompson Jul 4, 2026
5277aa3
feat(cm): add change-management validator
ItsThompson Jul 4, 2026
1fe1b1e
test(cm): cover the change-management validator
ItsThompson Jul 4, 2026
c6c991c
ci(cm): gate PRs with change-management job
ItsThompson Jul 4, 2026
9ddde15
refactor(settings): reuse canonical SettingsPageProps in tab defs
ItsThompson Jul 4, 2026
ca04184
chore(cm): ignore validator pycache artifacts
ItsThompson Jul 4, 2026
084e142
refactor(cm): tidy checklist guard and broaden CLI error catch
ItsThompson Jul 4, 2026
eeddd8e
test(shell): assert Log Expense FAB is hidden for an assumed session
ItsThompson Jul 4, 2026
d0f5077
fix(gateway): fail-safe deny unknown access levels in AccessControl
ItsThompson Jul 4, 2026
2a0dd71
test(cm): cover stray-validation rejection paths
ItsThompson Jul 4, 2026
bdf665b
feat(cm): author 001 admin-finance-cleanup item
ItsThompson Jul 4, 2026
810d157
feat(gateway): wire AccessControl into router
ItsThompson Jul 4, 2026
9c526ca
docs(auth): fix stale gateway guard references
ItsThompson Jul 4, 2026
ce5026c
docs(cm): make 001 backup example copy-paste-safe
ItsThompson Jul 4, 2026
2ed1097
docs(auth): describe operator-only admin and centralized access control
ItsThompson Jul 4, 2026
cc77f87
docs(data-model): note admin owns no finance data and link 001 cleanup
ItsThompson Jul 4, 2026
f895028
docs(api): annotate route access levels and policy resolution
ItsThompson Jul 4, 2026
fcc8d63
docs(architecture): describe centralized access control and operator-…
ItsThompson Jul 4, 2026
c0e785b
docs(readme): describe operator-only admin in RBAC blurb
ItsThompson Jul 4, 2026
098a642
docs(development): correct mock-mode admin-uses-finance instruction
ItsThompson Jul 4, 2026
6ec6126
docs(testing): reflect access-control, role-helper, and settings test…
ItsThompson Jul 4, 2026
ccaec69
docs(api): replace em dash with (any) in policy table per repo conven…
ItsThompson Jul 4, 2026
f1d70c0
docs(development): correct admin bootstrap to operator-only onboarding
ItsThompson Jul 4, 2026
999c178
docs(cm): document validator exit code 2 in skill
ItsThompson Jul 4, 2026
ca37e4c
fix(gateway): match prefixes on segment boundary
ItsThompson Jul 4, 2026
4a2d3ef
docs(gateway): clarify 403 code contract
ItsThompson Jul 4, 2026
00d4dcd
refactor(shell): rename shadowed onSuccess param
ItsThompson Jul 4, 2026
090af76
test(shell): lock admin returnTo precedence
ItsThompson Jul 4, 2026
15ea2de
fix(settings): fall back to first tab
ItsThompson Jul 4, 2026
85ef95f
feat(access): add shared route registry and resolver module
ItsThompson Jul 5, 2026
24ff27e
refactor(gateway): derive access policy from shared registry
ItsThompson Jul 5, 2026
2b218df
feat(access): add generic fail-fast route binder
ItsThompson Jul 5, 2026
2c48dbe
feat(access): add registration coverage verifier
ItsThompson Jul 5, 2026
8acafbe
feat(services): register routes from the shared registry
ItsThompson Jul 5, 2026
710e91c
refactor(shell): derive route guards from access metadata
ItsThompson Jul 5, 2026
b33937c
test(shell): migrate auth tests to userEvent and 403 guard model
ItsThompson Jul 5, 2026
b837624
docs: align access-control docs with the shared registry
ItsThompson Jul 5, 2026
b7f76cd
docs(access): drop stale prefix wording in comments
ItsThompson Jul 5, 2026
e353f1a
feat(access): deny unclassified routes by default
ItsThompson Jul 5, 2026
2f2da67
refactor(gateway): derive proxy wiring from the shared prefix inventory
ItsThompson Jul 5, 2026
fea2233
docs: describe deny-by-default and the proxy-prefix single source
ItsThompson Jul 5, 2026
2372668
refactor(shell): type route handles + drop dead guard
ItsThompson Jul 5, 2026
dd144b8
test(shell): cover canAccess and navLinksFor
ItsThompson Jul 5, 2026
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
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ jobs:
- run: npx turbo lint
working-directory: frontend

validate-change-management:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Validate change-management items
run: python change-management/.validation/validate_change_management.py

test-backend:
needs: [lint-backend, lint-frontend]
runs-on: ubuntu-latest
Expand Down
245 changes: 245 additions & 0 deletions .pi/skills/change-management/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
---
name: change-management
description: >
GoFin change-management framework. Activate when creating, executing, or
reviewing an operational/destructive change under change-management/ (e.g.
one-off data cleanups, manual prod procedures). Covers folder naming, the
description/preflight/steps files, the activity/validation pairing rule, the
validator and execution-log generator, and the completion (status suffix +
execution log) workflow.
---

# Change Management

The `change-management/` directory (repo root) is a durable, auditable framework
for delivering operational changes that are NOT ordinary code migrations:
destructive data operations and manual production procedures. Each change is a
numbered, immutable-ish record that accrues history, modeled on how ADRs are
organized.

This skill is the how-to. The copyable file formats live in
`change-management/000-template/`; read those files rather than trusting a
paraphrase here, and copy them to start a new item.

## When to Use Change Management

Use a CM item when the change is a **managed manual operation**, for example:

- A destructive one-off data operation (bulk `DELETE`/`UPDATE` against prod).
- A manual production procedure with a blast radius that needs a documented
rollback and an auditable record of who ran it and what happened.
- Anything where "just run the migration" is unsafe because the action cannot
be trivially reversed and needs a human executing checklists with validations.

Use a **normal migration** (e.g. `services/dbmigrate`) instead when the change
is idempotent, safely automatable, and reversible through ordinary
forward/backward migration tooling. If a golang-migrate migration expresses the
change safely, it does not belong under `change-management/`.

Rule of thumb: if you need a rollback plan written per step and a technician
signing off on validations, it is a CM item. If CI can apply and revert it
mechanically, it is a migration.

## Directory Layout

```
change-management/
├── .validation/
│ └── validate_change_management.py # template + status validator (CI + local)
├── .tools/
│ └── generate_execution_log.py # builds execution-log.md from preflight.md + steps.md
├── 000-template/ # reserved: the canonical template to copy
│ ├── description.md
│ ├── preflight.md
│ ├── steps.md
│ └── assets/ # optional supporting files (.gitkeep keeps it tracked)
└── <NNN>_<kebab-title>[_<status>]/ # one folder per change item
├── description.md
├── preflight.md
├── steps.md
├── execution-log.md # present once executed (completion PR)
└── assets/ # optional (scripts, images, SQL, ...)
```

- `.validation/` and `.tools/` are dot-prefixed and are NOT treated as items by
the validator.
- `000-template` is the reserved literal (hyphen). It is not an executable item;
the validator checks its file structure but skips status/log checks.

## Naming Convention

| Element | Rule |
|---------|------|
| Item folder (pre-completion) | `<NNN>_<kebab-title>`; `NNN` = zero-padded sequential id, `<kebab-title>` = lowercase kebab-case. Regex: `^\d{3}_[a-z0-9]+(-[a-z0-9]+)*$` |
| Item folder (completed) | `<NNN>_<kebab-title>_<status>` where `status ∈ {completed, completed-off-script, failed, aborted}` |
| Template | The reserved literal `000-template` (hyphen) |
| Tooling dirs | `.validation/` and `.tools/` (dot-prefixed, ignored as items) |

Ids are assigned sequentially by the author: pick the next unused number. The
template is `000`; the first real item is `001`. The per-folder regex does not
auto-enforce uniqueness, so confirm your id is not already taken before
creating.

## Creating an Item

1. Pick the next unused sequential id (`NNN`) and a lowercase kebab-case title.
2. Copy the template:

```
cp -r change-management/000-template change-management/<NNN>_<kebab-title>
```

3. Fill in `description.md`, `preflight.md`, and `steps.md`. Do not add, remove,
or rename the enforced headings. Replace every `<...>` placeholder.
4. Put any supporting files (SQL, scripts, images) under the item's `assets/`.
`assets/` contents are not validated.
5. Do NOT create `execution-log.md` yet and do NOT add a status suffix: those
arrive at completion.
6. Run the validator locally (below) and open the item-creation PR.

## The Three Required Files

Every item folder must contain `description.md`, `preflight.md`, and `steps.md`.
The authoritative formats are the files in `change-management/000-template/`:
read and copy those. Their roles and enforced shapes:

### `description.md`: rationale and risk assessment

A fixed section structure the validator enforces. It must contain these `##`
sections, each with its `####` prompts (see `000-template/description.md` for
the full prompt wording):

- `## Change Event` (5 prompts: purpose, what's required to execute, expected
end state, assumptions about system state, rollout date/time + duration)
- `## Impact / Risk Assessment` (5 prompts: why necessary, why under CM / can it
be automated, prerequisite changes, intrusiveness + impacted teams/services,
how it was tested for prod safety)
- `## Worst Case Scenario` (2 prompts: worst realistic failure, how this CM
mitigates it)
- `## Rollback Procedure` (3 prompts: rollback triggers, actions to reach a
known-good state, whether rollback was rehearsed in a dev environment)

Do not add, remove, or rename these headings.

### `preflight.md`: everything before execution

Merge, deploy, dry-run, backups: the work done before the change is executed.

### `steps.md`: the execution itself

The change action plus any repo housekeeping that finalizes the change
(committing the execution log, renaming the folder with a status suffix).

Both `preflight.md` and `steps.md` use the paired activity/validation structure
below.

## Activity / Validation Pairing Rule

This is the framework's core discipline: **no action ships without a defined way
to confirm it and a rollback if the confirmation fails.**

- Every `# Activity N` MUST be immediately followed by a matching
`# Validation N` with the same index.
- Keep indexes contiguous, starting at 1.
- Each block (Activity and Validation alike) MUST contain:
- a `**Description**` line,
- a `## Checklist:` section,
- a `## Rollback Plan:` section.

If an `Activity N` has no matching `Validation N`, the validator fails. See
`change-management/000-template/preflight.md` and `steps.md` for the exact
block layout; add or remove pairs as needed.

## Status Lifecycle

```
(author creates item) (technician executes) (completion PR)
000-template ─copy─▶ NNN_title ───────────────────────▶ NNN_title_<status>
no status suffix execution-log.md required
execution-log.md not required
```

| Status | Meaning |
|--------|---------|
| `completed` | All activities executed as written; all validations passed. |
| `completed-off-script` | Completed, but the technician deviated from the written steps; deviations documented in `execution-log.md`. |
| `failed` | Execution could not be completed successfully; state and follow-up documented. |
| `aborted` | Execution was stopped before completion (e.g. a preflight validation failed) and the system was returned to a known-good state. |

## Running the Validator

`change-management/.validation/validate_change_management.py` is a Python 3 CLI
that runs locally and in CI. It validates folder names, required files,
`description.md` headings, the activity/validation pairing, and completion
evidence.

```
# validate every item
python change-management/.validation/validate_change_management.py

# validate a single item
python change-management/.validation/validate_change_management.py --item <NNN>_<kebab-title>

# non-default root (rarely needed)
python change-management/.validation/validate_change_management.py --root change-management
```

- Exit `0`: all validated items conform.
- Exit `1`: at least one validated item has a violation; each is printed to
stderr (item, file, failing rule).
- Exit `2`: a usage error, e.g. the `--root` or `--item` path does not exist.

Run it before opening either PR (item creation and completion). CI runs the same
command on every push/PR via the `validate-change-management` job in
`.github/workflows/ci.yml`, so a violation blocks merge.

## Generating the Execution Log

`change-management/.tools/generate_execution_log.py` reads an item's
`preflight.md` and `steps.md` and writes `execution-log.md` into the item
folder. The generated log is the checklist the technician fills in during
execution and commits as evidence.

```
# generate the log for an item
python change-management/.tools/generate_execution_log.py change-management/<NNN>_<kebab-title>

# overwrite an existing log
python change-management/.tools/generate_execution_log.py change-management/<NNN>_<kebab-title> --force
```

It refuses to overwrite an existing `execution-log.md` unless `--force` is
passed. Generate the log at execution time, not at item creation.

## Completion Workflow

When the change has been executed:

1. **Generate the execution log** if you have not already
(`generate_execution_log.py <item-folder>`).
2. **Fill it in** during execution: technician, date/time, environment, each
activity/validation checkbox, and Comments for any off-script actions, log
output, or anomalies. Record the outcome.
3. **Rename the folder** with the outcome status suffix:

```
git mv change-management/<NNN>_<kebab-title> change-management/<NNN>_<kebab-title>_<status>
```

where `<status>` is one of `completed | completed-off-script | failed |
aborted`.
4. **Open the completion PR** with the rename and the filled-in
`execution-log.md`. CI re-validates: a status-suffixed folder must contain a
non-empty `execution-log.md`, or the `validate-change-management` job fails
and merge is blocked.

## Common Failures

| Case | Result |
|------|--------|
| An `Activity N` has no matching `Validation N` | Validator fails: unmatched activity. |
| Folder renamed with an invalid status suffix | Folder-name regex fails. |
| Completed folder missing `execution-log.md` | Validator fails (status suffix requires the log). |
| Wrong id padding (e.g. `1_foo`) | Folder-name regex fails (`\d{3}` required). |
| Empty `assets/` | Allowed; keep it tracked with `.gitkeep`. |
| Two items reuse an id | Not auto-enforced; assign ids sequentially and check first. |
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ https://github.com/user-attachments/assets/44ed82d6-a7b6-499c-90ba-3655cedaf110

## Introduction

gofin is an intentionally overengineered personal finance tracker that lets users set monthly budgets with an essentials/desires/savings split, log expenses, and track spending via a real-time dashboard. It serves a dual purpose: a functional personal finance tool and a learning platform for distributed systems patterns. Key features include an immutable expense ledger backed by [immudb](https://immudb.io/) with bank-style corrections (no edits, only appends), pro-rata expense spreading across multiple months, GDPR-compliant data export with email delivery, RBAC with admin identity assumption, and a full observability stack with Prometheus and Grafana.
gofin is an intentionally overengineered personal finance tracker that lets users set monthly budgets with an essentials/desires/savings split, log expenses, and track spending via a real-time dashboard. It serves a dual purpose: a functional personal finance tool and a learning platform for distributed systems patterns. Key features include an immutable expense ledger backed by [immudb](https://immudb.io/) with bank-style corrections (no edits, only appends), pro-rata expense spreading across multiple months, GDPR-compliant data export with email delivery, role-based access control with an operator-only admin identity (used for operations and identity assumption, never personal finance), and a full observability stack with Prometheus and Grafana.

## Technology Stack

- **Frontend:** React micro-frontends composed at runtime via Module Federation 2.0, with a Node.js SSR shell app
- **Backend:** Go microservices (Gin framework) communicating over REST and gRPC
- **Databases:** PostgreSQL (relational data), immudb (immutable expense ledger)
- **Auth:** JWT with RBAC, Google OAuth, admin identity assumption
- **Auth:** JWT with RBAC (operator-only admin), Google OAuth, admin identity assumption
- **Observability:** Prometheus, Grafana, Alertmanager
- **Infrastructure:** Docker Compose, Cloudflare Tunnels, single-VPS deployment
- **CI/CD:** GitHub Actions with automated deployment on push to main
Expand Down
2 changes: 2 additions & 0 deletions change-management/.tools/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__/
*.pyc
Empty file.
Loading
Loading