openclaw-scheduler 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +302 -0
- package/BEST-PRACTICES.md +506 -0
- package/CHANGELOG.md +82 -0
- package/CODE_OF_CONDUCT.md +22 -0
- package/CONTEXT.md +26 -0
- package/CONTRIBUTING.md +73 -0
- package/IMPLEMENTATION_SPEC.md +170 -0
- package/INSTALL-ADDITIONAL-HOST.md +333 -0
- package/INSTALL-LINUX.md +419 -0
- package/INSTALL-WINDOWS.md +305 -0
- package/INSTALL.md +364 -0
- package/JOB-QUICK-REF.md +222 -0
- package/LICENSE +21 -0
- package/QUICK-START.md +256 -0
- package/README.md +2170 -0
- package/SECURITY.md +34 -0
- package/UNINSTALL.md +129 -0
- package/UPGRADING.md +436 -0
- package/agents.js +67 -0
- package/approval.js +107 -0
- package/backup.js +390 -0
- package/bin/openclaw-scheduler.js +138 -0
- package/cli.js +1083 -0
- package/db.js +122 -0
- package/dispatch/529-recovery.mjs +204 -0
- package/dispatch/README.md +372 -0
- package/dispatch/config.example.json +24 -0
- package/dispatch/deliver-watcher.sh +57 -0
- package/dispatch/hooks.mjs +171 -0
- package/dispatch/index.mjs +1836 -0
- package/dispatch/watcher.mjs +1396 -0
- package/dispatch-queue.js +112 -0
- package/dispatcher-approvals.js +96 -0
- package/dispatcher-delivery.js +43 -0
- package/dispatcher-maintenance.js +242 -0
- package/dispatcher-shell.js +29 -0
- package/dispatcher-strategies.js +1280 -0
- package/dispatcher-utils.js +81 -0
- package/dispatcher.js +855 -0
- package/docs/adr-schedule-ownership.md +73 -0
- package/docs/gateway-contract.md +904 -0
- package/docs/plans/2026-03-09-fix-typescript-types.md +91 -0
- package/docs/plans/2026-03-09-test-coverage-gaps.md +83 -0
- package/docs/plans/2026-03-10-dispatcher-refactor.md +801 -0
- package/docs/trust-architecture.md +266 -0
- package/gateway.js +473 -0
- package/idempotency.js +119 -0
- package/index.d.ts +864 -0
- package/index.js +17 -0
- package/jobs.js +1224 -0
- package/messages.js +357 -0
- package/migrate-consolidate.js +694 -0
- package/migrate.js +125 -0
- package/package.json +130 -0
- package/paths.js +79 -0
- package/prompt-context.js +94 -0
- package/retrieval.js +176 -0
- package/runs.js +270 -0
- package/scheduler-schema.js +101 -0
- package/schema.sql +480 -0
- package/scripts/dispatch-cli-utils.mjs +65 -0
- package/scripts/inbox-consumer.mjs +288 -0
- package/scripts/stuck-detector.sh +18 -0
- package/scripts/stuck-run-detector.mjs +333 -0
- package/scripts/telegram-webhook-check.mjs +238 -0
- package/setup.mjs +724 -0
- package/shell-result.js +214 -0
- package/task-tracker.js +300 -0
- package/team-adapter.js +335 -0
- package/v02-runtime.js +599 -0
package/runs.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// Run lifecycle management
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { getDb } from './db.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create a new run for a job.
|
|
7
|
+
*/
|
|
8
|
+
export function createRun(jobId, opts = {}) {
|
|
9
|
+
const db = getDb();
|
|
10
|
+
const id = randomUUID();
|
|
11
|
+
|
|
12
|
+
db.prepare(`
|
|
13
|
+
INSERT INTO runs (
|
|
14
|
+
id, job_id, status, run_timeout_ms, session_key, session_id,
|
|
15
|
+
dispatched_at, context_summary, replay_of, idempotency_key, retry_count,
|
|
16
|
+
retry_of, triggered_by_run, dispatch_queue_id
|
|
17
|
+
)
|
|
18
|
+
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), ?, ?, ?, ?, ?, ?, ?)
|
|
19
|
+
`).run(
|
|
20
|
+
id,
|
|
21
|
+
jobId,
|
|
22
|
+
opts.status || 'running',
|
|
23
|
+
opts.run_timeout_ms || 300000,
|
|
24
|
+
opts.session_key || null,
|
|
25
|
+
opts.session_id || null,
|
|
26
|
+
opts.context_summary ? JSON.stringify(opts.context_summary) : null,
|
|
27
|
+
opts.replay_of || null,
|
|
28
|
+
opts.idempotency_key || null,
|
|
29
|
+
opts.retry_count ?? 0,
|
|
30
|
+
opts.retry_of || null,
|
|
31
|
+
opts.triggered_by_run || null,
|
|
32
|
+
opts.dispatch_queue_id || null
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return getRun(id);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get a run by ID.
|
|
40
|
+
*/
|
|
41
|
+
export function getRun(id) {
|
|
42
|
+
return getDb().prepare('SELECT * FROM runs WHERE id = ?').get(id);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get runs for a job (most recent first).
|
|
47
|
+
*/
|
|
48
|
+
export function getRunsForJob(jobId, limit = 50) {
|
|
49
|
+
return getDb().prepare(`
|
|
50
|
+
SELECT * FROM runs WHERE job_id = ? ORDER BY started_at DESC LIMIT ?
|
|
51
|
+
`).all(jobId, limit);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Update run status to finished (ok/error/timeout).
|
|
56
|
+
*/
|
|
57
|
+
export function finishRun(id, status, opts = {}) {
|
|
58
|
+
const db = getDb();
|
|
59
|
+
const run = getRun(id);
|
|
60
|
+
if (!run) return null;
|
|
61
|
+
|
|
62
|
+
const iso = run.started_at
|
|
63
|
+
? (run.started_at.includes('T') ? run.started_at : run.started_at.replace(' ', 'T') + 'Z')
|
|
64
|
+
: null;
|
|
65
|
+
const startedAt = iso ? new Date(iso).getTime() : Date.now();
|
|
66
|
+
const durationMs = Date.now() - startedAt;
|
|
67
|
+
|
|
68
|
+
db.prepare(`
|
|
69
|
+
UPDATE runs SET
|
|
70
|
+
status = ?,
|
|
71
|
+
finished_at = datetime('now'),
|
|
72
|
+
duration_ms = ?,
|
|
73
|
+
summary = COALESCE(?, summary),
|
|
74
|
+
error_message = COALESCE(?, error_message),
|
|
75
|
+
context_summary = COALESCE(?, context_summary),
|
|
76
|
+
shell_exit_code = COALESCE(?, shell_exit_code),
|
|
77
|
+
shell_signal = COALESCE(?, shell_signal),
|
|
78
|
+
shell_timed_out = COALESCE(?, shell_timed_out),
|
|
79
|
+
shell_stdout = COALESCE(?, shell_stdout),
|
|
80
|
+
shell_stderr = COALESCE(?, shell_stderr),
|
|
81
|
+
shell_stdout_path = COALESCE(?, shell_stdout_path),
|
|
82
|
+
shell_stderr_path = COALESCE(?, shell_stderr_path),
|
|
83
|
+
shell_stdout_bytes = COALESCE(?, shell_stdout_bytes),
|
|
84
|
+
shell_stderr_bytes = COALESCE(?, shell_stderr_bytes)
|
|
85
|
+
WHERE id = ? AND status IN ('pending', 'running', 'awaiting_approval', 'approved')
|
|
86
|
+
`).run(
|
|
87
|
+
status,
|
|
88
|
+
durationMs,
|
|
89
|
+
opts.summary ?? null,
|
|
90
|
+
opts.error_message ?? null,
|
|
91
|
+
opts.context_summary ? JSON.stringify(opts.context_summary) : null,
|
|
92
|
+
opts.shell_exit_code ?? null,
|
|
93
|
+
opts.shell_signal ?? null,
|
|
94
|
+
opts.shell_timed_out != null ? Number(Boolean(opts.shell_timed_out)) : null,
|
|
95
|
+
opts.shell_stdout ?? null,
|
|
96
|
+
opts.shell_stderr ?? null,
|
|
97
|
+
opts.shell_stdout_path ?? null,
|
|
98
|
+
opts.shell_stderr_path ?? null,
|
|
99
|
+
opts.shell_stdout_bytes ?? null,
|
|
100
|
+
opts.shell_stderr_bytes ?? null,
|
|
101
|
+
id
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return getRun(id);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Update last_heartbeat for a run (called by dispatcher when session activity detected).
|
|
109
|
+
*/
|
|
110
|
+
export function updateHeartbeat(id) {
|
|
111
|
+
getDb().prepare(`
|
|
112
|
+
UPDATE runs SET last_heartbeat = datetime('now') WHERE id = ?
|
|
113
|
+
`).run(id);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Update session info for a run.
|
|
118
|
+
*/
|
|
119
|
+
export function updateRunSession(id, sessionKey, sessionId) {
|
|
120
|
+
getDb().prepare(`
|
|
121
|
+
UPDATE runs SET session_key = ?, session_id = ? WHERE id = ?
|
|
122
|
+
`).run(sessionKey, sessionId, id);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Find stale runs: treats shell jobs and session-based jobs differently.
|
|
127
|
+
*
|
|
128
|
+
* - Session-based jobs (session_target != 'shell'): stale if last_heartbeat older than thresholdSeconds.
|
|
129
|
+
* These jobs emit heartbeats via gateway/session activity -- silence means stuck.
|
|
130
|
+
* - Shell jobs (session_target = 'shell'): stale only if elapsed time > run_timeout_ms.
|
|
131
|
+
* Shell jobs have no heartbeat mechanism; they run until exit. Use timeout as the upper bound.
|
|
132
|
+
* Shell jobs with run_timeout_ms IS NULL are NOT flagged -- that's getTimedOutRuns' concern.
|
|
133
|
+
*
|
|
134
|
+
* Default threshold: 90 seconds (3 missed 30s heartbeats) for agent jobs.
|
|
135
|
+
*/
|
|
136
|
+
export function getStaleRuns(thresholdSeconds = 90) {
|
|
137
|
+
if (!Number.isInteger(thresholdSeconds) || thresholdSeconds < 0) {
|
|
138
|
+
throw new Error(`getStaleRuns: thresholdSeconds must be a non-negative integer, got ${thresholdSeconds}`);
|
|
139
|
+
}
|
|
140
|
+
return getDb().prepare(`
|
|
141
|
+
SELECT r.*, j.name as job_name, j.run_timeout_ms as job_timeout_ms
|
|
142
|
+
FROM runs r
|
|
143
|
+
JOIN jobs j ON r.job_id = j.id
|
|
144
|
+
WHERE r.status = 'running'
|
|
145
|
+
AND (
|
|
146
|
+
-- Shell jobs: stale only if they exceed their absolute run_timeout_ms
|
|
147
|
+
(j.session_target = 'shell'
|
|
148
|
+
AND r.run_timeout_ms IS NOT NULL
|
|
149
|
+
AND (julianday('now') - julianday(r.started_at)) * 86400000 > r.run_timeout_ms)
|
|
150
|
+
OR
|
|
151
|
+
-- Session-based jobs: stale if last_heartbeat not updated within threshold,
|
|
152
|
+
-- or if they never heartbeated and started_at is past the threshold (startup grace)
|
|
153
|
+
(j.session_target != 'shell'
|
|
154
|
+
AND (r.last_heartbeat < datetime('now', '-' || ? || ' seconds')
|
|
155
|
+
OR (r.last_heartbeat IS NULL
|
|
156
|
+
AND r.started_at < datetime('now', '-' || ? || ' seconds'))))
|
|
157
|
+
)
|
|
158
|
+
`).all(thresholdSeconds, thresholdSeconds);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Find runs that have exceeded their absolute timeout (run_timeout_ms).
|
|
163
|
+
*
|
|
164
|
+
* Important overlap note: this function may return runs also returned by
|
|
165
|
+
* `getStaleRuns`. Both queries match running runs that have exceeded their
|
|
166
|
+
* run_timeout_ms. Callers must check the run's current status before acting
|
|
167
|
+
* on results to avoid double-processing (e.g., finishing an already-finished run).
|
|
168
|
+
*
|
|
169
|
+
* This function serves as the fallback for when heartbeat-based stale detection
|
|
170
|
+
* is not available -- for example, shell jobs that have no heartbeat mechanism,
|
|
171
|
+
* or agent jobs whose first heartbeat has not yet arrived. Unlike `getStaleRuns`,
|
|
172
|
+
* which requires a heartbeat timestamp to compare against, this function uses
|
|
173
|
+
* only the run's started_at and run_timeout_ms columns.
|
|
174
|
+
*/
|
|
175
|
+
export function getTimedOutRuns() {
|
|
176
|
+
return getDb().prepare(`
|
|
177
|
+
SELECT r.*, j.name as job_name, j.run_timeout_ms as job_timeout_ms
|
|
178
|
+
FROM runs r
|
|
179
|
+
JOIN jobs j ON r.job_id = j.id
|
|
180
|
+
WHERE r.status = 'running'
|
|
181
|
+
AND r.run_timeout_ms IS NOT NULL
|
|
182
|
+
AND (julianday('now') - julianday(r.started_at)) * 86400000 > r.run_timeout_ms
|
|
183
|
+
`).all();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get active running runs (for heartbeat checking).
|
|
188
|
+
*/
|
|
189
|
+
export function getRunningRuns() {
|
|
190
|
+
return getDb().prepare(`
|
|
191
|
+
SELECT r.*, j.name as job_name, j.run_timeout_ms as job_timeout_ms
|
|
192
|
+
FROM runs r
|
|
193
|
+
JOIN jobs j ON r.job_id = j.id
|
|
194
|
+
WHERE r.status = 'running'
|
|
195
|
+
`).all();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Prune old runs (keep last N per job).
|
|
200
|
+
*/
|
|
201
|
+
export function pruneRuns(keepPerJob = 100) {
|
|
202
|
+
const db = getDb();
|
|
203
|
+
const jobs = db.prepare('SELECT id FROM jobs').all();
|
|
204
|
+
|
|
205
|
+
for (const job of jobs) {
|
|
206
|
+
db.prepare(`
|
|
207
|
+
DELETE FROM runs WHERE job_id = ? AND id NOT IN (
|
|
208
|
+
SELECT id FROM runs WHERE job_id = ? ORDER BY started_at DESC LIMIT ?
|
|
209
|
+
)
|
|
210
|
+
`).run(job.id, job.id, keepPerJob);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get all running runs for jobs in a given resource pool.
|
|
216
|
+
*/
|
|
217
|
+
export function getRunningRunsByPool(poolName) {
|
|
218
|
+
return getDb().prepare(`
|
|
219
|
+
SELECT r.*, j.name as job_name FROM runs r
|
|
220
|
+
JOIN jobs j ON r.job_id = j.id
|
|
221
|
+
WHERE r.status = 'running' AND j.resource_pool = ?
|
|
222
|
+
`).all(poolName);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Store or update the context_summary JSON for a run.
|
|
227
|
+
*/
|
|
228
|
+
export function updateContextSummary(runId, summaryObj) {
|
|
229
|
+
const json = typeof summaryObj === 'string' ? summaryObj : JSON.stringify(summaryObj);
|
|
230
|
+
getDb().prepare(`
|
|
231
|
+
UPDATE runs SET context_summary = ? WHERE id = ?
|
|
232
|
+
`).run(json, runId);
|
|
233
|
+
return getRun(runId);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Persist v0.2 runtime outcomes on a run record.
|
|
238
|
+
*
|
|
239
|
+
* Only updates columns present in the outcomes object. Values that are objects
|
|
240
|
+
* are JSON-stringified before storage.
|
|
241
|
+
*
|
|
242
|
+
* Valid columns: identity_resolved, trust_evaluation, authorization_decision,
|
|
243
|
+
* authorization_proof_verification, evidence_record, credential_handoff_summary.
|
|
244
|
+
*/
|
|
245
|
+
const V02_OUTCOME_COLUMNS = new Set([
|
|
246
|
+
'identity_resolved',
|
|
247
|
+
'trust_evaluation',
|
|
248
|
+
'authorization_decision',
|
|
249
|
+
'authorization_proof_verification',
|
|
250
|
+
'evidence_record',
|
|
251
|
+
'credential_handoff_summary',
|
|
252
|
+
]);
|
|
253
|
+
|
|
254
|
+
export function persistV02Outcomes(runId, outcomes) {
|
|
255
|
+
if (!outcomes || typeof outcomes !== 'object') return;
|
|
256
|
+
if (!runId) return;
|
|
257
|
+
const db = getDb();
|
|
258
|
+
const fields = [];
|
|
259
|
+
const values = [];
|
|
260
|
+
for (const [key, value] of Object.entries(outcomes)) {
|
|
261
|
+
if (value === undefined) continue;
|
|
262
|
+
if (!V02_OUTCOME_COLUMNS.has(key)) continue;
|
|
263
|
+
if (!/^[a-z_]+$/.test(key)) throw new Error(`persistV02Outcomes: invalid column name "${key}"`);
|
|
264
|
+
fields.push(`${key} = ?`);
|
|
265
|
+
values.push(value != null && typeof value === 'object' ? JSON.stringify(value) : value);
|
|
266
|
+
}
|
|
267
|
+
if (fields.length === 0) return;
|
|
268
|
+
values.push(runId);
|
|
269
|
+
db.prepare(`UPDATE runs SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
|
270
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export const SCHEDULER_SCHEMAS = {
|
|
2
|
+
jobs: {
|
|
3
|
+
type: 'object',
|
|
4
|
+
required: ['name', 'payload_message', 'run_timeout_ms'], // origin also required for non-child jobs
|
|
5
|
+
fields: {
|
|
6
|
+
name: { type: 'string', maxLength: 200 },
|
|
7
|
+
enabled: { type: 'boolean', default: true },
|
|
8
|
+
schedule_cron: { type: 'string', requiredFor: 'root jobs' },
|
|
9
|
+
schedule_tz: { type: 'string', default: 'UTC' },
|
|
10
|
+
session_target: { type: 'string', enum: ['main', 'isolated', 'shell'], default: 'isolated' },
|
|
11
|
+
payload_kind: { type: 'string', enum: ['systemEvent', 'agentTurn', 'shellCommand'] },
|
|
12
|
+
payload_message: { type: 'string', maxLength: 100000 },
|
|
13
|
+
payload_model: { type: 'string', nullable: true },
|
|
14
|
+
payload_thinking: { type: 'string', nullable: true },
|
|
15
|
+
payload_timeout_seconds: { type: 'integer', min: 1, default: 120 },
|
|
16
|
+
execution_intent: { type: 'string', enum: ['execute', 'plan'], default: 'execute' },
|
|
17
|
+
execution_read_only: { type: 'boolean', default: false },
|
|
18
|
+
overlap_policy: { type: 'string', enum: ['skip', 'allow', 'queue'], default: 'skip' },
|
|
19
|
+
run_timeout_ms: { type: 'integer', min: 1, required: true, description: 'Required: max ms a run may execute before timeout (no default -- caller must be explicit)' },
|
|
20
|
+
max_queued_dispatches: { type: 'integer', min: 1, default: 25 },
|
|
21
|
+
max_pending_approvals: { type: 'integer', min: 1, default: 10 },
|
|
22
|
+
max_trigger_fanout: { type: 'integer', min: 1, default: 25 },
|
|
23
|
+
delivery_mode: { type: 'string', enum: ['announce', 'announce-always', 'none'], default: 'announce' },
|
|
24
|
+
delivery_channel: { type: 'string', nullable: true },
|
|
25
|
+
delivery_to: { type: 'string', nullable: true },
|
|
26
|
+
parent_id: { type: 'string', nullable: true },
|
|
27
|
+
trigger_on: { type: 'string', enum: ['success', 'failure', 'complete'], nullable: true },
|
|
28
|
+
trigger_delay_s: { type: 'integer', min: 0, default: 0 },
|
|
29
|
+
trigger_condition: { type: 'string', nullable: true, examples: ['contains:ALERT', 'regex:ERROR.*critical'] },
|
|
30
|
+
max_retries: { type: 'integer', min: 0, default: 0 },
|
|
31
|
+
payload_scope: { type: 'string', enum: ['own', 'global'], default: 'own' },
|
|
32
|
+
resource_pool: { type: 'string', nullable: true },
|
|
33
|
+
delivery_guarantee: { type: 'string', enum: ['at-most-once', 'at-least-once'], default: 'at-most-once' },
|
|
34
|
+
job_class: { type: 'string', enum: ['standard', 'pre_compaction_flush'], default: 'standard' },
|
|
35
|
+
approval_required: { type: 'boolean', default: false },
|
|
36
|
+
approval_timeout_s: { type: 'integer', min: 1, default: 3600 },
|
|
37
|
+
approval_auto: { type: 'string', enum: ['approve', 'reject'], default: 'reject' },
|
|
38
|
+
context_retrieval: { type: 'string', enum: ['none', 'recent', 'hybrid'], default: 'none' },
|
|
39
|
+
context_retrieval_limit: { type: 'integer', min: 1, default: 5 },
|
|
40
|
+
output_store_limit_bytes: { type: 'integer', min: 128, default: 65536 },
|
|
41
|
+
output_excerpt_limit_bytes: { type: 'integer', min: 64, default: 2000 },
|
|
42
|
+
output_summary_limit_bytes: { type: 'integer', min: 64, default: 5000 },
|
|
43
|
+
output_offload_threshold_bytes: { type: 'integer', min: 128, default: 65536 },
|
|
44
|
+
preferred_session_key: { type: 'string', nullable: true },
|
|
45
|
+
auth_profile: { type: 'string', nullable: true, description: 'Auth profile override: null=default, "inherit"=main session profile, or "provider:label"' },
|
|
46
|
+
delivery_opt_out_reason: { type: 'string', nullable: true, maxLength: 256 },
|
|
47
|
+
delete_after_run: { type: 'boolean', default: false },
|
|
48
|
+
run_now: { type: 'boolean', default: false, note: 'create-time convenience flag' },
|
|
49
|
+
|
|
50
|
+
// v0.2 Identity
|
|
51
|
+
identity_principal: { type: 'string', nullable: true },
|
|
52
|
+
identity_run_as: { type: 'string', nullable: true },
|
|
53
|
+
identity_attestation: { type: 'string', nullable: true },
|
|
54
|
+
identity_ref: { type: 'string', nullable: true },
|
|
55
|
+
identity_subject_kind: { type: 'string', enum: ['agent', 'service', 'workload', 'user', 'composite', 'delegated-agent', 'unknown'], nullable: true },
|
|
56
|
+
identity_subject_principal: { type: 'string', nullable: true },
|
|
57
|
+
identity_trust_level: { type: 'string', enum: ['untrusted', 'restricted', 'supervised', 'autonomous'], nullable: true },
|
|
58
|
+
identity_delegation_mode: { type: 'string', enum: ['none', 'on-behalf-of', 'impersonation'], nullable: true },
|
|
59
|
+
identity: { type: 'string', nullable: true, description: 'JSON blob: full identity declaration' },
|
|
60
|
+
|
|
61
|
+
// v0.2 Authorization Proof
|
|
62
|
+
authorization_proof_ref: { type: 'string', nullable: true },
|
|
63
|
+
authorization_proof: { type: 'string', nullable: true, description: 'JSON blob: authorization proof structure' },
|
|
64
|
+
|
|
65
|
+
// v0.2 Authorization
|
|
66
|
+
authorization_ref: { type: 'string', nullable: true },
|
|
67
|
+
authorization: { type: 'string', nullable: true, description: 'JSON blob: authorization policy declaration' },
|
|
68
|
+
|
|
69
|
+
// v0.2 Evidence
|
|
70
|
+
evidence_ref: { type: 'string', nullable: true },
|
|
71
|
+
evidence: { type: 'string', nullable: true, description: 'JSON blob: evidence collection declaration' },
|
|
72
|
+
|
|
73
|
+
// v0.2 Contract
|
|
74
|
+
contract_required_trust_level: { type: 'string', enum: ['untrusted', 'restricted', 'supervised', 'autonomous'], nullable: true },
|
|
75
|
+
contract_trust_enforcement: { type: 'string', enum: ['none', 'warn', 'block', 'advisory', 'strict'], nullable: true, description: 'advisory/strict normalize to warn/block at runtime' },
|
|
76
|
+
contract_sandbox: { type: 'string', nullable: true, description: 'JSON blob: sandbox constraints' },
|
|
77
|
+
contract_allowed_paths: { type: 'string', nullable: true, description: 'JSON blob: allowed filesystem paths' },
|
|
78
|
+
contract_network: { type: 'string', nullable: true, description: 'JSON blob: network access policy' },
|
|
79
|
+
contract_max_cost_usd: { type: 'number', nullable: true, min: 0 },
|
|
80
|
+
contract_audit: { type: 'string', nullable: true, description: 'JSON blob: audit configuration' },
|
|
81
|
+
child_credential_policy: { type: 'string', nullable: true, enum: ['none', 'inherit', 'downscope', 'independent'], description: 'Credential flow policy for child tasks' },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
runs: {
|
|
85
|
+
statuses: ['pending', 'running', 'ok', 'error', 'timeout', 'skipped', 'awaiting_approval', 'approved', 'cancelled', 'crashed'],
|
|
86
|
+
key_fields: ['job_id', 'status', 'started_at', 'finished_at', 'summary', 'error_message', 'shell_exit_code', 'shell_signal', 'shell_timed_out', 'shell_stdout', 'shell_stderr', 'shell_stdout_path', 'shell_stderr_path', 'shell_stdout_bytes', 'shell_stderr_bytes', 'retry_of', 'triggered_by_run', 'dispatch_queue_id', 'idempotency_key', 'identity_resolved', 'trust_evaluation', 'authorization_decision', 'authorization_proof_verification', 'evidence_record', 'credential_handoff_summary'],
|
|
87
|
+
},
|
|
88
|
+
approvals: {
|
|
89
|
+
statuses: ['pending', 'approved', 'rejected', 'timed_out', 'dispatched'],
|
|
90
|
+
key_fields: ['job_id', 'run_id', 'dispatch_queue_id', 'requested_at', 'resolved_at', 'resolved_by', 'notes'],
|
|
91
|
+
},
|
|
92
|
+
dispatches: {
|
|
93
|
+
kinds: ['manual', 'chain', 'retry'],
|
|
94
|
+
statuses: ['pending', 'claimed', 'awaiting_approval', 'done', 'cancelled'],
|
|
95
|
+
key_fields: ['job_id', 'dispatch_kind', 'status', 'scheduled_for', 'source_run_id', 'retry_of_run_id'],
|
|
96
|
+
},
|
|
97
|
+
messages: {
|
|
98
|
+
kinds: ['text', 'task', 'result', 'status', 'system', 'spawn', 'decision', 'constraint', 'fact', 'preference'],
|
|
99
|
+
statuses: ['pending', 'delivered', 'read', 'expired', 'failed'],
|
|
100
|
+
},
|
|
101
|
+
};
|