Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions skills/dunning-ladder/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
name: dunning-ladder
version: 0.1.0
description: Choose the next bounded accounts-receivable reminder step, propose a gated send, and escalate instead of exceeding the cadence cap.
source:
type: cli-tool
command: node
args:
- run.mjs
links:
source: https://github.com/luismireles12/runx/tree/feat/dunning-ladder/skills/dunning-ladder
runx:
category: ops
---

# Dunning Ladder

`dunning-ladder` evaluates one overdue receivable against a supplied cadence
policy. It chooses the eligible next reminder step while below the hard cap and
emits a gated proposal for `send-as`. It sends nothing itself.

## Inputs

- `invoice_status`: invoice ID, current status, reminders already sent, and an
optional non-sensitive customer reference.
- `aging_days`: whole days overdue.
- `cadence_policy`: ordered `steps` and a hard `cap`.

Each step supplies `step`, `min_days`, `channel`, and `template`.

## Outputs

- `decision`: chosen step and proposed action.
- `reminder_proposal`: channel, template, content digest, and mandatory approval
gate.
- `escalation`: whether operator escalation is needed and why.

## Guardrails

- Refuse records that are not explicitly overdue.
- Never propose more reminders than the cadence cap.
- At the cap, fail the run with an escalation instruction and no proposal.
- Never send, post, charge, suspend service, or mutate the receivable.
- Do not include private contact or payment details in the output.
- A separate governed `send-as` run must approve and perform any reminder.

The skill gives finance operators a deterministic cadence decision while
preventing unbounded or duplicate nagging.

92 changes: 92 additions & 0 deletions skills/dunning-ladder/X.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
skill: dunning-ladder
version: "0.1.0"

catalog:
kind: skill
audience: operator
visibility: public
role: canonical

harness:
cases:
- name: within-cap-reminder-proposal
inputs:
invoice_status:
invoice_id: inv-100
status: overdue
reminders_sent: 1
customer_ref: customer-7
aging_days: 18
cadence_policy:
cap: 3
steps:
- step: 1
min_days: 1
channel: email
template: friendly-reminder
- step: 2
min_days: 14
channel: email
template: firm-reminder
- step: 3
min_days: 30
channel: email
template: final-reminder
expect:
status: sealed
receipt:
schema: runx.receipt.v1
state: sealed
disposition: closed

- name: refuse-at-cadence-cap
inputs:
invoice_status:
invoice_id: inv-200
status: overdue
reminders_sent: 3
customer_ref: customer-9
aging_days: 45
cadence_policy:
cap: 3
steps:
- step: 1
min_days: 1
channel: email
template: friendly-reminder
- step: 2
min_days: 14
channel: email
template: firm-reminder
- step: 3
min_days: 30
channel: email
template: final-reminder
expect:
status: failure

runners:
decide:
default: true
type: cli-tool
command: node
args:
- run.mjs
inputs:
invoice_status:
type: json
required: true
description: "Bounded receivable state including status and reminders_sent."
aging_days:
type: number
required: true
description: "Whole days the receivable is overdue."
cadence_policy:
type: json
required: true
description: "Ordered reminder steps and a hard cadence cap."
outputs:
decision: object
reminder_proposal: object
escalation: object

17 changes: 17 additions & 0 deletions skills/dunning-ladder/fixtures/at-cap.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"invoice_status": {
"invoice_id": "inv-200",
"status": "overdue",
"reminders_sent": 3,
"customer_ref": "customer-9"
},
"aging_days": 45,
"cadence_policy": {
"cap": 3,
"steps": [
{"step": 1, "min_days": 1, "channel": "email", "template": "friendly-reminder"},
{"step": 2, "min_days": 14, "channel": "email", "template": "firm-reminder"},
{"step": 3, "min_days": 30, "channel": "email", "template": "final-reminder"}
]
}
}
18 changes: 18 additions & 0 deletions skills/dunning-ladder/fixtures/within-cap.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"invoice_status": {
"invoice_id": "inv-100",
"status": "overdue",
"reminders_sent": 1,
"customer_ref": "customer-7"
},
"aging_days": 18,
"cadence_policy": {
"cap": 3,
"steps": [
{"step": 1, "min_days": 1, "channel": "email", "template": "friendly-reminder"},
{"step": 2, "min_days": 14, "channel": "email", "template": "firm-reminder"},
{"step": 3, "min_days": 30, "channel": "email", "template": "final-reminder"}
]
}
}

