8.4 KiB
Projects & Approvals — Implementation Reference
Projects
Database Schema
Migration 006 + 009
-- projects table
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(10) UNIQUE NOT NULL,
name TEXT,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
created_by TEXT -- added in migration 009
);
-- many-to-many junction
CREATE TABLE item_projects (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
item_id UUID NOT NULL REFERENCES items(id) ON DELETE CASCADE,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(item_id, project_id)
);
Items can belong to multiple projects. Project codes are immutable after creation.
API Endpoints
All gated by RequireModule("projects").
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/projects |
viewer | List all projects |
| POST | /api/projects |
editor | Create project (code: 2-10 alphanum) |
| GET | /api/projects/{code} |
viewer | Get single project |
| PUT | /api/projects/{code} |
editor | Update name/description |
| DELETE | /api/projects/{code} |
editor | Delete project (cascades associations) |
| GET | /api/projects/{code}/items |
viewer | List non-archived items in project |
| GET | /api/projects/{code}/sheet.ods |
viewer | Export ODS workbook (items + BOMs) |
| GET | /api/items/{pn}/projects |
viewer | Get projects for an item |
| POST | /api/items/{pn}/projects |
editor | Add item to projects ({"projects":["A","B"]}) |
| DELETE | /api/items/{pn}/projects/{code} |
editor | Remove item from project |
Backend Files
| File | Contents |
|---|---|
internal/db/projects.go |
ProjectRepository — 13 methods: List, GetByCode, GetByID, Create, Update, Delete, AddItemToProject, AddItemToProjectByCode, RemoveItemFromProject, RemoveItemFromProjectByCode, GetProjectsForItem, GetProjectCodesForItem, GetItemsForProject, SetItemProjects |
internal/api/handlers.go |
Handlers: HandleListProjects, HandleCreateProject, HandleGetProject, HandleUpdateProject, HandleDeleteProject, HandleGetProjectItems, HandleGetItemProjects, HandleAddItemProjects, HandleRemoveItemProject |
internal/api/ods.go |
HandleProjectSheetODS — multi-sheet ODS export with items list + per-assembly BOM sheets |
internal/api/routes.go |
Route registration under /api/projects and /api/items/{pn}/projects |
Frontend
Page: web/src/pages/ProjectsPage.tsx at route /projects
Features:
- Sortable table (code, name, description, item count, created date)
- Create form with code validation (2-10 chars, auto-uppercase)
- Inline edit (name/description only, code immutable)
- Delete with confirmation dialog
- Item count fetched per-project in parallel
- Catppuccin Mocha theme, conditionally shown in sidebar when module enabled
Config
modules:
projects:
enabled: true # default true
Not Yet Implemented
- Project-level permissions / ownership (all projects visible to all viewers)
- Project-based filtering in the main items list page
- Bulk item operations across projects
- Project archival / soft-delete
- Project hierarchies or team assignment
Approvals & ECO Workflows
Database Schema
Migration 018 + 019
-- item_approvals table
CREATE TABLE item_approvals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
item_id UUID NOT NULL REFERENCES items(id) ON DELETE CASCADE,
workflow_name TEXT, -- added in migration 019
eco_number TEXT,
state TEXT NOT NULL DEFAULT 'draft',
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_by TEXT
);
-- approval_signatures table
CREATE TABLE approval_signatures (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
approval_id UUID NOT NULL REFERENCES item_approvals(id) ON DELETE CASCADE,
username TEXT NOT NULL,
role TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
signed_at TIMESTAMPTZ,
comment TEXT
);
State Machine
Approval states: draft | pending | approved | rejected
Signature states: pending | approved | rejected
Auto-transitions (evaluated after each signature):
- If any signature is
rejectedand workflow definesany_rejectrule -> approval becomesrejected - If all signatures on required gates are
approvedand workflow definesall_required_approverule -> approval becomesapproved - Otherwise -> stays
pending
Workflow Definitions
YAML files in workflows/ directory, loaded at server startup.
workflows/engineering-change.yaml:
workflow:
name: engineering-change
version: 1
description: "Standard engineering change order with peer review and manager approval"
states: [draft, pending, approved, rejected]
gates:
- role: engineer
label: "Peer Review"
required: true
- role: manager
label: "Manager Approval"
required: true
- role: quality
label: "Quality Sign-off"
required: false
rules:
any_reject: rejected
all_required_approve: approved
workflows/quick-review.yaml:
workflow:
name: quick-review
version: 1
description: "Single reviewer approval for minor changes"
states: [draft, pending, approved, rejected]
gates:
- role: reviewer
label: "Review"
required: true
rules:
any_reject: rejected
all_required_approve: approved
Custom workflows: add a .yaml file to the workflows directory with the same structure.
API Endpoints
Not module-gated (always available when server is running).
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/workflows |
viewer | List all loaded workflow definitions |
| GET | /api/items/{pn}/approvals |
viewer | List approvals for item (with signatures) |
| POST | /api/items/{pn}/approvals |
editor | Create approval with signers |
| POST | /api/items/{pn}/approvals/{id}/sign |
editor | Record signature (approve/reject) |
Create approval request:
{
"workflow": "engineering-change",
"eco_number": "ECO-2026-042",
"signers": [
{ "username": "alice", "role": "engineer" },
{ "username": "bob", "role": "manager" },
{ "username": "carol", "role": "quality" }
]
}
Validation: workflow must exist, all signer roles must be defined in the workflow, all required gates must have at least one signer.
Sign request:
{
"status": "approved",
"comment": "Looks good"
}
Validation: approval must be in pending state, caller must be a listed signer, caller must not have already signed.
SSE Events
Published on the existing GET /api/events stream.
| Event | Payload | Trigger |
|---|---|---|
approval.created |
{part_number, approval_id, workflow, eco_number} |
Approval created |
approval.signed |
{part_number, approval_id, username, status} |
Signature recorded |
approval.completed |
{part_number, approval_id, state} |
All required gates resolved |
Backend Files
| File | Contents |
|---|---|
internal/db/item_approvals.go |
ItemApprovalRepository — Create, AddSignature, GetWithSignatures, ListByItemWithSignatures, UpdateState, GetSignatureForUser, UpdateSignature |
internal/api/approval_handlers.go |
HandleGetApprovals, HandleCreateApproval, HandleSignApproval, HandleListWorkflows, evaluateApprovalState |
internal/workflow/workflow.go |
Workflow/Gate/Rules types, Load, LoadAll, Validate, RequiredGates, HasRole |
workflows/engineering-change.yaml |
3-gate ECO workflow |
workflows/quick-review.yaml |
1-gate quick review workflow |
Config
workflows:
directory: /etc/silo/workflows # path to workflow YAML files
Frontend
No approval UI exists. The backend API is fully functional but has no web interface. Issue #147 includes an approvals page scaffold as part of the .kc metadata web UI phase.
Not Yet Implemented
- Web UI for creating, viewing, and signing approvals
- Approval history / audit log view
- Lifecycle state transitions tied to approval outcomes (e.g. auto-release on approval)
- Email or notification integration for pending signatures
- Delegation / proxy signing
- Approval templates (pre-filled signer lists per workflow)
- Bulk approval operations