nx_cron.nx
buildroot/runtime/nx_cron.nx
about
nx_cron.nx -- JOB-REGISTRY + DISPATCH-LOOP layer above the FULLY_WIRED
nx_race_timing.nx primitive (sealed 8-mode timing taxonomy). Replaces
/etc/crontab on Synology DSM.
AUDIT-FIRST CORRECTION (2026-05-20, post-creation): user caught that I
built this without checking for existing prior art. nx_race_timing.nx
already ships the canonical sealed enum of 8 timing modes (MANUAL / EVENT /
SCHEDULED / CONTINUOUS / IDLE / THRESHOLD / PROBABILISTIC / QUORUM) + the
classifier helpers (is_reactive / needs_config / parse). My nx_cron 9-kind
enum maps directly:
NX_CRON_KIND_INTERVAL_SECONDS/MINUTES/HOURS -> NX_RACE_TIMING_CONTINUOUS
NX_CRON_KIND_DAILY_AT_HOUR/WEEKLY_AT_DOW/MONTHLY_AT_DOM -> NX_RACE_TIMING_SCHEDULED
NX_CRON_KIND_ON_EVENT -> NX_RACE_TIMING_EVENT
NX_CRON_KIND_ON_DEMAND -> NX_RACE_TIMING_MANUAL
NX_CRON_KIND_IDLE_OPPORTUNISTIC -> NX_RACE_TIMING_IDLE
nx_cron does NOT replace nx_race_timing -- it COMPOSES against it.
nx_race_timing classifies a timing mode; nx_cron registers + dispatches
JOBS attached to those timing modes. Different layer:
- nx_race_timing : WHAT timing mode is this? (taxonomy + classifier)
- nx_cron : HOW do we run job X on schedule Y? (registry + loop)
When graduated, nx_cron_register_job will call into nx_race_timing helpers
(nx_race_timing_is_valid, nx_race_timing_needs_config) to validate the
declared schedule before persisting the job. Also composes with the
already-shipped nx_timerfd.nx for the actual fire-time wakeup.
Per feedback-no-temporary-third-party-scaffolding-when-substrate-arc-exists
audit-first rule: ls nxc2/runtime/ before authoring new primitives.
module: nishi-core.ops.cron
depends: nishi-core.race.timing + nishi-core.io.syscalls
disk_kb: 5
capability: OPS
wired_status: PARTIAL_WIRED
WIRED (2026-06-13, W-RE-CRON-001 -- the in-memory DISPATCH CORE, gate-proven
by nx_cms_schedule_gate -> a real schedule->publish):
- JOB_REGISTRY (static in-memory job table; register/unregister/find
dependencies 2 imports · 3 importers
imports: nx_syscalls.nxnx_race_timing.nx
imported by: nx_cms_booking.nxnx_cms_schedule_exceed_gate.nxnx_cms_schedule_gate.nx
structs
| 161 | struct NxCronJob |
consts
| 96 | const NX_CRON_KIND_INTERVAL_SECONDS: i64 = 1 // every N seconds (CONTINUOUS) |
| 97 | const NX_CRON_KIND_INTERVAL_MINUTES: i64 = 2 |
| 98 | const NX_CRON_KIND_INTERVAL_HOURS: i64 = 3 |
| 99 | const NX_CRON_KIND_DAILY_AT_HOUR: i64 = 4 // SCHEDULED at HH:MM |
| 100 | const NX_CRON_KIND_WEEKLY_AT_DOW: i64 = 5 // SCHEDULED day-of-week |
| 101 | const NX_CRON_KIND_MONTHLY_AT_DOM: i64 = 6 |
| 102 | const NX_CRON_KIND_ON_EVENT: i64 = 7 // EVENT-driven; not periodic |
| 103 | const NX_CRON_KIND_ON_DEMAND: i64 = 8 // MANUAL; explicit trigger only |
| 104 | const NX_CRON_KIND_IDLE_OPPORTUNISTIC: i64 = 9 // IDLE; substrate runs when |
| 106 | const NX_CRON_KIND_AT_UNIX_ONCE: i64 = 10 // SCHEDULED one-shot at absolute |
| 125 | const NX_CRON_MISSED_SKIP: i64 = 1 // missed fires forgotten |
| 126 | const NX_CRON_MISSED_CATCH_UP_ONCE: i64 = 2 // fire ONCE if any missed |
| 127 | const NX_CRON_MISSED_CATCH_UP_ALL: i64 = 3 // fire every missed (rare; |
| 132 | const NX_CRON_VERDICT_OK: i64 = 0 |
| 133 | const NX_CRON_VERDICT_JOB_REGISTERED: i64 = 1 |
| 134 | const NX_CRON_VERDICT_JOB_UPDATED: i64 = 2 |
| 135 | const NX_CRON_VERDICT_JOB_UNREGISTERED: i64 = 3 |
| 136 | const NX_CRON_VERDICT_JOB_FIRED: i64 = 4 |
| 137 | const NX_CRON_VERDICT_JOB_FAILED: i64 = 5 |
| 138 | const NX_CRON_VERDICT_FAIL_BAD_SCHEDULE: i64 = 6 |
| 139 | const NX_CRON_VERDICT_FAIL_DUPLICATE_NAME: i64 = 7 |
| 140 | const NX_CRON_VERDICT_FAIL_SUPERVISOR_MISSING: i64 = 8 |
| 141 | const NX_CRON_VERDICT_FAIL_DEPENDENCY_MISSING: i64 = 9 // PARTIAL_WIRED default |
| 186 | const NX_CRON_ACTION_NONE: i64 = 0 |
| 187 | const NX_CRON_ACTION_PUBLISH: i64 = 1 |
| 197 | const NX_CRON_MAX_JOBS: i64 = 64 |
| 198 | const NX_CRON_REC_BYTES: i64 = 128 // 16 i64 slots per job |
| 199 | const NX_CRON_TABLE_BYTES: i64 = 8192 // 64 * 128 |
| 200 | const NX_CRON_FIRED_CAP: i64 = 64 |
| 203 | const CJ_IN_USE: i64 = 0 |
| 204 | const CJ_NAME_HASH: i64 = 1 |
| 205 | const CJ_NAME_LEN: i64 = 2 |
| 206 | const CJ_KIND: i64 = 3 |
| 207 | const CJ_INTERVAL: i64 = 4 |
| 208 | const CJ_FIRE_AT: i64 = 5 // absolute next-fire unix seconds |
| 209 | const CJ_MISSED: i64 = 6 |
| 210 | const CJ_ACTION_K: i64 = 7 |
| 211 | const CJ_ACTION_A: i64 = 8 |
| 212 | const CJ_ENABLED: i64 = 9 |
| 213 | const CJ_FIRED_CNT: i64 = 10 |
| 214 | const CJ_LAST_FIRE: i64 = 11 |
| 215 | const CJ_RECURRING: i64 = 12 |
functions
| 109 | func nx_cron_kind_name(k: i64) -> *u8 |
| 143 | func nx_cron_verdict_name(v: i64) -> *u8 |
| 224 | func nx_cron_init() -> i64 |
| 244 | func nx_cron_kind_race_mode(k: i64) -> i64 called by 1: nx_cron_schedule_ok |
| 260 | func nx_cron_schedule_ok(job_ptr: *NxCronJob) -> i64 |
| 270 | func nx_cron_recurring(k: i64) -> i64 called by 1: nx_cron_register_job |
| 282 | func nx_cron_name_hash(p: *u8, n: i64) -> i64 |
| 290 | func nx_cron_rec(slot: i64) -> *i64 |
| 295 | func nx_cron_find(nh: i64, nl: i64) -> i64 |
| 306 | func nx_cron_fired_push(kind: i64, arg: i64) -> i64 |
| 316 | func nx_cron_drain_fired(out_kind: *i64, out_arg: *i64) -> i64 |
| 329 | func nx_cron_fire_slot(rec: *i64, now: i64) -> i64 |
| 346 | func nx_cron_register_job(job_ptr: *NxCronJob) -> i64 |
| 385 | func nx_cron_unregister_job(job_name_ptr: *u8, job_name_len: i64) -> i64 |
| 400 | func nx_cron_fire_now(job_name_ptr: *u8, job_name_len: i64) -> i64 |
| 416 | func nx_cron_tick(current_unix_seconds: i64) -> i64 |
| 438 | func nx_cron_count_jobs() -> i64 |
| 452 | func nx_cron_job_fired_count(job_name_ptr: *u8, job_name_len: i64) -> i64 |
| 462 | func nx_cron_get_last_verdict() -> i64 calls 1: nx_cron_init |