Curator workflow v1 + Claude Code plugin (#4)#5
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
curator_audit,curator_auth,curator_integrity, plusPROV-008inprovenance_tiers. Non-negotiable invariants including:derivedFromimmutable on CURATED (C-INT-004)derivedFromon create (C-INT-007) — no orphan interpretationsDELETErevoked at privilege level (C-AUDIT-003)0006_curator_workflow.sql:api_keys.role, obligations concurrency + staleness + soft-delete columns,curator_editsappend-only audit table@lexius/core, transactional, with re-embed + cross-check + audit in one DB txn/api/v1/curate/*withrequireCuratorRolemiddleware +If-Match+?dry_run@robotixai/lexius-credentialsand@robotixai/lexius-curate(paste-a-key login, dry-run default, --apply to commit)setWhere: eq(curatedBy, 'seed:rob'))Test plan
pnpm build— 11 packages cleanpnpm 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 violations0006_curator_workflow.sqlverified against local DB (api_keys.role + CHECK, obligations columns + index, curator_edits table + CHECK constraints + REVOKE DELETE)--helpsmoke testWaves shipped
0ef956f20a70925e8e2f87d3185104d83acrequireCuratorRole63fa9dadf39873@robotixai/lexius-credentials+@robotixai/lexius-curate569cef4c145c4f0a266caWhat's explicitly out of scope
@robotixai/wikivaultv0.1 exists)🤖 Generated with Claude Code