feat(sessions): workstations table, registration API, and module scaffold #161

Closed
opened 2026-03-01 15:35:58 +00:00 by forbes · 1 comment
Owner

Context

Sub-issue of #125 (Context-Aware Part Subscription System).

Workstations are the foundation for edit sessions, checkpoints, and subscriptions. A workstation represents a specific client installation (Create instance on a particular machine). This issue sets up the module scaffold and workstation CRUD.

1. Module Registration

Register sessions as an optional module in internal/modules/modules.go:

const Sessions = "sessions"

{ID: Sessions, Name: "Edit Sessions", Description: "Workstation tracking, edit session locking, checkpoints", DependsOn: []string{Auth, DAG}, DefaultEnabled: false}

2. Database Migration

Create migrations/022_workstations.sql:

CREATE TABLE workstations (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id     UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    name        TEXT NOT NULL,
    fingerprint TEXT NOT NULL UNIQUE,
    last_seen   TIMESTAMPTZ,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_workstations_user ON workstations(user_id);

fingerprint is a client-generated stable identifier (e.g. hostname + MAC hash or OS machine-id). Uniqueness prevents duplicate registrations.

3. Database Layer

Create internal/db/workstations.go:

  • WorkstationRepository with methods:
    • Register(ctx, ws) — upsert on fingerprint (idempotent)
    • ListForUser(ctx, userID) — all workstations for a user
    • GetByID(ctx, id) — single lookup
    • GetByFingerprint(ctx, fingerprint) — lookup by fingerprint
    • Touch(ctx, id) — update last_seen to now()
    • Delete(ctx, id) — remove (cascades to future sessions/subscriptions)

4. API Endpoints

Create internal/api/workstation_handlers.go:

Method Path Auth Description
POST /api/workstations viewer Register workstation (idempotent on fingerprint)
GET /api/workstations viewer List current user's workstations
DELETE /api/workstations/{id} viewer Remove own workstation

All routes gated by RequireModule("sessions").

Users can only see/manage their own workstations.

5. Config

modules:
  sessions:
    enabled: true
    session_timeout_minutes: 15
    checkpoint_ttl_hours: 168
    checkpoint_max_per_item: 5

Add SessionsConfig to internal/config/config.go.

Acceptance Criteria

  • sessions module registered with dependency on auth + dag
  • Migration creates workstations table
  • POST /api/workstations registers idempotently on fingerprint
  • GET /api/workstations returns only current user's workstations
  • DELETE /api/workstations/{id} removes own workstation (403 if not owner)
  • Routes return 404 when sessions module disabled

Depends On

None (foundation layer)

Part Of

#125

## Context Sub-issue of #125 (Context-Aware Part Subscription System). Workstations are the foundation for edit sessions, checkpoints, and subscriptions. A workstation represents a specific client installation (Create instance on a particular machine). This issue sets up the module scaffold and workstation CRUD. ## 1. Module Registration Register `sessions` as an optional module in `internal/modules/modules.go`: ```go const Sessions = "sessions" {ID: Sessions, Name: "Edit Sessions", Description: "Workstation tracking, edit session locking, checkpoints", DependsOn: []string{Auth, DAG}, DefaultEnabled: false} ``` ## 2. Database Migration Create `migrations/022_workstations.sql`: ```sql CREATE TABLE workstations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, name TEXT NOT NULL, fingerprint TEXT NOT NULL UNIQUE, last_seen TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_workstations_user ON workstations(user_id); ``` `fingerprint` is a client-generated stable identifier (e.g. hostname + MAC hash or OS machine-id). Uniqueness prevents duplicate registrations. ## 3. Database Layer Create `internal/db/workstations.go`: - `WorkstationRepository` with methods: - `Register(ctx, ws)` — upsert on fingerprint (idempotent) - `ListForUser(ctx, userID)` — all workstations for a user - `GetByID(ctx, id)` — single lookup - `GetByFingerprint(ctx, fingerprint)` — lookup by fingerprint - `Touch(ctx, id)` — update `last_seen` to now() - `Delete(ctx, id)` — remove (cascades to future sessions/subscriptions) ## 4. API Endpoints Create `internal/api/workstation_handlers.go`: | Method | Path | Auth | Description | |--------|------|------|-------------| | POST | `/api/workstations` | viewer | Register workstation (idempotent on fingerprint) | | GET | `/api/workstations` | viewer | List current user's workstations | | DELETE | `/api/workstations/{id}` | viewer | Remove own workstation | All routes gated by `RequireModule("sessions")`. Users can only see/manage their own workstations. ## 5. Config ```yaml modules: sessions: enabled: true session_timeout_minutes: 15 checkpoint_ttl_hours: 168 checkpoint_max_per_item: 5 ``` Add `SessionsConfig` to `internal/config/config.go`. ## Acceptance Criteria - [ ] `sessions` module registered with dependency on `auth` + `dag` - [ ] Migration creates `workstations` table - [ ] `POST /api/workstations` registers idempotently on fingerprint - [ ] `GET /api/workstations` returns only current user's workstations - [ ] `DELETE /api/workstations/{id}` removes own workstation (403 if not owner) - [ ] Routes return 404 when sessions module disabled ## Depends On None (foundation layer) ## Part Of #125
Author
Owner

Admins should be able to see all workstations

Admins should be able to see all workstations
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kindred/silo#161