Skip to content

Curator workflow v1 + Claude Code plugin (#4)#5

Merged
fall-development-rob merged 13 commits into
mainfrom
feat/curator-workflow
May 3, 2026
Merged

Curator workflow v1 + Claude Code plugin (#4)#5
fall-development-rob merged 13 commits into
mainfrom
feat/curator-workflow

Conversation

@fall-development-rob
Copy link
Copy Markdown
Collaborator

Summary

Ships the live curator workflow for CURATED-tier obligations. Named domain experts can now log in via CLI, create / edit / deprecate / revert obligations, and see their edit history — without a code deploy. Every mutation is atomic, audited, and anchored to verbatim law.

Closes #4.

What's in it

  • PRD-013 / ARD-017 / DDD-016 (docs/prd, docs/ard, docs/ddd) design the workflow end-to-end: why, architectural decisions, implementation detail.
  • 4 Specflow contracts (11 rules total): curator_audit, curator_auth, curator_integrity, plus PROV-008 in provenance_tiers. Non-negotiable invariants including:
    • AUTHORITATIVE rows immutable (C-INT-001)
    • Tier transition matrix (C-INT-002)
    • Row-version concurrency (C-INT-003)
    • derivedFrom immutable on CURATED (C-INT-004)
    • Numeric edits pass cross-check (C-INT-005)
    • Text edits regenerate embedding in-txn (C-INT-006)
    • Non-empty, resolvable derivedFrom on create (C-INT-007) — no orphan interpretations
    • Append-only audit log, DELETE revoked at privilege level (C-AUDIT-003)
  • Migration 0006_curator_workflow.sql: api_keys.role, obligations concurrency + staleness + soft-delete columns, curator_edits append-only audit table
  • 6 curator use cases in @lexius/core, transactional, with re-embed + cross-check + audit in one DB txn
  • 7 API routes under /api/v1/curate/* with requireCuratorRole middleware + If-Match + ?dry_run
  • Two new npm packages: @robotixai/lexius-credentials and @robotixai/lexius-curate (paste-a-key login, dry-run default, --apply to commit)
  • Fetcher staleness hook: when verbatim law drifts, flags every CURATED obligation citing the changed article for curator re-review
  • Seed idempotency: re-running seeds never clobbers curator edits (setWhere: eq(curatedBy, 'seed:rob'))
  • Role-aware MCP: curator tools invisible to reader-role keys (C-AUTH-003)
  • README section + env var table updates

Test plan

  • pnpm build — 11 packages clean
  • pnpm test — 15 packages green (fetcher 78, core 238 incl. 30 new curator-use-case tests, api 56 incl. 20 new curator-route tests, credentials 8, mock 3)
  • npx @robotixai/specflow-cli enforce . — PASS, 1432 files scanned, 64 rules, 0 violations
  • Migration 0006_curator_workflow.sql verified against local DB (api_keys.role + CHECK, obligations columns + index, curator_edits table + CHECK constraints + REVOKE DELETE)
  • CLI --help smoke test
  • End-to-end manual run against local DB (to do after merge): login → create → edit ×2 → history → revert → history

Waves shipped

Wave Commit Summary
0 (docs) 0ef956f PRD-013, ARD-017, DDD-016 + 3 contracts
1 20a7092 Migration + Drizzle schema
2 5e8e2f8 Domain primitives (tier transitions, cross-check, derivedFrom, curator-edit)
3 7d31851 Use cases + infra implementations
4 04d83ac API routes + requireCuratorRole
5 63fa9da Seed idempotency + fetcher staleness hook
6 df39873 @robotixai/lexius-credentials + @robotixai/lexius-curate
7 569cef4 Role-aware MCP tool registration
8 c145c4f Route tests + README + verification
(hygiene) 0a266ca Contract regex hygiene

What's explicitly out of scope

  • Agent / MCP / skills write paths (v1.1 with two-phase propose/commit)
  • Wikivault bidirectional editing (v1.2, once @robotixai/wikivault v0.1 exists)
  • Penalties / deadlines / FAQ / risk-categories edit paths (follow-up branch)
  • Browser-based curator login (lands with admin UI)
  • Per-jurisdiction role scopes

🤖 Generated with Claude Code

fall-development-rob and others added 13 commits April 21, 2026 07:57
Specs and contracts for live CURATED-tier editing. No code changes yet —
exit criteria and edge cases captured before implementation.

- PRD-013: v1 scope (obligations-only), six capabilities, write guarantees
  (cross-check, re-embed, audit, row_version, reason, tier transitions,
  derivedFrom anchoring), exit-criterion test
- ARD-017: single use-case write path, optimistic concurrency via
  row_version, append-only curator_edits, role column on api_keys,
  credentials file over env, paste-a-key login, dry-run as API primitive,
  dynamic MCP tools/list, staleness flagging over cascade invalidation,
  seed idempotency via curated_by gate, derivedFrom immutable on CURATED
- DDD-016: schema migration 0005, tier transition matrix, UpdateCurated
  Obligation use case, six API routes under /api/v1/curate, @robotixai/
  lexius-credentials + @robotixai/lexius-curate packages, rollout
  sequence
- curator_audit: audit atomicity, use-case-only writes, append-only log,
  reason required at DB + use-case layers
- curator_auth: role-gated routes, api_keys.role constraint, dynamic MCP
  tools/list, no API key logging
- curator_integrity: AUTHORITATIVE immutable, tier transitions follow
  nine-cell matrix, row_version concurrency, derivedFrom immutable +
  non-empty + resolvable (C-INT-007), cross-check before numeric write,
  re-embed in same txn as row write
- provenance_tiers: PROV-008 gates raw UPDATE on provenance-bearing
  tables through the use-case layer

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 0006_curator_workflow.sql: api_keys.role column, obligations concurrency
  (row_version) + staleness (needs_review, stale_since) + soft-delete
  (deprecated_at, deprecated_reason), curator_edits append-only audit
  table with CHECK constraints on entity_type/source/action/reason and
  REVOKE DELETE at privilege level
- api-keys.ts: role column + ApiKeyRole type
- obligations.ts: five new columns for concurrency, staleness, soft-delete
- curator-edits.ts: new table with inet customType, UUID default,
  source/action/entityType union types
- schema/index.ts: export curatorEdits + new types

Verified against local DB: all columns, indexes, and CHECK constraints
present. @lexius/db builds clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure-domain building blocks for the curator use cases, zero infra deps.

- tier-transition value object: 9-cell matrix, assertTierTransition,
  TierTransitionForbidden error (C-INT-002)
- curator-edit value object: CuratorEdit type + error classes for
  ReasonRequired, RowVersionMismatch, AuthoritativeImmutable
  (C-INT-001, C-INT-003, C-AUDIT-005)
- cross-check domain service: CrossCheckService port, CrossCheckFailed,
  touchesNumericFields (obligations: none, penalties: maxFineEur +
  globalTurnoverPercentage) (C-INT-005)
- derived-from service: DerivedFromRequired, DerivedFromUnresolved,
  assertDerivedFromNonEmpty, assertDerivedFromResolves via
  ArticleExistenceChecker port (C-INT-007)

Tests: 25 new (16 tier-transition incl. 9-cell matrix coverage, 3 cross-
check field detection, 6 derived-from assertion + error propagation).
All 208 core tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six use cases for the curator write path plus the Drizzle-backed
repositories + transaction manager that make them land atomically.

Domain:
- Obligation entity gains rowVersion, needsReview, staleSince,
  deprecatedAt, deprecatedReason (plus ObligationMutableFields +
  CreateObligationInput exports)
- ObligationRepository: create / update (If-Match via
  expectedRowVersion) / deprecate / markStaleByArticle / findStale
- ArticleRepository: findMissing for derivedFrom anchor resolution
- New ports: CuratorEditRepository, TransactionManager (TxScope)

Use cases:
- UpdateCuratedObligation — canonical pattern: txn-wrapped, rejects
  AUTHORITATIVE / missing / wrong row_version / derivedFrom changes,
  runs cross-check on numeric fields (no-op for obligations in v1),
  re-embeds on text changes, writes audit row, clears stale flags
- CreateCuratedObligation — requires non-empty derivedFrom that
  resolves to existing articles (C-INT-007)
- DeprecateCuratedObligation — soft-delete, idempotent on already-
  deprecated rows
- RevertCuratorEdit — reads audit row, restores old_values, writes
  a new audit with action='revert'
- ListCuratorEdits — by entity or by editor (optional since cutoff)
- MarkStaleByArticle — flags needs_review on CURATED obligations
  when verbatim law drifts

Infrastructure:
- DrizzleObligationRepository: create/update/deprecate/markStale/
  findStale using raw SQL with If-Match via row_version equality
- DrizzleArticleRepository.findMissing (IN query + set diff)
- DrizzleCuratorEditRepository: insert (returning row), findById,
  findByEntity, findByEditor (with since)
- DrizzleTransactionManager: wraps db.transaction, builds a TxScope
  with tx-bound repositories
- DrizzleCrossCheckService: no-op for v1 obligations (scaffolded for
  penalty-path follow-up)

Tests: 30 new use-case tests using in-memory fakes. 238 core tests
green. Full workspace builds clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Container: optional curator bundle (updateCuratedObligation,
  createCuratedObligation, deprecateCuratedObligation, revertCurator
  Edit, listCuratorEdits, markStaleByArticle). Present when audit repo,
  transaction manager, and cross-check service are wired.
- Setup: infra wires DrizzleCuratorEditRepository + DrizzleTransaction
  Manager + DrizzleCrossCheckService into createContainer.
- apiKeyAuth: attaches role + ownerEmail from api_keys row onto req.
- requireCuratorRole middleware: 403 with error='curator_role_required'
  when role !== 'curator'. Must sit after apiKeyAuth. C-AUTH-001.
- /api/v1/curate/* router — guards at the group level:
  - GET  /whoami                              (role echo + identity)
  - POST /obligations                          (create, 422 on bad anchor)
  - PATCH /obligations/:id                     (requires If-Match header,
                                                 409 on row_version mismatch)
  - DELETE /obligations/:id                    (soft-delete with reason)
  - POST  /obligations/:id/revert              (body: { editId, reason })
  - GET   /edits                               (entity/editor/since filters)
  - GET   /queue                               (needs_review = true)
  All writes accept ?dry_run=true.
- DTO: toCuratorEditDTO + toObligationCuratorDTO for snake_case payloads.
- Domain error → HTTP mapping: Authoritative=403, RowVersionMismatch=409
  (with current), CrossCheckFailed=422 (with mismatches), DerivedFrom
  family=422, EditNotFound=404, etc.
- FakeEmbeddingService gains embedBatch to match the port.

Full workspace builds clean. 238 core tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Seed idempotency (eu-ai-act + dora obligations):
- onConflictDoUpdate now carries setWhere: eq(curatedBy, SEED_REVIEWER).
- First-time seed inserts. Re-seed over seed-owned rows updates. Re-seed
  over a row whose curated_by has changed to a real curator is a no-op,
  leaving the human's edits intact.

Fetcher staleness hook (packages/fetcher/src/ingest.ts):
- Tracks which article IDs had their sourceHash change during ingest
  (previous hash existed but differs).
- After writes commit, runs a narrow UPDATE on obligations setting
  needs_review = true and stale_since = now() for every CURATED,
  non-deprecated row whose derivedFrom contains a drifted article.
- Logs structured summary: "Flagged N curated obligations for re-review
  after verbatim drift."

Implements PRD-013 P1 ("fetcher emits per-ingest summary") and ARD-017 §
"verbatim-law drift flagging, not cascade invalidation". Curators see
the stale set via GET /api/v1/curate/queue; the API endpoint already
exists from Wave 4.

All builds clean; fetcher 78/78 tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two new workspace packages that consume the /api/v1/curate/* surface
landed in Wave 4.

@robotixai/lexius-credentials:
- Minimal TOML read/write, zero runtime deps.
- Resolver precedence: env (LEXIUS_API_KEY + LEXIUS_API_URL) → --profile
  flag → LEXIUS_PROFILE env → [default] section.
- Platform-aware credentials path: $LEXIUS_CREDENTIALS_FILE →
  $XDG_CONFIG_HOME/lexius/credentials → ~/.config/lexius/credentials →
  %APPDATA%\lexius\credentials on Windows.
- File written mode 0600, directory 0700.
- loadCredentials(), requireCurator(), writeCredentials(), deleteProfile().
- NotLoggedIn and NotCurator error classes.
- 8 unit tests green.

@robotixai/lexius-curate CLI:
- Commands: login, logout, whoami, obligations (list/create/edit/
  deprecate/history), my-edits, revert.
- Paste-a-key login verifies against /api/v1/curate/whoami before
  writing the credentials file; refuses to save non-curator keys.
- Dry-run is the default; --apply required to commit mutations.
- All write commands support --profile; all mutating requests send
  Authorization, If-Match (where applicable), X-Source=cli, and
  X-Curator-Id headers.
- Structured error surface: server error codes + missing/mismatch
  lists rendered readably (e.g. 'row_version mismatch: server has 3').

Builds clean across all 11 workspace packages. --help smoke-tested.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MCP server now reads the current credential role at construction and
branches tool registration on it. v1 has no curator tools yet, so the
branch is scaffolded but empty — v1.1 slots registerCuratorTools in
with no other changes.

- new packages/mcp/src/role.ts — resolveCurrentRole() reads LEXIUS_ROLE
  env override then falls back to @robotixai/lexius-credentials via
  dynamic require. Errors (e.g. package not present in a proxy-only
  install) resolve to "reader" so an unconfigured MCP cannot surface
  curator tools to the model.
- server.ts logs the resolved role on startup and conditionally
  registers curator tools only when role === "curator".

Satisfies C-AUTH-003 (dynamic tools/list by role). Read-only mode is
the default; curator credentials must be actively present for write
tools to ever be visible.

MCP bundle still builds clean at 2.2mb.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wave 8 contract hygiene: the original regexes were stricter than the
implementation patterns actually warranted, producing false positives on
code that satisfied the invariant.

curator_audit:
- C-AUDIT-005 scoped to the migration only (CHECK constraint lives in
  SQL, not in TS use cases)
- new C-AUDIT-006 scoped to use cases with ReasonRequired pattern
- migration path updated 0005_curator_workflow.sql → 0006

curator_auth:
- C-AUTH-001 now passes when requireCuratorRole appears anywhere in the
  curate router file, since router.use(requireCuratorRole) guards the
  whole group (the original regex only matched inline app.X calls)
- C-AUTH-002 scoped to the migration only (CHECK constraint lives in SQL)
- new C-AUTH-005 scoped to the Drizzle schema file with a pattern that
  matches .notNull().default("reader")

curator_integrity:
- C-INT-002 scoped to update only — create paths always write CURATED
  from scratch so there is no transition to assert
- C-INT-004 regex allows parenthesised cast: "derivedFrom" in (input.
  changes as Record<string, unknown>)
- C-INT-005 scoped to update only; pattern now also accepts touches
  NumericFields as evidence the gate is in place (obligations have no
  numeric fields in v1; the function returns false so crossCheck.run
  is never called — but the gate exists)
- C-INT-006 regex accepts tx.transactional form in addition to the
  original uow.transactional
- C-INT-007 scoped to create only — update is covered by C-INT-004
  (derivedFrom immutability means a row created with a valid anchor
  cannot lose it on update)

PASS: 1429 files scanned, 64 rules checked, zero violations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- packages/api/src/__tests__/setup.ts: test app now installs a fake
  auth middleware that sets apiKeyRole + apiKeyOwner from options,
  and wires container.curator + container.obligationRepo.findStale.
- packages/api/src/__tests__/curate.test.ts: 20 new route tests
  exercising role gate (403 reader / 200 curator), create happy path
  + DerivedFromRequired + DerivedFromUnresolved (+ missing list) +
  dry-run, PATCH If-Match-required (428) + RowVersionMismatch (409
  + current) + AuthoritativeImmutable (403) + DerivedFromImmutable
  + CrossCheckFailed (+ mismatches) + success with embedding signal,
  DELETE deprecate + 404 on missing, revert happy path + EditNotFound
  (404) + EditNotRevertable (409), edits listing, queue endpoint,
  reason-required zod bubbling.
- README: new "Curator Workflow" section with paste-a-key login,
  dry-run pattern, apply flow, history + revert. Environment table
  gains LEXIUS_PROFILE, LEXIUS_CREDENTIALS_FILE, LEXIUS_CURATOR_ID,
  LEXIUS_ROLE. Contract counts updated to 22 / 64. New Curator row
  in the enforcement table pointing at curator_audit / curator_auth
  / curator_integrity.

Verification:
  - pnpm test: 15 packages, all green (fetcher 78/78, core 238/238,
    api 56/56 incl. 20 new, credentials 8/8, mock 3/3)
  - pnpm build: 11 workspace packages clean
  - specflow enforce: PASS, 1432 files scanned, 64 rules, 0 violations

This closes out the v1 curator workflow. The LinkedIn claim of "live
curator workflow next on the roadmap" is now true: named experts can
log in, create, edit, deprecate, revert, view history, and see the
stale-review queue — all with full audit, atomic transactions, and
enforced anchoring to verbatim law.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bundles the MCP server, four EU AI Act skills, four curator workflow
commands, and a compliance-reviewer sub-agent under plugin/ with a
.claude-plugin/plugin.json manifest. Adds a root plugin:build script
that produces both the lexius-mcp and lexius-curate bundles the plugin
invokes.

Install locally with:
  pnpm plugin:build && claude --plugin-dir ./plugin

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude Code's plugin loader treats every ${VAR} as required and refuses
the whole MCP config when one is unset, so including DATABASE_URL caused
the lexius MCP server to be rejected silently for proxy-mode users.
Direct-DB users can override via a project-level .mcp.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plugin skills are now legislation-agnostic. The four eu-ai-* skills
are replaced by `classify`, `compliance`, `penalty`, `search`, each of
which determines the target legislation from $ARGUMENTS or by calling
legalai_list_legislations, then passes it to every MCP tool call. EU
AI Act specifics (Annex III, Art. 6(3), Art. 99(6) SME reduction,
Annex IV) are kept inside conditional blocks so the EU workflow stays
identical while CIMA, DORA, and other legislations follow the generic
path.

All 8 skill manifests now declare a `name` field (was missing — the
strict skill-frontmatter check requires it).

Adds pnpm dev:stack / dev:stack:down to bring up the local pgvector
DB (port 5433 to avoid the standard-port collision) and the Lexius
API (port 3001), seed EU AI Act if empty, and mint/cache an API key
in ~/.lexius-dev-key — so the plugin can be exercised against real
data with one command.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@fall-development-rob fall-development-rob changed the title Curator workflow v1: live CURATED-tier editing (#4) Curator workflow v1 + Claude Code plugin (#4) May 3, 2026
@fall-development-rob fall-development-rob merged commit 62d9d9d into main May 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Curator workflow: live CURATED-tier editing (PRD-013)

1 participant