2 changes: 2 additions & 0 deletions skills/dunning-ladder/references/dogfood-receipt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"schema":"runx.receipt.v1","id":"sha256:d3da84c3ef48e3d67a5f0e5c0a89f9025e0ff485962d4af4f8de92c6b99a727b","created_at":"2026-06-23T05:57:04.333Z","canonicalization":"runx.receipt.c14n.v1","issuer":{"type":"hosted","kid":"runx-demo-key","public_key_sha256":"sha256:3097e2dee2cb4a34b53840cdb705aed71067c36f68db0e0f559c3f3fa043315f"},"signature":{"alg":"Ed25519","value":"base64:3g1v4obOPUo7_oQS0WM2krW8q0onKOULeQGI4e9COE3P1EjD-OSPYsH87aKtB3pctD6yL_kgGa0Ueo2jjuB8CQ"},"digest":"sha256:66c421b235a99731c6318cc8921303f5dd03cbb89fd8bf47cb90419e36ea5d2d","idempotency":{"intent_key":"sha256:run_decide_8df2b3dcf6b6-decide-intent","trigger_fingerprint":"sha256:run_decide_8df2b3dcf6b6-decide-trigger","content_hash":"sha256:run_decide_8df2b3dcf6b6-decide-content"},"subject":{"kind":"skill","ref":{"type":"harness","uri":"hrn_run_decide_8df2b3dcf6b6_decide"},"commitments":[]},"authority":{"actor_ref":{"type":"principal","uri":"runx:principal:local_runtime"},"grant_refs":[],"scope_refs":[],"authority_proof_refs":[],"attenuation":{"parent_authority_ref":null,"subset_proof":null},"terms":[],"enforcement":{"profile_hash":"sha256:runtime-skeleton-enforcement","redaction_refs":[],"setup_refs":[],"teardown_refs":[]}},"signals":[],"decisions":[{"decision_id":"dec_decide","choice":"open","inputs":{"signal_refs":[],"target_ref":null,"opportunity_refs":[],"selection_ref":null},"proposed_intent":{"purpose":"Open runtime node decide","legitimacy":"Local graph execution requested this node","success_criteria":[],"constraints":[],"derived_from":[]},"selected_act_id":"act_decide","selected_harness_ref":null,"justification":{"summary":"runtime graph planner selected this node","evidence_refs":[]},"closure":null,"artifact_refs":[]}],"acts":[{"id":"act_decide","form":"observation","intent":{"purpose":"Run graph step decide","legitimacy":"Runtime graph execution was admitted by the local harness","success_criteria":[{"criterion_id":"process_exit","statement":"cli-tool exits successfully","required":true}],"constraints":[],"derived_from":[]},"summary":"Executed graph step decide","criterion_bindings":[{"criterion_id":"process_exit","status":"verified","evidence_refs":[],"verification_refs":[],"summary":"cli-tool exited successfully"}],"source_refs":[],"target_refs":[],"artifact_refs":[],"closure":{"disposition":"closed","reason_code":"process_exit","summary":"cli-tool exited successfully","closed_at":"2026-06-23T05:57:04.333Z"}}],"seal":{"disposition":"closed","reason_code":"process_closed","summary":"cli-tool decide completed","closed_at":"2026-06-23T05:57:04.333Z","last_observed_at":"2026-06-23T05:57:04.333Z","criteria":[{"criterion_id":"process_exit","status":"verified","evidence_refs":[],"verification_refs":[],"summary":"cli-tool exited successfully"}]},"lineage":{"children":[],"sync":[]}}

