@deftai/directive-core 0.88.0 → 0.89.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/dist/authz/actions.d.ts +55 -0
- package/dist/authz/actions.js +125 -0
- package/dist/authz/classify.d.ts +23 -0
- package/dist/authz/classify.js +217 -0
- package/dist/authz/closed-verb.d.ts +42 -0
- package/dist/authz/closed-verb.js +279 -0
- package/dist/authz/evaluate.d.ts +41 -0
- package/dist/authz/evaluate.js +298 -0
- package/dist/authz/index.d.ts +15 -0
- package/dist/authz/index.js +15 -0
- package/dist/authz/origin.d.ts +28 -0
- package/dist/authz/origin.js +64 -0
- package/dist/authz/paths.d.ts +6 -0
- package/dist/authz/paths.js +19 -0
- package/dist/authz/store.d.ts +40 -0
- package/dist/authz/store.js +317 -0
- package/dist/authz/templates.d.ts +139 -0
- package/dist/authz/templates.js +241 -0
- package/dist/authz/types.d.ts +111 -0
- package/dist/authz/types.js +41 -0
- package/dist/authz/verb-classification.d.ts +44 -0
- package/dist/authz/verb-classification.js +237 -0
- package/dist/branch/evaluate.js +6 -1
- package/dist/cache/fetch.js +10 -5
- package/dist/cache/io.d.ts +9 -2
- package/dist/cache/io.js +30 -10
- package/dist/cache/task-cache/store.js +11 -7
- package/dist/capacity/backfill.js +10 -4
- package/dist/category-b-namespace/index.js +10 -4
- package/dist/codebase/default-extractor.js +3 -0
- package/dist/codebase/map.js +11 -4
- package/dist/doctor/doctor-state.js +28 -4
- package/dist/doctor/main.d.ts +10 -0
- package/dist/doctor/main.js +160 -0
- package/dist/doctor/types.d.ts +11 -0
- package/dist/escalation/actions.d.ts +63 -0
- package/dist/escalation/actions.js +137 -0
- package/dist/escalation/index.d.ts +8 -0
- package/dist/escalation/index.js +8 -0
- package/dist/escalation/paths.d.ts +3 -0
- package/dist/escalation/paths.js +10 -0
- package/dist/escalation/store.d.ts +17 -0
- package/dist/escalation/store.js +172 -0
- package/dist/escalation/types.d.ts +73 -0
- package/dist/escalation/types.js +43 -0
- package/dist/eval/crud-telemetry.js +30 -10
- package/dist/eval/health.js +22 -7
- package/dist/eval/readback.js +11 -10
- package/dist/eval/run.js +32 -16
- package/dist/events/attribution-enrichment.js +10 -4
- package/dist/finish-loop/directive-finish-loop.d.ts +37 -0
- package/dist/finish-loop/directive-finish-loop.js +239 -0
- package/dist/finish-loop/grant-gate.d.ts +31 -0
- package/dist/finish-loop/grant-gate.js +169 -0
- package/dist/finish-loop/index.d.ts +11 -0
- package/dist/finish-loop/index.js +11 -0
- package/dist/finish-loop/main.d.ts +35 -0
- package/dist/finish-loop/main.js +365 -0
- package/dist/finish-loop/pr-finish-loop.d.ts +34 -0
- package/dist/finish-loop/pr-finish-loop.js +217 -0
- package/dist/finish-loop/progress.d.ts +17 -0
- package/dist/finish-loop/progress.js +45 -0
- package/dist/finish-loop/queue.d.ts +16 -0
- package/dist/finish-loop/queue.js +65 -0
- package/dist/finish-loop/types.d.ts +52 -0
- package/dist/finish-loop/types.js +10 -0
- package/dist/fs/contained-write.d.ts +101 -0
- package/dist/fs/contained-write.js +281 -0
- package/dist/hooks/classify/classify.d.ts +10 -0
- package/dist/hooks/classify/classify.js +37 -0
- package/dist/hooks/classify/index.d.ts +14 -0
- package/dist/hooks/classify/index.js +13 -0
- package/dist/hooks/classify/paths.d.ts +22 -0
- package/dist/hooks/classify/paths.js +72 -0
- package/dist/hooks/classify/payload.d.ts +16 -0
- package/dist/hooks/classify/payload.js +45 -0
- package/dist/hooks/classify/stdin.d.ts +12 -0
- package/dist/hooks/classify/stdin.js +63 -0
- package/dist/hooks/classify/tool-name.d.ts +18 -0
- package/dist/hooks/classify/tool-name.js +85 -0
- package/dist/hooks/classify/types.d.ts +41 -0
- package/dist/hooks/classify/types.js +7 -0
- package/dist/hooks/dispatcher.d.ts +21 -18
- package/dist/hooks/dispatcher.js +212 -163
- package/dist/hooks/fixtures/cases.d.ts +44 -0
- package/dist/hooks/fixtures/cases.js +595 -0
- package/dist/hooks/fixtures/index.d.ts +2 -0
- package/dist/hooks/fixtures/index.js +2 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/init-deposit/agent-hooks.js +27 -8
- package/dist/init-deposit/gitignore.js +11 -3
- package/dist/init-deposit/headless-manifest.js +12 -4
- package/dist/init-deposit/migrate.d.ts +1 -1
- package/dist/init-deposit/migrate.js +13 -3
- package/dist/init-deposit/prettierignore.js +10 -3
- package/dist/init-deposit/scaffold.js +35 -26
- package/dist/init-deposit/xbrief-projections.js +11 -4
- package/dist/intake/candidates-log.js +46 -35
- package/dist/intake/github-body-cli.d.ts +6 -0
- package/dist/intake/github-body-cli.js +17 -0
- package/dist/intake/github-body.d.ts +46 -3
- package/dist/intake/github-body.js +191 -20
- package/dist/intake/issue-emit.d.ts +1 -0
- package/dist/intake/issue-emit.js +41 -23
- package/dist/lifecycle/event-detect.js +12 -4
- package/dist/lifecycle/events.js +29 -20
- package/dist/lifecycle/lifecycle-hygiene.js +9 -3
- package/dist/packs/pack-render.js +19 -6
- package/dist/plan-sequence/store.js +11 -4
- package/dist/policy/hotfix-criteria.d.ts +79 -0
- package/dist/policy/hotfix-criteria.js +197 -0
- package/dist/policy/index.d.ts +4 -0
- package/dist/policy/index.js +31 -1
- package/dist/policy/intent-ceiling.d.ts +79 -0
- package/dist/policy/intent-ceiling.js +181 -0
- package/dist/policy/no-deft-directive.js +10 -3
- package/dist/policy/org-force-on-migration.js +9 -5
- package/dist/policy/require-human-merge.d.ts +75 -0
- package/dist/policy/require-human-merge.js +324 -0
- package/dist/policy/resolve.js +17 -6
- package/dist/policy/runtime-authority.d.ts +15 -2
- package/dist/policy/runtime-authority.js +23 -3
- package/dist/policy/write-fence.d.ts +78 -0
- package/dist/policy/write-fence.js +164 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +10 -0
- package/dist/pr-wait-mergeable/cascade.js +28 -0
- package/dist/preflight/evaluate.js +10 -0
- package/dist/product-signal/consent.js +21 -5
- package/dist/product-signal/submit.js +22 -12
- package/dist/release/build-dist-runner.js +5 -2
- package/dist/release/build-dist.d.ts +19 -1
- package/dist/release/build-dist.js +50 -2
- package/dist/release/native-steps.js +7 -2
- package/dist/release-publish/pipeline.d.ts +13 -0
- package/dist/release-publish/pipeline.js +46 -1
- package/dist/scope/audit-log.js +19 -24
- package/dist/scope/decompose.js +10 -5
- package/dist/scope/decomposed-refs.js +18 -3
- package/dist/scope/demote.js +9 -2
- package/dist/scope/undo.js +9 -2
- package/dist/session/release-availability.js +26 -6
- package/dist/session/ritual-sentinel.js +19 -12
- package/dist/session/session-start.js +7 -0
- package/dist/staleness-tickler/state.js +26 -6
- package/dist/swarm/launch.js +13 -3
- package/dist/swarm/routing.js +9 -3
- package/dist/task-surface/index.js +24 -9
- package/dist/triage/bootstrap/gitignore.js +53 -10
- package/dist/triage/cache-path.js +10 -3
- package/dist/triage/welcome/summary.js +11 -4
- package/dist/triage/welcome/writers.js +45 -16
- package/dist/validate-content/validate-links.js +5 -0
- package/dist/value/readback.js +11 -6
- package/dist/vbrief-activate/activate.js +12 -4
- package/dist/vbrief-build/project-definition-io.js +14 -6
- package/dist/verify-source/contained-writes.d.ts +59 -0
- package/dist/verify-source/contained-writes.js +272 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/openclaw-tier1.js +14 -1
- package/dist/verify-source/verify-stubs.js +3 -0
- package/dist/xbrief-migrate/migrate-project.js +26 -12
- package/dist/xbrief-migrate/transforms.d.ts +4 -2
- package/dist/xbrief-migrate/transforms.js +37 -14
- package/package.json +15 -3
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator-facing escalation actions: file, list, resolve, batch-approve (#518).
|
|
3
|
+
*/
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import { listEscalations, listOpenEscalations, loadEscalation, parseEscalation, saveEscalation, utcIso, validateEscalationType, } from "./store.js";
|
|
6
|
+
import { BATCH_APPROVABLE_TYPES, DEFAULT_SLA_HOURS, ESCALATION_SCHEMA_VERSION, isBatchApprovableType, isEscalationDecision, } from "./types.js";
|
|
7
|
+
function newEscalationId(now) {
|
|
8
|
+
const ts = (now ?? new Date())
|
|
9
|
+
.toISOString()
|
|
10
|
+
.replace(/[-:TZ.]/g, "")
|
|
11
|
+
.slice(0, 14);
|
|
12
|
+
const suffix = randomBytes(3).toString("hex");
|
|
13
|
+
return `esc-${ts}-${suffix}`;
|
|
14
|
+
}
|
|
15
|
+
export function fileEscalation(input) {
|
|
16
|
+
const type = validateEscalationType(input.type);
|
|
17
|
+
const title = input.title.trim();
|
|
18
|
+
if (title.length === 0) {
|
|
19
|
+
throw new Error("escalation title must be non-empty");
|
|
20
|
+
}
|
|
21
|
+
const createdAt = utcIso(input.now);
|
|
22
|
+
const event = {
|
|
23
|
+
schemaVersion: ESCALATION_SCHEMA_VERSION,
|
|
24
|
+
id: input.id?.trim() || newEscalationId(input.now),
|
|
25
|
+
agentId: (input.agentId ?? "agent").trim() || "agent",
|
|
26
|
+
type,
|
|
27
|
+
title,
|
|
28
|
+
body: input.body ?? "",
|
|
29
|
+
contextRefs: input.contextRefs ?? [],
|
|
30
|
+
createdAt,
|
|
31
|
+
slaHours: input.slaHours !== undefined && input.slaHours > 0 ? input.slaHours : DEFAULT_SLA_HOURS[type],
|
|
32
|
+
status: "open",
|
|
33
|
+
dangerous: input.dangerous === true,
|
|
34
|
+
resolution: null,
|
|
35
|
+
};
|
|
36
|
+
// Round-trip through parse to guarantee store shape.
|
|
37
|
+
const parsed = parseEscalation(event);
|
|
38
|
+
if (parsed === null) {
|
|
39
|
+
throw new Error("internal: filed escalation failed validation");
|
|
40
|
+
}
|
|
41
|
+
saveEscalation(input.projectRoot, parsed);
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
export function resolveEscalation(input) {
|
|
45
|
+
const decisionRaw = input.decision.trim().toLowerCase();
|
|
46
|
+
if (!isEscalationDecision(decisionRaw)) {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
code: "invalid-decision",
|
|
50
|
+
message: `invalid decision '${input.decision}'; expected one of: approved, denied, answered, dismissed`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const existing = loadEscalation(input.projectRoot, input.id);
|
|
54
|
+
if (existing === null) {
|
|
55
|
+
return {
|
|
56
|
+
ok: false,
|
|
57
|
+
code: "not-found",
|
|
58
|
+
message: `escalation not found: ${input.id}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (existing.status !== "open") {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
code: "already-resolved",
|
|
65
|
+
message: `escalation already resolved: ${input.id}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const resolved = {
|
|
69
|
+
...existing,
|
|
70
|
+
status: "resolved",
|
|
71
|
+
resolution: {
|
|
72
|
+
decision: decisionRaw,
|
|
73
|
+
resolvedAt: utcIso(input.now),
|
|
74
|
+
resolvedBy: (input.actor ?? "operator").trim() || "operator",
|
|
75
|
+
note: input.note ?? null,
|
|
76
|
+
answer: input.answer ?? null,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
saveEscalation(input.projectRoot, resolved);
|
|
80
|
+
return { ok: true, event: resolved };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Bulk-approve only `cmd_approval` and `question` open items.
|
|
84
|
+
* design_decision / approval / resource / external must use individual resolve.
|
|
85
|
+
* Dangerous items are skipped unless `includeDangerous` is true.
|
|
86
|
+
*/
|
|
87
|
+
export function batchApproveEscalations(input) {
|
|
88
|
+
const open = listOpenEscalations(input.projectRoot);
|
|
89
|
+
const idFilter = input.ids !== undefined && input.ids.length > 0
|
|
90
|
+
? new Set(input.ids.map((x) => x.trim()).filter((x) => x.length > 0))
|
|
91
|
+
: null;
|
|
92
|
+
const approved = [];
|
|
93
|
+
const skipped = [];
|
|
94
|
+
for (const event of open) {
|
|
95
|
+
if (idFilter !== null && !idFilter.has(event.id)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!isBatchApprovableType(event.type)) {
|
|
99
|
+
skipped.push({
|
|
100
|
+
id: event.id,
|
|
101
|
+
reason: `type ${event.type} is not batch-approvable (use escalation:resolve); allowed bulk types: ${BATCH_APPROVABLE_TYPES.join(", ")}`,
|
|
102
|
+
});
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (event.dangerous && input.includeDangerous !== true) {
|
|
106
|
+
skipped.push({
|
|
107
|
+
id: event.id,
|
|
108
|
+
reason: "dangerous=true — resolve individually or pass --include-dangerous",
|
|
109
|
+
});
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const decision = event.type === "question" ? "answered" : "approved";
|
|
113
|
+
const result = resolveEscalation({
|
|
114
|
+
projectRoot: input.projectRoot,
|
|
115
|
+
id: event.id,
|
|
116
|
+
decision,
|
|
117
|
+
actor: input.actor,
|
|
118
|
+
note: input.note ?? "batch-approve",
|
|
119
|
+
answer: event.type === "question" ? (input.note ?? "acknowledged") : null,
|
|
120
|
+
now: input.now,
|
|
121
|
+
});
|
|
122
|
+
if (result.ok) {
|
|
123
|
+
approved.push(result.event);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
skipped.push({ id: event.id, reason: result.message });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { approved, skipped };
|
|
130
|
+
}
|
|
131
|
+
export function listEscalationsFiltered(projectRoot, opts = {}) {
|
|
132
|
+
const base = opts.openOnly === true ? listOpenEscalations(projectRoot) : listEscalations(projectRoot);
|
|
133
|
+
if (opts.type === undefined)
|
|
134
|
+
return base;
|
|
135
|
+
return base.filter((e) => e.type === opts.type);
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=actions.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { ESCALATION_DIR } from "./types.js";
|
|
3
|
+
export function escalationsDir(projectRoot) {
|
|
4
|
+
return join(projectRoot, ...ESCALATION_DIR.split("/"));
|
|
5
|
+
}
|
|
6
|
+
export function escalationPath(projectRoot, escalationId) {
|
|
7
|
+
const safe = escalationId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
8
|
+
return join(escalationsDir(projectRoot), `${safe}.json`);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk store for typed escalations under `.deft/escalations/` (#518).
|
|
3
|
+
*/
|
|
4
|
+
import { type EscalationEvent, type EscalationType } from "./types.js";
|
|
5
|
+
export declare function utcIso(now?: Date): string;
|
|
6
|
+
/**
|
|
7
|
+
* Parse an escalation JSON object.
|
|
8
|
+
* Returns null when type/status/required fields are invalid (tests reject invalid types).
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseEscalation(raw: unknown): EscalationEvent | null;
|
|
11
|
+
/** Validate type string alone (unit tests + CLI). */
|
|
12
|
+
export declare function validateEscalationType(type: string): EscalationType;
|
|
13
|
+
export declare function saveEscalation(projectRoot: string, event: EscalationEvent): void;
|
|
14
|
+
export declare function loadEscalation(projectRoot: string, escalationId: string): EscalationEvent | null;
|
|
15
|
+
export declare function listEscalations(projectRoot: string): EscalationEvent[];
|
|
16
|
+
export declare function listOpenEscalations(projectRoot: string): EscalationEvent[];
|
|
17
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk store for typed escalations under `.deft/escalations/` (#518).
|
|
3
|
+
*/
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import { existsSync, readdirSync, readFileSync, renameSync, rmSync } from "node:fs";
|
|
6
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
7
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
8
|
+
import { assertWriteTargetSafe } from "../fs/projection-containment.js";
|
|
9
|
+
import { escalationPath, escalationsDir } from "./paths.js";
|
|
10
|
+
import { DEFAULT_SLA_HOURS, ESCALATION_DECISIONS, ESCALATION_SCHEMA_VERSION, ESCALATION_STATUSES, ESCALATION_TYPES, isEscalationType, } from "./types.js";
|
|
11
|
+
export function utcIso(now) {
|
|
12
|
+
const dt = now ?? new Date();
|
|
13
|
+
return dt.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
14
|
+
}
|
|
15
|
+
function record(value) {
|
|
16
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
17
|
+
? value
|
|
18
|
+
: null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Contained atomic JSON write for escalations (#2980 / Greptile P1).
|
|
22
|
+
* Containment root is projectRoot (not dirname(target)) so parent-symlink escape fails closed.
|
|
23
|
+
* Unique random temp names avoid PID-reuse collisions; rename is the atomic publish step.
|
|
24
|
+
*/
|
|
25
|
+
function writeJsonContained(projectRoot, targetPath, payload) {
|
|
26
|
+
const root = resolve(projectRoot);
|
|
27
|
+
const abs = resolve(targetPath);
|
|
28
|
+
// Refuse leaf/parent symlinks on the final path before temp+rename publish.
|
|
29
|
+
assertWriteTargetSafe(root, abs);
|
|
30
|
+
const dir = dirname(abs);
|
|
31
|
+
const tmpBase = `.${basename(abs)}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
32
|
+
const tmp = join(dir, tmpBase);
|
|
33
|
+
try {
|
|
34
|
+
containedWrite({
|
|
35
|
+
root,
|
|
36
|
+
target: tmp,
|
|
37
|
+
data: `${JSON.stringify(payload, null, 2)}\n`,
|
|
38
|
+
mode: "create",
|
|
39
|
+
});
|
|
40
|
+
renameSync(tmp, abs);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
try {
|
|
44
|
+
rmSync(tmp, { force: true });
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
/* best-effort cleanup */
|
|
48
|
+
}
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function readString(rec, key) {
|
|
53
|
+
const v = rec[key];
|
|
54
|
+
return typeof v === "string" && v.trim().length > 0 ? v.trim() : null;
|
|
55
|
+
}
|
|
56
|
+
function readStringArray(raw) {
|
|
57
|
+
if (!Array.isArray(raw))
|
|
58
|
+
return [];
|
|
59
|
+
return raw.filter((x) => typeof x === "string" && x.trim().length > 0);
|
|
60
|
+
}
|
|
61
|
+
function readNumber(rec, key) {
|
|
62
|
+
const v = rec[key];
|
|
63
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
64
|
+
}
|
|
65
|
+
function parseResolution(raw) {
|
|
66
|
+
if (raw === null || raw === undefined)
|
|
67
|
+
return null;
|
|
68
|
+
const rec = record(raw);
|
|
69
|
+
if (rec === null)
|
|
70
|
+
return null;
|
|
71
|
+
const decision = readString(rec, "decision");
|
|
72
|
+
const resolvedAt = readString(rec, "resolvedAt") ?? readString(rec, "resolved_at") ?? utcIso();
|
|
73
|
+
const resolvedBy = readString(rec, "resolvedBy") ?? readString(rec, "resolved_by") ?? "operator";
|
|
74
|
+
if (decision === null || !ESCALATION_DECISIONS.includes(decision)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
decision: decision,
|
|
79
|
+
resolvedAt,
|
|
80
|
+
resolvedBy,
|
|
81
|
+
note: readString(rec, "note"),
|
|
82
|
+
answer: readString(rec, "answer"),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Parse an escalation JSON object.
|
|
87
|
+
* Returns null when type/status/required fields are invalid (tests reject invalid types).
|
|
88
|
+
*/
|
|
89
|
+
export function parseEscalation(raw) {
|
|
90
|
+
const rec = record(raw);
|
|
91
|
+
if (rec === null)
|
|
92
|
+
return null;
|
|
93
|
+
const id = readString(rec, "id");
|
|
94
|
+
const agentId = readString(rec, "agentId") ?? readString(rec, "agent_id");
|
|
95
|
+
const typeRaw = readString(rec, "type");
|
|
96
|
+
const title = readString(rec, "title");
|
|
97
|
+
if (id === null || agentId === null || typeRaw === null || title === null)
|
|
98
|
+
return null;
|
|
99
|
+
if (!isEscalationType(typeRaw))
|
|
100
|
+
return null;
|
|
101
|
+
const statusRaw = (readString(rec, "status") ?? "open").toLowerCase();
|
|
102
|
+
if (!ESCALATION_STATUSES.includes(statusRaw))
|
|
103
|
+
return null;
|
|
104
|
+
const body = typeof rec.body === "string" ? rec.body : "";
|
|
105
|
+
const contextRefs = readStringArray(rec.contextRefs ?? rec.context_refs);
|
|
106
|
+
const createdAt = readString(rec, "createdAt") ?? readString(rec, "created_at") ?? utcIso();
|
|
107
|
+
const sla = readNumber(rec, "slaHours") ??
|
|
108
|
+
readNumber(rec, "sla_hours") ??
|
|
109
|
+
DEFAULT_SLA_HOURS[typeRaw];
|
|
110
|
+
const dangerous = rec.dangerous === true;
|
|
111
|
+
const resolution = parseResolution(rec.resolution);
|
|
112
|
+
return {
|
|
113
|
+
schemaVersion: ESCALATION_SCHEMA_VERSION,
|
|
114
|
+
id,
|
|
115
|
+
agentId,
|
|
116
|
+
type: typeRaw,
|
|
117
|
+
title,
|
|
118
|
+
body,
|
|
119
|
+
contextRefs,
|
|
120
|
+
createdAt,
|
|
121
|
+
slaHours: sla > 0 ? sla : DEFAULT_SLA_HOURS[typeRaw],
|
|
122
|
+
status: statusRaw,
|
|
123
|
+
dangerous,
|
|
124
|
+
resolution,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** Validate type string alone (unit tests + CLI). */
|
|
128
|
+
export function validateEscalationType(type) {
|
|
129
|
+
const normalized = type.trim().toLowerCase();
|
|
130
|
+
if (!isEscalationType(normalized)) {
|
|
131
|
+
throw new Error(`invalid escalation type '${type}'; expected one of: ${ESCALATION_TYPES.join(", ")}`);
|
|
132
|
+
}
|
|
133
|
+
return normalized;
|
|
134
|
+
}
|
|
135
|
+
export function saveEscalation(projectRoot, event) {
|
|
136
|
+
writeJsonContained(projectRoot, escalationPath(projectRoot, event.id), event);
|
|
137
|
+
}
|
|
138
|
+
export function loadEscalation(projectRoot, escalationId) {
|
|
139
|
+
const path = escalationPath(projectRoot, escalationId);
|
|
140
|
+
if (!existsSync(path))
|
|
141
|
+
return null;
|
|
142
|
+
try {
|
|
143
|
+
return parseEscalation(JSON.parse(readFileSync(path, "utf8")));
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export function listEscalations(projectRoot) {
|
|
150
|
+
const dir = escalationsDir(projectRoot);
|
|
151
|
+
if (!existsSync(dir))
|
|
152
|
+
return [];
|
|
153
|
+
const out = [];
|
|
154
|
+
for (const name of readdirSync(dir)) {
|
|
155
|
+
if (!name.endsWith(".json"))
|
|
156
|
+
continue;
|
|
157
|
+
try {
|
|
158
|
+
const event = parseEscalation(JSON.parse(readFileSync(join(dir, name), "utf8")));
|
|
159
|
+
if (event !== null)
|
|
160
|
+
out.push(event);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// skip corrupt files
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
out.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
export function listOpenEscalations(projectRoot) {
|
|
170
|
+
return listEscalations(projectRoot).filter((e) => e.status === "open");
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed escalation events + local queue store (#518 slim / #2948 Wave 5).
|
|
3
|
+
*
|
|
4
|
+
* Full priority-inbox web UI and metrics remain residual — this module is the
|
|
5
|
+
* versioned schema + `.deft/escalations/` file queue for CLI batching.
|
|
6
|
+
*/
|
|
7
|
+
/** Fixed vocabulary of escalation reasons (#518). */
|
|
8
|
+
export declare const ESCALATION_TYPES: readonly ["cmd_approval", "design_decision", "approval", "resource", "external", "question"];
|
|
9
|
+
export type EscalationType = (typeof ESCALATION_TYPES)[number];
|
|
10
|
+
/**
|
|
11
|
+
* Types eligible for `escalation:batch-approve`.
|
|
12
|
+
* design_decision / approval / resource / external stay individual resolve.
|
|
13
|
+
*/
|
|
14
|
+
export declare const BATCH_APPROVABLE_TYPES: readonly ["cmd_approval", "question"];
|
|
15
|
+
export type BatchApprovableType = (typeof BATCH_APPROVABLE_TYPES)[number];
|
|
16
|
+
export declare const ESCALATION_STATUSES: readonly ["open", "resolved"];
|
|
17
|
+
export type EscalationStatus = (typeof ESCALATION_STATUSES)[number];
|
|
18
|
+
export declare const ESCALATION_DECISIONS: readonly ["approved", "denied", "answered", "dismissed"];
|
|
19
|
+
export type EscalationDecision = (typeof ESCALATION_DECISIONS)[number];
|
|
20
|
+
/** Default SLA hours per type (issue #518 table). */
|
|
21
|
+
export declare const DEFAULT_SLA_HOURS: Readonly<Record<EscalationType, number>>;
|
|
22
|
+
export interface EscalationResolution {
|
|
23
|
+
readonly decision: EscalationDecision;
|
|
24
|
+
readonly resolvedAt: string;
|
|
25
|
+
readonly resolvedBy: string;
|
|
26
|
+
readonly note: string | null;
|
|
27
|
+
/** Free-text answer for `question` type (optional for other types). */
|
|
28
|
+
readonly answer: string | null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Versioned escalation event (YAML shape from #518, camelCase on disk).
|
|
32
|
+
*
|
|
33
|
+
* ```json
|
|
34
|
+
* {
|
|
35
|
+
* "schemaVersion": 1,
|
|
36
|
+
* "id": "esc-…",
|
|
37
|
+
* "agentId": "agent-b",
|
|
38
|
+
* "type": "design_decision",
|
|
39
|
+
* "title": "…",
|
|
40
|
+
* "body": "…",
|
|
41
|
+
* "contextRefs": ["xbrief/…", "#496"],
|
|
42
|
+
* "createdAt": "2026-07-30T10:00:00Z",
|
|
43
|
+
* "slaHours": 4,
|
|
44
|
+
* "status": "open",
|
|
45
|
+
* "dangerous": false,
|
|
46
|
+
* "resolution": null
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export interface EscalationEvent {
|
|
51
|
+
readonly schemaVersion: 1;
|
|
52
|
+
readonly id: string;
|
|
53
|
+
readonly agentId: string;
|
|
54
|
+
readonly type: EscalationType;
|
|
55
|
+
readonly title: string;
|
|
56
|
+
readonly body: string;
|
|
57
|
+
readonly contextRefs: readonly string[];
|
|
58
|
+
readonly createdAt: string;
|
|
59
|
+
readonly slaHours: number;
|
|
60
|
+
readonly status: EscalationStatus;
|
|
61
|
+
/**
|
|
62
|
+
* When true, batch-approve skips this item unless `--include-dangerous`.
|
|
63
|
+
* Use for shell write-scope cmd_approval, PR merges, and similar.
|
|
64
|
+
*/
|
|
65
|
+
readonly dangerous: boolean;
|
|
66
|
+
readonly resolution: EscalationResolution | null;
|
|
67
|
+
}
|
|
68
|
+
export declare const ESCALATION_SCHEMA_VERSION: 1;
|
|
69
|
+
export declare const ESCALATION_DIR = ".deft/escalations";
|
|
70
|
+
export declare function isEscalationType(value: string): value is EscalationType;
|
|
71
|
+
export declare function isBatchApprovableType(type: EscalationType): type is BatchApprovableType;
|
|
72
|
+
export declare function isEscalationDecision(value: string): value is EscalationDecision;
|
|
73
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed escalation events + local queue store (#518 slim / #2948 Wave 5).
|
|
3
|
+
*
|
|
4
|
+
* Full priority-inbox web UI and metrics remain residual — this module is the
|
|
5
|
+
* versioned schema + `.deft/escalations/` file queue for CLI batching.
|
|
6
|
+
*/
|
|
7
|
+
/** Fixed vocabulary of escalation reasons (#518). */
|
|
8
|
+
export const ESCALATION_TYPES = [
|
|
9
|
+
"cmd_approval",
|
|
10
|
+
"design_decision",
|
|
11
|
+
"approval",
|
|
12
|
+
"resource",
|
|
13
|
+
"external",
|
|
14
|
+
"question",
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Types eligible for `escalation:batch-approve`.
|
|
18
|
+
* design_decision / approval / resource / external stay individual resolve.
|
|
19
|
+
*/
|
|
20
|
+
export const BATCH_APPROVABLE_TYPES = ["cmd_approval", "question"];
|
|
21
|
+
export const ESCALATION_STATUSES = ["open", "resolved"];
|
|
22
|
+
export const ESCALATION_DECISIONS = ["approved", "denied", "answered", "dismissed"];
|
|
23
|
+
/** Default SLA hours per type (issue #518 table). */
|
|
24
|
+
export const DEFAULT_SLA_HOURS = {
|
|
25
|
+
cmd_approval: 1,
|
|
26
|
+
design_decision: 4,
|
|
27
|
+
approval: 4,
|
|
28
|
+
resource: 4,
|
|
29
|
+
external: 72,
|
|
30
|
+
question: 24,
|
|
31
|
+
};
|
|
32
|
+
export const ESCALATION_SCHEMA_VERSION = 1;
|
|
33
|
+
export const ESCALATION_DIR = ".deft/escalations";
|
|
34
|
+
export function isEscalationType(value) {
|
|
35
|
+
return ESCALATION_TYPES.includes(value);
|
|
36
|
+
}
|
|
37
|
+
export function isBatchApprovableType(type) {
|
|
38
|
+
return BATCH_APPROVABLE_TYPES.includes(type);
|
|
39
|
+
}
|
|
40
|
+
export function isEscalationDecision(value) {
|
|
41
|
+
return ESCALATION_DECISIONS.includes(value);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, resolve } from "node:path";
|
|
3
3
|
import { readCorePackageVersion } from "../engine-version.js";
|
|
4
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
4
5
|
import { helpedMetricsHistoryPath } from "../metrics/resolve-metrics-home.js";
|
|
5
6
|
import { scanVbrief } from "../vbrief-validate/conformance.js";
|
|
6
7
|
import { validateVbriefSchema } from "../vbrief-validate/schema.js";
|
|
@@ -83,8 +84,19 @@ function assessDocument(path, parsed) {
|
|
|
83
84
|
function sanitizeInlineMessage(message) {
|
|
84
85
|
return message.replace(/\r?\n/g, " ");
|
|
85
86
|
}
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Contained replace under the target's parent directory (#2980 wave C).
|
|
89
|
+
* Metrics/CRUD sinks may live outside the project tree (platform metrics home).
|
|
90
|
+
*/
|
|
91
|
+
function containedReplaceAt(path, content) {
|
|
92
|
+
const dir = dirname(path);
|
|
93
|
+
mkdirSync(dir, { recursive: true });
|
|
94
|
+
containedWrite({
|
|
95
|
+
root: resolve(dir),
|
|
96
|
+
target: basename(path),
|
|
97
|
+
data: content,
|
|
98
|
+
mode: "replace",
|
|
99
|
+
});
|
|
88
100
|
}
|
|
89
101
|
/** Absolute path to the versioned CRUD metrics ledger (#1703 Tier 1 / #2545). */
|
|
90
102
|
export function crudMetricsHistoryPath(projectRoot) {
|
|
@@ -99,9 +111,17 @@ export function persistCrudMetrics(projectRoot, metrics) {
|
|
|
99
111
|
if (path === null) {
|
|
100
112
|
return;
|
|
101
113
|
}
|
|
102
|
-
|
|
114
|
+
// #2980 wave C: product write sink routes through containedWrite.
|
|
115
|
+
// Metrics home is often outside the project tree (#2545) — contain under ledger parent.
|
|
116
|
+
const dir = dirname(path);
|
|
117
|
+
mkdirSync(dir, { recursive: true });
|
|
103
118
|
for (const metric of metrics) {
|
|
104
|
-
|
|
119
|
+
containedWrite({
|
|
120
|
+
root: resolve(dir),
|
|
121
|
+
target: basename(path),
|
|
122
|
+
data: `${JSON.stringify(metric)}\n`,
|
|
123
|
+
mode: "append",
|
|
124
|
+
});
|
|
105
125
|
}
|
|
106
126
|
}
|
|
107
127
|
/**
|
|
@@ -155,8 +175,8 @@ export class InstrumentedVbriefCrud {
|
|
|
155
175
|
if (!assessment.schemaValid) {
|
|
156
176
|
return { ok: false, error: assessment.schemaErrors.join("; ") };
|
|
157
177
|
}
|
|
158
|
-
|
|
159
|
-
|
|
178
|
+
// #2980 wave C: product write sink routes through containedWrite.
|
|
179
|
+
containedReplaceAt(path, content);
|
|
160
180
|
return { ok: true };
|
|
161
181
|
}
|
|
162
182
|
read(path) {
|
|
@@ -244,8 +264,8 @@ export class InstrumentedVbriefCrud {
|
|
|
244
264
|
if (!assessment.schemaValid && !options.trustedWrite) {
|
|
245
265
|
return { ok: false, error: assessment.schemaErrors.join("; ") };
|
|
246
266
|
}
|
|
247
|
-
|
|
248
|
-
|
|
267
|
+
// #2980 wave C: product write sink routes through containedWrite.
|
|
268
|
+
containedReplaceAt(path, content);
|
|
249
269
|
return { ok: true };
|
|
250
270
|
}
|
|
251
271
|
/** Record update metrics without persisting (caller owns validated atomic write). */
|
package/dist/eval/health.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
|
|
3
3
|
import { agentsRefreshPlan } from "../doctor/agents-md.js";
|
|
4
4
|
import { evaluate as evaluateEncoding } from "../encoding/evaluate.js";
|
|
5
5
|
import { readCorePackageVersion } from "../engine-version.js";
|
|
6
|
-
import {
|
|
6
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
7
7
|
import { resolveProjectDefinitionPath } from "../layout/resolve.js";
|
|
8
8
|
import { healthMetricsHistoryPath } from "../metrics/resolve-metrics-home.js";
|
|
9
9
|
import { readPlanPolicy } from "../policy/plan-extensions.js";
|
|
@@ -163,14 +163,29 @@ export function persistHealthRun(projectRoot, report) {
|
|
|
163
163
|
if (path === null) {
|
|
164
164
|
return;
|
|
165
165
|
}
|
|
166
|
+
// #2980 wave C: product write sink routes through containedWrite.
|
|
167
|
+
// Project-local metrics: contain under projectRoot. Platform/env home: under ledger parent.
|
|
166
168
|
const projectAbs = resolve(projectRoot);
|
|
167
169
|
const targetAbs = resolve(path);
|
|
168
170
|
const rel = relative(projectAbs, targetAbs);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
+
const nested = rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel);
|
|
172
|
+
if (nested) {
|
|
173
|
+
containedWrite({
|
|
174
|
+
root: projectAbs,
|
|
175
|
+
target: targetAbs,
|
|
176
|
+
data: `${JSON.stringify(report)}\n`,
|
|
177
|
+
mode: "append",
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
171
180
|
}
|
|
172
|
-
|
|
173
|
-
|
|
181
|
+
const parent = dirname(targetAbs);
|
|
182
|
+
mkdirSync(parent, { recursive: true });
|
|
183
|
+
containedWrite({
|
|
184
|
+
root: resolve(parent),
|
|
185
|
+
target: basename(targetAbs),
|
|
186
|
+
data: `${JSON.stringify(report)}\n`,
|
|
187
|
+
mode: "append",
|
|
188
|
+
});
|
|
174
189
|
}
|
|
175
190
|
function collectStaticGates(projectRoot, frameworkSource) {
|
|
176
191
|
const gates = [
|
package/dist/eval/readback.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
4
4
|
import { MAX_LINE_CHARS } from "../triage/welcome/constants.js";
|
|
5
5
|
import { evaluateHealth, healthHistoryPath } from "./health.js";
|
|
6
6
|
/** Repeat-suppression window for the budgeted eval session nudge (#1703 / #1279 parity). */
|
|
@@ -125,15 +125,16 @@ function appendEvalReadbackHistory(projectRoot, nudgeKey, line, options = {}) {
|
|
|
125
125
|
line,
|
|
126
126
|
};
|
|
127
127
|
try {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
// #2980 wave C: product write sink routes through containedWrite.
|
|
129
|
+
containedWrite({
|
|
130
|
+
root: resolve(projectRoot),
|
|
131
|
+
target: path,
|
|
132
|
+
data: `${JSON.stringify(record)}\n`,
|
|
133
|
+
mode: "append",
|
|
134
|
+
});
|
|
131
135
|
}
|
|
132
|
-
catch
|
|
133
|
-
|
|
134
|
-
throw err;
|
|
135
|
-
}
|
|
136
|
-
// observability only
|
|
136
|
+
catch {
|
|
137
|
+
// observability only (containment refusals included — outer caller may swallow)
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
function failedGateCount(report) {
|