tamperward 2.39.24 → 2.40.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/cli/index.js +94 -8
- package/package.json +1 -1
- package/schemas/audit-v1.schema.json +33 -1
- package/schemas/stats-v1.schema.json +166 -1
package/dist/cli/index.js
CHANGED
|
@@ -12546,6 +12546,16 @@ function sessionHash(sessionId) {
|
|
|
12546
12546
|
if (!sessionId) return void 0;
|
|
12547
12547
|
return "sha256:" + createHash6("sha256").update(sessionId).digest("hex").slice(0, 24);
|
|
12548
12548
|
}
|
|
12549
|
+
function sanitizedMetrics(metrics) {
|
|
12550
|
+
const out3 = {};
|
|
12551
|
+
if (!metrics) return out3;
|
|
12552
|
+
for (const field2 of AUDIT_DURATION_FIELDS) {
|
|
12553
|
+
const v = metrics[field2];
|
|
12554
|
+
if (typeof v === "number" && Number.isFinite(v)) out3[field2] = Math.max(0, Math.round(v));
|
|
12555
|
+
}
|
|
12556
|
+
if (typeof metrics.oob_signoff === "boolean") out3.oob_signoff = metrics.oob_signoff;
|
|
12557
|
+
return out3;
|
|
12558
|
+
}
|
|
12549
12559
|
function eventId() {
|
|
12550
12560
|
return "sha256:" + createHash6("sha256").update(randomBytes2(32)).digest("hex").slice(0, 32);
|
|
12551
12561
|
}
|
|
@@ -12570,6 +12580,7 @@ function recordAuditFindings(findings, context) {
|
|
|
12570
12580
|
mkdirSync5(dirname4(path), { recursive: true });
|
|
12571
12581
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
12572
12582
|
const session = sessionHash(context.sessionId);
|
|
12583
|
+
const metrics = sanitizedMetrics(context.metrics);
|
|
12573
12584
|
const lines = findings.map((finding) => {
|
|
12574
12585
|
const severity = finding.severity === "block" ? "block" : "warn";
|
|
12575
12586
|
const event = {
|
|
@@ -12581,7 +12592,8 @@ function recordAuditFindings(findings, context) {
|
|
|
12581
12592
|
rule: finding.rule,
|
|
12582
12593
|
severity,
|
|
12583
12594
|
decision: severity === "block" ? "deny" : "warn",
|
|
12584
|
-
...session ? { session } : {}
|
|
12595
|
+
...session ? { session } : {},
|
|
12596
|
+
...metrics
|
|
12585
12597
|
};
|
|
12586
12598
|
return JSON.stringify(event);
|
|
12587
12599
|
});
|
|
@@ -12638,6 +12650,15 @@ function parseAuditEvent(value, line = 0) {
|
|
|
12638
12650
|
if (value.session !== void 0 && (typeof value.session !== "string" || !/^sha256:[0-9a-f]{24}$/.test(value.session))) {
|
|
12639
12651
|
throw new Error(`${where} has an invalid session`);
|
|
12640
12652
|
}
|
|
12653
|
+
for (const field2 of AUDIT_DURATION_FIELDS) {
|
|
12654
|
+
const v = value[field2];
|
|
12655
|
+
if (v !== void 0 && (typeof v !== "number" || !Number.isInteger(v) || v < 0)) {
|
|
12656
|
+
throw new Error(`${where} has an invalid ${field2}`);
|
|
12657
|
+
}
|
|
12658
|
+
}
|
|
12659
|
+
if (value.oob_signoff !== void 0 && typeof value.oob_signoff !== "boolean") {
|
|
12660
|
+
throw new Error(`${where} has an invalid oob_signoff`);
|
|
12661
|
+
}
|
|
12641
12662
|
return {
|
|
12642
12663
|
schema_version: AUDIT_SCHEMA_VERSION,
|
|
12643
12664
|
id: value.id,
|
|
@@ -12647,9 +12668,19 @@ function parseAuditEvent(value, line = 0) {
|
|
|
12647
12668
|
rule: value.rule,
|
|
12648
12669
|
severity: value.severity,
|
|
12649
12670
|
decision: value.decision,
|
|
12650
|
-
...typeof value.session === "string" ? { session: value.session } : {}
|
|
12671
|
+
...typeof value.session === "string" ? { session: value.session } : {},
|
|
12672
|
+
...durationsOf(value),
|
|
12673
|
+
...typeof value.oob_signoff === "boolean" ? { oob_signoff: value.oob_signoff } : {}
|
|
12651
12674
|
};
|
|
12652
12675
|
}
|
|
12676
|
+
function durationsOf(value) {
|
|
12677
|
+
const out3 = {};
|
|
12678
|
+
for (const field2 of AUDIT_DURATION_FIELDS) {
|
|
12679
|
+
const v = value[field2];
|
|
12680
|
+
if (typeof v === "number") out3[field2] = v;
|
|
12681
|
+
}
|
|
12682
|
+
return out3;
|
|
12683
|
+
}
|
|
12653
12684
|
function parseAuditLine(line, lineNumber) {
|
|
12654
12685
|
let value;
|
|
12655
12686
|
try {
|
|
@@ -12724,6 +12755,12 @@ function createAuditAggregator() {
|
|
|
12724
12755
|
const rules = /* @__PURE__ */ new Map();
|
|
12725
12756
|
const surfaces = /* @__PURE__ */ new Map();
|
|
12726
12757
|
const sessions = /* @__PURE__ */ new Set();
|
|
12758
|
+
const durations = {
|
|
12759
|
+
hook_latency_ms: /* @__PURE__ */ new Map(),
|
|
12760
|
+
verify_wall_clock_ms: /* @__PURE__ */ new Map(),
|
|
12761
|
+
bare_suite_wall_clock_ms: /* @__PURE__ */ new Map()
|
|
12762
|
+
};
|
|
12763
|
+
let oobSignoffs = 0;
|
|
12727
12764
|
const bounds = { first: null, last: null };
|
|
12728
12765
|
return {
|
|
12729
12766
|
add(event) {
|
|
@@ -12737,6 +12774,11 @@ function createAuditAggregator() {
|
|
|
12737
12774
|
rules.set(event.rule, bucket);
|
|
12738
12775
|
surfaces.set(event.surface, (surfaces.get(event.surface) ?? 0) + 1);
|
|
12739
12776
|
if (event.session) sessions.add(event.session);
|
|
12777
|
+
for (const field2 of AUDIT_DURATION_FIELDS) {
|
|
12778
|
+
const v = event[field2];
|
|
12779
|
+
if (typeof v === "number") durations[field2].set(v, (durations[field2].get(v) ?? 0) + 1);
|
|
12780
|
+
}
|
|
12781
|
+
if (event.oob_signoff === true) oobSignoffs++;
|
|
12740
12782
|
const ms = Date.parse(event.timestamp);
|
|
12741
12783
|
const { first, last } = bounds;
|
|
12742
12784
|
if (first === null || ms < first.ms || ms === first.ms && event.id.localeCompare(first.id) < 0) {
|
|
@@ -12759,11 +12801,32 @@ function createAuditAggregator() {
|
|
|
12759
12801
|
last_event: bounds.last === null ? null : bounds.last.timestamp,
|
|
12760
12802
|
by_rule,
|
|
12761
12803
|
by_surface,
|
|
12804
|
+
latency: {
|
|
12805
|
+
hook_latency_ms: percentiles(durations.hook_latency_ms),
|
|
12806
|
+
verify_wall_clock_ms: percentiles(durations.verify_wall_clock_ms),
|
|
12807
|
+
bare_suite_wall_clock_ms: percentiles(durations.bare_suite_wall_clock_ms)
|
|
12808
|
+
},
|
|
12809
|
+
oob_signoffs: oobSignoffs,
|
|
12762
12810
|
interpretation: "finding-is-not-proof-of-intent"
|
|
12763
12811
|
};
|
|
12764
12812
|
}
|
|
12765
12813
|
};
|
|
12766
12814
|
}
|
|
12815
|
+
function percentiles(counts) {
|
|
12816
|
+
const values = [...counts.keys()].sort((a, b) => a - b);
|
|
12817
|
+
if (values.length === 0) return null;
|
|
12818
|
+
const samples = [...counts.values()].reduce((a, b) => a + b, 0);
|
|
12819
|
+
const at = (q) => {
|
|
12820
|
+
const rank = Math.max(1, Math.ceil(q * samples));
|
|
12821
|
+
let seen = 0;
|
|
12822
|
+
for (const v of values) {
|
|
12823
|
+
seen += counts.get(v) ?? 0;
|
|
12824
|
+
if (seen >= rank) return v;
|
|
12825
|
+
}
|
|
12826
|
+
return values[values.length - 1];
|
|
12827
|
+
};
|
|
12828
|
+
return { samples, p50: at(0.5), p95: at(0.95), max: values[values.length - 1] };
|
|
12829
|
+
}
|
|
12767
12830
|
function renderAuditStats(summary) {
|
|
12768
12831
|
const out3 = [
|
|
12769
12832
|
"TamperWard audit stats",
|
|
@@ -12786,6 +12849,23 @@ function renderAuditStats(summary) {
|
|
|
12786
12849
|
const width = Math.max(...summary.by_surface.map((row) => row.surface.length));
|
|
12787
12850
|
for (const row of summary.by_surface) out3.push(` ${row.surface.padEnd(width)} ${row.events}`);
|
|
12788
12851
|
}
|
|
12852
|
+
const measures = [
|
|
12853
|
+
["hook_latency_ms", "Hook latency"],
|
|
12854
|
+
["verify_wall_clock_ms", "verify wall-clock"],
|
|
12855
|
+
["bare_suite_wall_clock_ms", "Bare-suite wall-clock"]
|
|
12856
|
+
];
|
|
12857
|
+
const measured = [];
|
|
12858
|
+
for (const [field2, label] of measures) {
|
|
12859
|
+
const p = summary.latency[field2];
|
|
12860
|
+
if (p !== null) measured.push([label, p]);
|
|
12861
|
+
}
|
|
12862
|
+
if (measured.length || summary.oob_signoffs > 0) {
|
|
12863
|
+
out3.push("", "Measures");
|
|
12864
|
+
for (const [label, p] of measured) {
|
|
12865
|
+
out3.push(` ${label.padEnd(22)} p50 ${p.p50} ms p95 ${p.p95} ms max ${p.max} ms (${p.samples} samples)`);
|
|
12866
|
+
}
|
|
12867
|
+
if (summary.oob_signoffs > 0) out3.push(` ${"Out-of-band sign-offs".padEnd(22)} ${summary.oob_signoffs}`);
|
|
12868
|
+
}
|
|
12789
12869
|
out3.push("", "Note: an integrity finding is a signal, not proof of agent intent; legitimate refactors can trigger findings.");
|
|
12790
12870
|
return out3.join("\n") + "\n";
|
|
12791
12871
|
}
|
|
@@ -12807,7 +12887,7 @@ function runStats(opts = {}) {
|
|
|
12807
12887
|
process.stdout.write(opts.json ? JSON.stringify(summary) + "\n" : renderAuditStats(summary));
|
|
12808
12888
|
return 0;
|
|
12809
12889
|
}
|
|
12810
|
-
var AUDIT_SCHEMA_VERSION, AUDIT_LOG_ENV, MAX_AUDIT_LINE_BYTES, ALLOWED_KEYS, STATS_READ_REFUSED;
|
|
12890
|
+
var AUDIT_SCHEMA_VERSION, AUDIT_LOG_ENV, MAX_AUDIT_LINE_BYTES, AUDIT_DURATION_FIELDS, ALLOWED_KEYS, STATS_READ_REFUSED;
|
|
12811
12891
|
var init_audit = __esm({
|
|
12812
12892
|
"src/cli/audit.ts"() {
|
|
12813
12893
|
"use strict";
|
|
@@ -12816,6 +12896,7 @@ var init_audit = __esm({
|
|
|
12816
12896
|
AUDIT_SCHEMA_VERSION = 1;
|
|
12817
12897
|
AUDIT_LOG_ENV = "TAMPERWARD_AUDIT_LOG";
|
|
12818
12898
|
MAX_AUDIT_LINE_BYTES = 16 * 1024;
|
|
12899
|
+
AUDIT_DURATION_FIELDS = ["hook_latency_ms", "verify_wall_clock_ms", "bare_suite_wall_clock_ms"];
|
|
12819
12900
|
ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
12820
12901
|
"schema_version",
|
|
12821
12902
|
"id",
|
|
@@ -12825,7 +12906,9 @@ var init_audit = __esm({
|
|
|
12825
12906
|
"rule",
|
|
12826
12907
|
"severity",
|
|
12827
12908
|
"decision",
|
|
12828
|
-
"session"
|
|
12909
|
+
"session",
|
|
12910
|
+
...AUDIT_DURATION_FIELDS,
|
|
12911
|
+
"oob_signoff"
|
|
12829
12912
|
]);
|
|
12830
12913
|
STATS_READ_REFUSED = "; stats reads the audit log only as a regular file \u2014 remove what stands there so the log can be read";
|
|
12831
12914
|
}
|
|
@@ -12903,7 +12986,8 @@ function verdict(blocks2, kind, audit) {
|
|
|
12903
12986
|
recordAuditFindings(blocks2, {
|
|
12904
12987
|
cwd: audit.cwd,
|
|
12905
12988
|
surface: kind === "PreToolUse" ? "pretooluse" : "stop",
|
|
12906
|
-
sessionId: audit.sessionId
|
|
12989
|
+
sessionId: audit.sessionId,
|
|
12990
|
+
...audit.startedAt !== void 0 ? { metrics: { hook_latency_ms: performance.now() - audit.startedAt } } : {}
|
|
12907
12991
|
});
|
|
12908
12992
|
}
|
|
12909
12993
|
const reason = formatDenial(blocks2);
|
|
@@ -13086,6 +13170,7 @@ function sanctionPredictedWrites(cwd, sessionId, policy, changes) {
|
|
|
13086
13170
|
if (dirty) savePtree(cwd, sessionId, tree);
|
|
13087
13171
|
}
|
|
13088
13172
|
function preToolUseVerdict(input, defaultCwd, trustedRoot) {
|
|
13173
|
+
const startedAt = performance.now();
|
|
13089
13174
|
const sessionCwd = input.cwd ?? defaultCwd ?? process.cwd();
|
|
13090
13175
|
try {
|
|
13091
13176
|
if (trustedRoot !== void 0) {
|
|
@@ -13096,11 +13181,11 @@ function preToolUseVerdict(input, defaultCwd, trustedRoot) {
|
|
|
13096
13181
|
turnBaseline(cwd, input.session_id);
|
|
13097
13182
|
const policy = loadPolicy(cwd);
|
|
13098
13183
|
const driftBlocks = effectDriftBlocks(cwd, input.session_id, policy);
|
|
13099
|
-
if (driftBlocks) return verdict(driftBlocks, "PreToolUse", { cwd, sessionId: input.session_id });
|
|
13184
|
+
if (driftBlocks) return verdict(driftBlocks, "PreToolUse", { cwd, sessionId: input.session_id, startedAt });
|
|
13100
13185
|
const changes = changesFromClaudeHook(input, cwd, sessionCwd);
|
|
13101
13186
|
const blocks2 = evaluate(changes, policy, void 0, "tool-call", { cwd }).filter((f) => f.severity === "block");
|
|
13102
13187
|
if (blocks2.length === 0) sanctionPredictedWrites(cwd, input.session_id, policy, changes);
|
|
13103
|
-
return verdict(blocks2, "PreToolUse", { cwd, sessionId: input.session_id });
|
|
13188
|
+
return verdict(blocks2, "PreToolUse", { cwd, sessionId: input.session_id, startedAt });
|
|
13104
13189
|
} catch (e) {
|
|
13105
13190
|
return failClosed("PreToolUse", errText2(e), { cwd: sessionCwd, sessionId: input.session_id });
|
|
13106
13191
|
}
|
|
@@ -13206,6 +13291,7 @@ function recordObserverHealth(cwd, sessionId, state, reason) {
|
|
|
13206
13291
|
}
|
|
13207
13292
|
function stopVerdict(input, defaultCwd, trustedRoot) {
|
|
13208
13293
|
if (input.stop_hook_active) return { exitCode: 0, stdout: "" };
|
|
13294
|
+
const startedAt = performance.now();
|
|
13209
13295
|
const sessionCwd = input.cwd ?? defaultCwd ?? process.cwd();
|
|
13210
13296
|
if (trustedRoot !== void 0) {
|
|
13211
13297
|
const v = validateClaimAgainstRoot(input.cwd, trustedRoot, defaultCwd ?? process.cwd());
|
|
@@ -13247,7 +13333,7 @@ function stopVerdict(input, defaultCwd, trustedRoot) {
|
|
|
13247
13333
|
advanceTurnBaseline(cwd, input.session_id);
|
|
13248
13334
|
commitCursor();
|
|
13249
13335
|
}
|
|
13250
|
-
return verdict(blocks2, "Stop", { cwd, sessionId: input.session_id });
|
|
13336
|
+
return verdict(blocks2, "Stop", { cwd, sessionId: input.session_id, startedAt });
|
|
13251
13337
|
}
|
|
13252
13338
|
function emit(r) {
|
|
13253
13339
|
if (r.stdout) process.stdout.write(r.stdout);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.40.0",
|
|
4
4
|
"description": "The deterministic agent-integrity gate. One ruleset, evaluated on the actual diff/commands as a verdict, enforced everywhere a change can be made.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "hexrift",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://raw.githubusercontent.com/hexrift/tamperward/v2.
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/hexrift/tamperward/v2.40.0/schemas/audit-v1.schema.json",
|
|
4
4
|
"title": "TamperWard audit event v1",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
@@ -55,6 +55,25 @@
|
|
|
55
55
|
"session": {
|
|
56
56
|
"type": "string",
|
|
57
57
|
"pattern": "^sha256:[0-9a-f]{24}$"
|
|
58
|
+
},
|
|
59
|
+
"hook_latency_ms": {
|
|
60
|
+
"type": "integer",
|
|
61
|
+
"minimum": 0,
|
|
62
|
+
"description": "Wall-clock milliseconds from the hook reading its input to the deny being written. Present only on events the hook itself records."
|
|
63
|
+
},
|
|
64
|
+
"verify_wall_clock_ms": {
|
|
65
|
+
"type": "integer",
|
|
66
|
+
"minimum": 0,
|
|
67
|
+
"description": "Wall-clock milliseconds of a `tamperward verify` run, where a producer records it."
|
|
68
|
+
},
|
|
69
|
+
"bare_suite_wall_clock_ms": {
|
|
70
|
+
"type": "integer",
|
|
71
|
+
"minimum": 0,
|
|
72
|
+
"description": "Wall-clock milliseconds of the same suite run without TamperWard, the comparison for verify_wall_clock_ms."
|
|
73
|
+
},
|
|
74
|
+
"oob_signoff": {
|
|
75
|
+
"type": "boolean",
|
|
76
|
+
"description": "True when an out-of-band sign-off (a CI label or local allow) was needed to clear this finding."
|
|
58
77
|
}
|
|
59
78
|
},
|
|
60
79
|
"allOf": [
|
|
@@ -108,6 +127,19 @@
|
|
|
108
127
|
"severity": "block",
|
|
109
128
|
"decision": "deny",
|
|
110
129
|
"session": "sha256:bbbbbbbbbbbbbbbbbbbbbbbb"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"schema_version": 1,
|
|
133
|
+
"id": "sha256:cccccccccccccccccccccccccccccccc",
|
|
134
|
+
"timestamp": "2026-09-23T14:00:00.000Z",
|
|
135
|
+
"surface": "pretooluse",
|
|
136
|
+
"agent": "claude-code",
|
|
137
|
+
"rule": "test-deletion",
|
|
138
|
+
"severity": "block",
|
|
139
|
+
"decision": "deny",
|
|
140
|
+
"session": "sha256:bbbbbbbbbbbbbbbbbbbbbbbb",
|
|
141
|
+
"hook_latency_ms": 42,
|
|
142
|
+
"oob_signoff": false
|
|
111
143
|
}
|
|
112
144
|
]
|
|
113
145
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://raw.githubusercontent.com/hexrift/tamperward/v2.
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/hexrift/tamperward/v2.40.0/schemas/stats-v1.schema.json",
|
|
4
4
|
"title": "TamperWard audit stats v1",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
"last_event",
|
|
15
15
|
"by_rule",
|
|
16
16
|
"by_surface",
|
|
17
|
+
"latency",
|
|
18
|
+
"oob_signoffs",
|
|
17
19
|
"interpretation"
|
|
18
20
|
],
|
|
19
21
|
"properties": {
|
|
@@ -112,6 +114,128 @@
|
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
116
|
},
|
|
117
|
+
"latency": {
|
|
118
|
+
"type": "object",
|
|
119
|
+
"additionalProperties": false,
|
|
120
|
+
"required": [
|
|
121
|
+
"hook_latency_ms",
|
|
122
|
+
"verify_wall_clock_ms",
|
|
123
|
+
"bare_suite_wall_clock_ms"
|
|
124
|
+
],
|
|
125
|
+
"properties": {
|
|
126
|
+
"hook_latency_ms": {
|
|
127
|
+
"anyOf": [
|
|
128
|
+
{
|
|
129
|
+
"type": "null"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"type": "object",
|
|
133
|
+
"additionalProperties": false,
|
|
134
|
+
"required": [
|
|
135
|
+
"samples",
|
|
136
|
+
"p50",
|
|
137
|
+
"p95",
|
|
138
|
+
"max"
|
|
139
|
+
],
|
|
140
|
+
"properties": {
|
|
141
|
+
"samples": {
|
|
142
|
+
"type": "integer",
|
|
143
|
+
"minimum": 1
|
|
144
|
+
},
|
|
145
|
+
"p50": {
|
|
146
|
+
"type": "integer",
|
|
147
|
+
"minimum": 0
|
|
148
|
+
},
|
|
149
|
+
"p95": {
|
|
150
|
+
"type": "integer",
|
|
151
|
+
"minimum": 0
|
|
152
|
+
},
|
|
153
|
+
"max": {
|
|
154
|
+
"type": "integer",
|
|
155
|
+
"minimum": 0
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
"verify_wall_clock_ms": {
|
|
162
|
+
"anyOf": [
|
|
163
|
+
{
|
|
164
|
+
"type": "null"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"type": "object",
|
|
168
|
+
"additionalProperties": false,
|
|
169
|
+
"required": [
|
|
170
|
+
"samples",
|
|
171
|
+
"p50",
|
|
172
|
+
"p95",
|
|
173
|
+
"max"
|
|
174
|
+
],
|
|
175
|
+
"properties": {
|
|
176
|
+
"samples": {
|
|
177
|
+
"type": "integer",
|
|
178
|
+
"minimum": 1
|
|
179
|
+
},
|
|
180
|
+
"p50": {
|
|
181
|
+
"type": "integer",
|
|
182
|
+
"minimum": 0
|
|
183
|
+
},
|
|
184
|
+
"p95": {
|
|
185
|
+
"type": "integer",
|
|
186
|
+
"minimum": 0
|
|
187
|
+
},
|
|
188
|
+
"max": {
|
|
189
|
+
"type": "integer",
|
|
190
|
+
"minimum": 0
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
},
|
|
196
|
+
"bare_suite_wall_clock_ms": {
|
|
197
|
+
"anyOf": [
|
|
198
|
+
{
|
|
199
|
+
"type": "null"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"type": "object",
|
|
203
|
+
"additionalProperties": false,
|
|
204
|
+
"required": [
|
|
205
|
+
"samples",
|
|
206
|
+
"p50",
|
|
207
|
+
"p95",
|
|
208
|
+
"max"
|
|
209
|
+
],
|
|
210
|
+
"properties": {
|
|
211
|
+
"samples": {
|
|
212
|
+
"type": "integer",
|
|
213
|
+
"minimum": 1
|
|
214
|
+
},
|
|
215
|
+
"p50": {
|
|
216
|
+
"type": "integer",
|
|
217
|
+
"minimum": 0
|
|
218
|
+
},
|
|
219
|
+
"p95": {
|
|
220
|
+
"type": "integer",
|
|
221
|
+
"minimum": 0
|
|
222
|
+
},
|
|
223
|
+
"max": {
|
|
224
|
+
"type": "integer",
|
|
225
|
+
"minimum": 0
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
"description": "Nearest-rank percentiles over the events that carry each duration; null when none did."
|
|
233
|
+
},
|
|
234
|
+
"oob_signoffs": {
|
|
235
|
+
"type": "integer",
|
|
236
|
+
"minimum": 0,
|
|
237
|
+
"description": "Events that recorded an out-of-band sign-off."
|
|
238
|
+
},
|
|
115
239
|
"interpretation": {
|
|
116
240
|
"const": "finding-is-not-proof-of-intent"
|
|
117
241
|
}
|
|
@@ -127,6 +251,47 @@
|
|
|
127
251
|
"last_event": null,
|
|
128
252
|
"by_rule": [],
|
|
129
253
|
"by_surface": [],
|
|
254
|
+
"latency": {
|
|
255
|
+
"hook_latency_ms": null,
|
|
256
|
+
"verify_wall_clock_ms": null,
|
|
257
|
+
"bare_suite_wall_clock_ms": null
|
|
258
|
+
},
|
|
259
|
+
"oob_signoffs": 0,
|
|
260
|
+
"interpretation": "finding-is-not-proof-of-intent"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"schema_version": 1,
|
|
264
|
+
"events": 2,
|
|
265
|
+
"blocked": 2,
|
|
266
|
+
"warnings": 0,
|
|
267
|
+
"sessions": 1,
|
|
268
|
+
"first_event": "2026-09-23T14:00:00.000Z",
|
|
269
|
+
"last_event": "2026-09-23T14:05:00.000Z",
|
|
270
|
+
"by_rule": [
|
|
271
|
+
{
|
|
272
|
+
"rule": "test-deletion",
|
|
273
|
+
"events": 2,
|
|
274
|
+
"blocked": 2,
|
|
275
|
+
"warnings": 0
|
|
276
|
+
}
|
|
277
|
+
],
|
|
278
|
+
"by_surface": [
|
|
279
|
+
{
|
|
280
|
+
"surface": "pretooluse",
|
|
281
|
+
"events": 2
|
|
282
|
+
}
|
|
283
|
+
],
|
|
284
|
+
"latency": {
|
|
285
|
+
"hook_latency_ms": {
|
|
286
|
+
"samples": 2,
|
|
287
|
+
"p50": 40,
|
|
288
|
+
"p95": 55,
|
|
289
|
+
"max": 55
|
|
290
|
+
},
|
|
291
|
+
"verify_wall_clock_ms": null,
|
|
292
|
+
"bare_suite_wall_clock_ms": null
|
|
293
|
+
},
|
|
294
|
+
"oob_signoffs": 1,
|
|
130
295
|
"interpretation": "finding-is-not-proof-of-intent"
|
|
131
296
|
}
|
|
132
297
|
]
|