* feat(salary): agent path to set this month's per-run salary
Agents could not do variable owner pay: the only per-run edit tool,
gnubok_update_payslip_line, edits the display-only Grundlon line that
every recalculation rebuilds from salary_run_employees.monthly_salary,
so the fixed employee salary silently won (user-reported).
- lib/salary/run-employees.ts: setRunEmployeeSalary() shared service
(draft gate, roundOre, 0 = nollkorning, display-line refresh); the
cookie route PATCH now delegates to it (behavior unchanged)
- MCP: gnubok_set_run_salary staged tool (search catalog: tools/list
budget at zero headroom), op type set_run_salary (medium risk),
commitSetRunSalary executor, payroll:write scope, payroll_month
loadout + payroll-monthly skill step; update_payslip_line description
now warns that recalc rebuilds base salary lines
- v1 REST: PATCH /salary-runs/{id}/employees/{employeeId} accepting
monthly_salary (draft only, dry-run, idempotency key)
- Migration pair (NOT VALID + VALIDATE) adds set_run_salary to the
pending_operations op-type CHECK; base list verified against prod live
- Tests: service, staged tool, executor, cookie route, v1 route; spec
snapshot updated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MP37pE3zk667nP6S766iJG
* fix(salary): harden set_run_salary per skeptic + CI findings
- Clear calculation_breakdown when the per-run salary changes so the
existing book preflights force a recalculation: a run can no longer
be booked with gross/tax derived from the old salary (skeptic R1)
- Enforce SALARY_OVERRIDE_MAX (10 MSEK) in the shared service and the
v1 body schema: closes the unbounded/1e307-overflow path that wrote
Infinity -> NULL -> 500 (skeptic R2)
- Promote gnubok_set_run_salary to the default catalog: a search-only
WRITE is uncallable on Claude.ai (update_customer lesson) while three
surfaces pointed agents at it; payload ceiling bumped 63.8K -> 64.4K
with a ledger entry, read-demotion left as its own change (skeptic R3)
- Granskning label type_set_run_salary in vocabulary.ts + sv/en (R4)
- Display-line refresh is fire-and-forget again (write already
committed; matches pre-refactor route behavior) and DB error details
carry the SQLSTATE code for Swedish error mapping
- v1 risk metadata aligned to 'medium'; NOT_DRAFT message now covers
salary edits, not just roster changes
- npm run apiskill:generate committed (CI apiskill:check failure)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MP37pE3zk667nP6S766iJG
* chore(migrations): rename set_run_salary pair past main's newest versions
origin/main gained 20260828120000 and 20260828154800 after this branch
staged 20260828110000/1; out-of-order versions are skipped at merge, so
the pair moves to 20260828160000/1 (byte-identical SQL, reference in the
VALIDATE header updated).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MP37pE3zk667nP6S766iJG
* chore: retrigger Supabase preview after migration-version repair
The preview branch tracked 20260828110000/1 before the rename to
20260828160000/1; the orphan rows are deleted from the preview branch's
schema_migrations (preview only, prod never saw those versions) and this
empty commit re-runs the tasks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MP37pE3zk667nP6S766iJG
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
179 lines
6.9 KiB
TypeScript
179 lines
6.9 KiB
TypeScript
/**
|
|
* Recommended tool loadouts per workflow, surfaced by gnubok_get_agent_briefing
|
|
* as `recommended_tools`.
|
|
*
|
|
* Why: client harnesses with deferred tool loading (Claude Code ToolSearch,
|
|
* claude.ai connector search) otherwise burn 4-6 round-trips discovering tools
|
|
* cluster by cluster before any work happens. Each loadout names the exact
|
|
* registry tools a workflow needs, ordered by typical call sequence, so a
|
|
* harness that supports batch selection (ToolSearch select:a,b,c) loads the
|
|
* whole cluster in ONE call, and connector agents stop guessing search
|
|
* keywords.
|
|
*
|
|
* Drift protection (same spirit as deriveToolMeta): the loadouts are validated
|
|
* against the real tool registry and the workflow-skill registry via
|
|
* assertRecommendedLoadoutsValid(), called at module init in server.ts right
|
|
* after the tools array is defined. A loadout naming a tool or skill that does
|
|
* not exist fails module load, and therefore every test that imports the
|
|
* server. __tests__/agent-briefing.test.ts additionally pins the check.
|
|
*
|
|
* The list is static per issue #1098: the briefing does not currently query
|
|
* workflow state (unbooked counts, open periods), so gating inclusion on state
|
|
* would mean new reads in the hot bootstrap path. Every loadout is returned
|
|
* for every company; applicability is the agent's judgment call.
|
|
*/
|
|
import { workflowSkills } from './skills'
|
|
|
|
export interface WorkflowLoadout {
|
|
/** Stable snake_case workflow key (e.g. "categorize_month"). */
|
|
workflow: string
|
|
/** One-line English description of what the workflow accomplishes. */
|
|
description: string
|
|
/** Workflow-skill slug: pass to gnubok_load_skill for the full playbook. */
|
|
skill: string
|
|
/** Exact registry tool names, ordered by typical call sequence. */
|
|
tools: readonly string[]
|
|
}
|
|
|
|
export const RECOMMENDED_WORKFLOW_LOADOUTS: readonly WorkflowLoadout[] = [
|
|
{
|
|
workflow: 'categorize_month',
|
|
description: 'Categorize and book a month of bank transactions.',
|
|
skill: 'bank-reconciliation',
|
|
tools: [
|
|
'gnubok_list_uncategorized_transactions',
|
|
'gnubok_suggest_categories',
|
|
'gnubok_categorize_transaction',
|
|
'gnubok_match_transaction_to_invoice',
|
|
// For transactions whose affärshändelse is already booked on an existing
|
|
// verifikat: links without creating new bookkeeping. Categorizing such a
|
|
// transaction would double-book it.
|
|
'gnubok_link_transaction_to_journal_entry',
|
|
// Tagging: check the registry before writing dimensions bags on
|
|
// categorize calls (resolve-don't-select needs real codes/names).
|
|
'gnubok_list_dimensions',
|
|
'gnubok_load_skill',
|
|
'gnubok_approve_pending_operation',
|
|
],
|
|
},
|
|
{
|
|
workflow: 'close_period',
|
|
description: 'Reconcile, document voucher gaps, and lock a fiscal period.',
|
|
skill: 'month-end-close',
|
|
tools: [
|
|
'gnubok_list_fiscal_periods',
|
|
'gnubok_list_uncategorized_transactions',
|
|
'gnubok_get_reconciliation_status',
|
|
// Account-keyed reconciliation: the rows behind the bridge and the
|
|
// staged link (bank accounts and skattekonto alike).
|
|
'gnubok_list_reconciliation_items',
|
|
'gnubok_reconcile_match',
|
|
'gnubok_reconcile_residual',
|
|
'gnubok_reconcile_signoff',
|
|
'gnubok_list_voucher_gaps',
|
|
'gnubok_explain_voucher_gap',
|
|
'gnubok_lock_period',
|
|
'gnubok_approve_pending_operation',
|
|
],
|
|
},
|
|
{
|
|
workflow: 'reconcile_month',
|
|
description: 'Reconcile every account with an outside truth (bank accounts, skattekonto) for a month and sign it off.',
|
|
skill: 'reconcile-month',
|
|
tools: [
|
|
'gnubok_get_reconciliation_status',
|
|
'gnubok_list_reconciliation_items',
|
|
'gnubok_reconcile_match',
|
|
'gnubok_reconcile_unmatch',
|
|
// Near-miss on a bank account (fee, interest, rounding): link and book
|
|
// the difference in one staged step.
|
|
'gnubok_reconcile_residual',
|
|
// Rows with no counterpart: book them (bank side) or link to the
|
|
// verifikat that already holds the affärshändelse.
|
|
'gnubok_categorize_transaction',
|
|
'gnubok_link_transaction_to_journal_entry',
|
|
'gnubok_reconcile_signoff',
|
|
'gnubok_approve_pending_operation',
|
|
],
|
|
},
|
|
{
|
|
workflow: 'invoice_run',
|
|
description: 'Create and send customer invoices.',
|
|
skill: 'invoicing-rules',
|
|
tools: [
|
|
'gnubok_list_customers',
|
|
'gnubok_create_customer',
|
|
'gnubok_list_articles',
|
|
// Tagging: invoices carry default_dimensions + per-item bags; check the
|
|
// registry before setting them on gnubok_create_invoice.
|
|
'gnubok_list_dimensions',
|
|
'gnubok_create_invoice',
|
|
'gnubok_send_invoice',
|
|
'gnubok_mark_invoice_as_sent',
|
|
'gnubok_approve_pending_operation',
|
|
],
|
|
},
|
|
{
|
|
workflow: 'vat_declaration',
|
|
description: 'Compute, review, and file the VAT declaration.',
|
|
skill: 'quarterly-vat-review',
|
|
tools: [
|
|
'gnubok_get_vat_report',
|
|
'gnubok_vat_close_check',
|
|
'gnubok_get_general_ledger',
|
|
'gnubok_vat_declaration_validate',
|
|
'gnubok_vat_declaration_submit',
|
|
'gnubok_vat_declaration_status',
|
|
'gnubok_approve_pending_operation',
|
|
],
|
|
},
|
|
{
|
|
workflow: 'payroll_month',
|
|
description: 'Run monthly payroll and generate the AGI.',
|
|
skill: 'payroll-monthly',
|
|
tools: [
|
|
'gnubok_list_employees',
|
|
'gnubok_create_salary_run',
|
|
'gnubok_set_run_salary',
|
|
'gnubok_calculate_salary_run',
|
|
'gnubok_get_salary_run',
|
|
'gnubok_book_salary_run',
|
|
'gnubok_generate_agi',
|
|
'gnubok_approve_pending_operation',
|
|
],
|
|
},
|
|
]
|
|
|
|
/**
|
|
* Fails fast when a loadout references a tool or workflow skill that does not
|
|
* exist. Called at module init in server.ts (after the tools array is built)
|
|
* so any rename/removal in the registry breaks the build and the test suite
|
|
* immediately instead of shipping a briefing that recommends phantom tools.
|
|
*/
|
|
export function assertRecommendedLoadoutsValid(knownToolNames: ReadonlySet<string>): void {
|
|
const knownSkillSlugs = new Set(workflowSkills.map((s) => s.slug))
|
|
const seenWorkflows = new Set<string>()
|
|
for (const loadout of RECOMMENDED_WORKFLOW_LOADOUTS) {
|
|
if (seenWorkflows.has(loadout.workflow)) {
|
|
throw new Error(
|
|
`recommended_tools: duplicate workflow key "${loadout.workflow}" in RECOMMENDED_WORKFLOW_LOADOUTS.`
|
|
)
|
|
}
|
|
seenWorkflows.add(loadout.workflow)
|
|
if (!knownSkillSlugs.has(loadout.skill)) {
|
|
throw new Error(
|
|
`recommended_tools: workflow "${loadout.workflow}" references unknown skill slug "${loadout.skill}". ` +
|
|
'Update RECOMMENDED_WORKFLOW_LOADOUTS in recommended-tools.ts.'
|
|
)
|
|
}
|
|
for (const toolName of loadout.tools) {
|
|
if (!knownToolNames.has(toolName)) {
|
|
throw new Error(
|
|
`recommended_tools: workflow "${loadout.workflow}" references unknown tool "${toolName}". ` +
|
|
'Update RECOMMENDED_WORKFLOW_LOADOUTS in recommended-tools.ts when renaming or removing tools.'
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|