113 changes: 113 additions & 0 deletions skills/dunning-ladder/references/evidence.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
{
"schema": "frantic.evidence.v1",
"summary": "Published dunning-ladder package chose an eligible second reminder under a three-step cap, emitted only a human-gated send-as proposal, demonstrated escalation at the cap, and produced a sealed receipt that verifies as valid.",
"observations": [
{
"id": "cli-version",
"command": "runx --version",
"output": "runx-cli 0.6.13",
"details": {"publisher_owner": "luismireles12", "package_name": "dunning-ladder"}
},
{
"id": "source-provenance",
"command": "gh pr view 130 --repo runxhq/runx",
"output": "Public PR contains package, fixtures, and harness evidence.",
"details": {
"pr_url": "https://github.com/runxhq/runx/pull/130",
"source_url": "https://github.com/luismireles12/runx/tree/feat/dunning-ladder/skills/dunning-ladder",
"x_yaml": "https://raw.githubusercontent.com/luismireles12/runx/feat/dunning-ladder/skills/dunning-ladder/X.yaml",
"skill_md": "https://raw.githubusercontent.com/luismireles12/runx/feat/dunning-ladder/skills/dunning-ladder/SKILL.md",
"verification_json": "https://raw.githubusercontent.com/luismireles12/runx/feat/dunning-ladder/skills/dunning-ladder/references/verification.json"
}
},
{
"id": "local-harness",
"command": "runx harness skills/dunning-ladder --json",
"output": "passed: 2 cases, 0 assertion errors",
"details": {
"cases": [
{"name": "within-cap-reminder-proposal", "status": "sealed"},
{"name": "refuse-at-cadence-cap", "status": "refused"}
]
}
},
{
"id": "publish",
"command": "runx registry publish ./skills/dunning-ladder/SKILL.md --registry https://api.runx.ai --json",
"output": "published",
"details": {
"version": "sha-c3ca3eb81b17",
"registry_ref": "luismireles12/dunning-ladder@sha-c3ca3eb81b17",
"public_url": "https://runx.ai/x/luismireles12/dunning-ladder@sha-c3ca3eb81b17",
"hosted_harness_status": "passed"
}
},
{
"id": "registry-read",
"command": "runx registry read luismireles12/dunning-ladder@sha-c3ca3eb81b17 --registry https://api.runx.ai --json",
"output": "status: success; owner: luismireles12",
"details": {
"digest": "9ccc4ba3d7c7a327d2668547162621044629c0d8bed552d9291fd1eb6fcb8a9b",
"profile_digest": "afdbafad1612ebf25c97e3a602429cb0b9f1ada59b91206c123d5fb07ea6ee8b"
}
},
{
"id": "clean-install",
"command": "runx add luismireles12/dunning-ladder@sha-c3ca3eb81b17 --registry https://api.runx.ai --to ./clean-install --json",
"output": "status: installed",
"details": {"install_count": 1}
},
{
"id": "chosen-step",
"command": "run published package with 21 aging days and one reminder sent",
"output": "step 2 / propose_reminder",
"details": {"step": 2, "reminders_sent": 1, "cap": 3, "cap_remaining_after_proposal": 1}
},
{
"id": "reminder-proposal",
"command": "inspect dogfood reminder_proposal",
"output": "email firm-reminder proposed via send-as",
"details": {
"channel": "email",
"template": "firm-reminder",
"content_digest": "sha256:baed65314b883e39fa576062a9576001a603ab58eb13c95e64c61b0216475c3e",
"gate": "requires_human_approval",
"effects_emitted": []
}
},
{
"id": "cap-state",
"command": "inspect decision and cap harness case",
"output": "within-cap proposal succeeds; at-cap run refuses",
"details": {"cap": 3, "at_cap_action": "operator_escalation_no_reminder"}
},
{
"id": "escalation-path",
"command": "inspect escalation output",
"output": "accounts_receivable_operator",
"details": {"path": "accounts_receivable_operator", "at_cap_required": true}
},
{
"id": "receipt-verification",
"command": "runx verify sha256:d3da84c3ef48e3d67a5f0e5c0a89f9025e0ff485962d4af4f8de92c6b99a727b --receipt-dir ./published-dogfood --json",
"output": "valid: true",
"details": {
"signature_mode": "production",
"findings": [],
"receipt_ref": "runx:receipt:sha256:d3da84c3ef48e3d67a5f0e5c0a89f9025e0ff485962d4af4f8de92c6b99a727b"
}
}
],
"dogfood": {
"package": "luismireles12/dunning-ladder@sha-c3ca3eb81b17",
"input": "Synthetic overdue receivable at 21 days with one reminder already sent and a three-step capped policy.",
"command": "runx skill luismireles12/dunning-ladder@sha-c3ca3eb81b17 --registry https://api.runx.ai ... --json",
"receipt_ref": "runx:receipt:sha256:d3da84c3ef48e3d67a5f0e5c0a89f9025e0ff485962d4af4f8de92c6b99a727b",
"verify_verdict": "valid: true",
"harness_cases": [
{"name": "within-cap-reminder-proposal", "status": "sealed"},
{"name": "refuse-at-cadence-cap", "status": "refused"}
]
}
}

25 changes: 25 additions & 0 deletions skills/dunning-ladder/references/harness-evidence.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"schema": "runx.dunning_ladder.harness_evidence.v1",
"generated_at": "2026-06-23T05:56:00Z",
"cli_version": "runx-cli 0.6.13",
"result": {
"status": "passed",
"case_count": 2,
"case_names": [
"within-cap-reminder-proposal",
"refuse-at-cadence-cap"
],
"receipt_ids": [
"sha256:d36dc9aed6f499872c1a7586c4ded7a6036e0f23d441d9b010d5ca41a43e5310",
"sha256:9098ca7e4bc3e53005b09282a61964f9e3f2129430010db9754883019a06ead8"
]
},
"observations": {
"chosen_step": 2,
"cap_state": "1 of 3 reminders already sent; step 2 proposed",
"reminder_proposal": "email / firm-reminder / requires_human_approval",
"escalation_path": "accounts_receivable_operator",
"at_cap": "failure with no reminder proposal"
}
}

19 changes: 19 additions & 0 deletions skills/dunning-ladder/references/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Dunning Ladder verification report

- Built with `runx-cli 0.6.13`.
- Doctor reports zero errors and warnings.
- Both required harness cases pass.
- The within-cap case chooses step 2 from a supplied cadence.
- The reminder is only a gated proposal for `send-as`.
- Content is represented by a deterministic digest.
- No email, charge, suspension, or receivable mutation occurs.
- The cadence cap is a hard upper bound.
- At the cap, the run fails and directs operator escalation.
- A record not explicitly overdue is refused.
- Inputs contain bounded references rather than private payment details.
- Human approval is required before any reminder is sent.
- Published registry ref: `luismireles12/dunning-ladder@sha-c3ca3eb81b17`.
- Public adoption page: https://runx.ai/x/luismireles12/dunning-ladder@sha-c3ca3eb81b17.
- Clean installation resolved the same package and profile digests.
- Post-publish dogfood selected step 2 under a cap of 3.
- Receipt `sha256:d3da84c3ef48e3d67a5f0e5c0a89f9025e0ff485962d4af4f8de92c6b99a727b` verifies as valid with no findings.
21 changes: 21 additions & 0 deletions skills/dunning-ladder/references/verification.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"schema": "runx.dunning_ladder.verification.v1",
"generated_at": "2026-06-23T05:58:00Z",
"cli_version": "runx-cli 0.6.13",
"doctor": {"status": "success", "errors": 0, "warnings": 0},
"harness": {"status": "passed", "case_count": 2},
"published_package": {
"ref": "luismireles12/dunning-ladder@sha-c3ca3eb81b17",
"public_url": "https://runx.ai/x/luismireles12/dunning-ladder@sha-c3ca3eb81b17",
"registry_read": "success",
"clean_install": "success",
"hosted_harness": "passed"
},
"dogfood": {
"receipt_id": "sha256:d3da84c3ef48e3d67a5f0e5c0a89f9025e0ff485962d4af4f8de92c6b99a727b",
"status": "sealed",
"verify_valid": true,
"findings": []
},
"effects_emitted": []
}
Loading