@qmilab/lodestar-core 0.1.5 → 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/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas/action.d.ts +31 -13
- package/dist/schemas/action.d.ts.map +1 -1
- package/dist/schemas/action.js +20 -1
- package/dist/schemas/action.js.map +1 -1
- package/dist/schemas/approval.d.ts +271 -0
- package/dist/schemas/approval.d.ts.map +1 -0
- package/dist/schemas/approval.js +119 -0
- package/dist/schemas/approval.js.map +1 -0
- package/dist/schemas/belief.d.ts.map +1 -1
- package/dist/schemas/belief.js +7 -1
- package/dist/schemas/belief.js.map +1 -1
- package/dist/schemas/calibration.d.ts +977 -0
- package/dist/schemas/calibration.d.ts.map +1 -0
- package/dist/schemas/calibration.js +187 -0
- package/dist/schemas/calibration.js.map +1 -0
- package/dist/schemas/claim.d.ts.map +1 -1
- package/dist/schemas/claim.js +4 -2
- package/dist/schemas/claim.js.map +1 -1
- package/dist/schemas/common.d.ts.map +1 -1
- package/dist/schemas/common.js +11 -5
- package/dist/schemas/common.js.map +1 -1
- package/dist/schemas/policy.d.ts +768 -0
- package/dist/schemas/policy.d.ts.map +1 -0
- package/dist/schemas/policy.js +200 -0
- package/dist/schemas/policy.js.map +1 -0
- package/dist/schemas/probe-pack.d.ts +152 -0
- package/dist/schemas/probe-pack.d.ts.map +1 -0
- package/dist/schemas/probe-pack.js +140 -0
- package/dist/schemas/probe-pack.js.map +1 -0
- package/dist/schemas/reflection.d.ts +405 -0
- package/dist/schemas/reflection.d.ts.map +1 -0
- package/dist/schemas/reflection.js +154 -0
- package/dist/schemas/reflection.js.map +1 -0
- package/dist/schemas/revision.d.ts.map +1 -1
- package/dist/schemas/revision.js.map +1 -1
- package/dist/schemas/sentinel.d.ts +134 -0
- package/dist/schemas/sentinel.d.ts.map +1 -0
- package/dist/schemas/sentinel.js +97 -0
- package/dist/schemas/sentinel.js.map +1 -0
- package/package.json +2 -7
- package/src/index.ts +18 -0
- package/src/schemas/action.ts +20 -1
- package/src/schemas/approval.ts +136 -0
- package/src/schemas/belief.ts +7 -1
- package/src/schemas/calibration.ts +212 -0
- package/src/schemas/claim.ts +15 -8
- package/src/schemas/common.ts +16 -10
- package/src/schemas/policy.ts +231 -0
- package/src/schemas/probe-pack.ts +169 -0
- package/src/schemas/reflection.ts +166 -0
- package/src/schemas/revision.ts +7 -5
- package/src/schemas/sentinel.ts +104 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Sentinel alerts — the wire format a Sentinel emits when it pattern-matches
|
|
4
|
+
* a suspicious shape in the event stream.
|
|
5
|
+
*
|
|
6
|
+
* Design lock: `docs/architecture/sentinels.md` (and Q7 of
|
|
7
|
+
* `docs/architecture/reflection-pass.md`, which settled the execution model).
|
|
8
|
+
* The short version:
|
|
9
|
+
*
|
|
10
|
+
* - Sentinels are an async tail of the event stream. They never block the
|
|
11
|
+
* Action Kernel; they emit `sentinel.alerted@1` events and a future
|
|
12
|
+
* (additive) `arbitrate` hook honours them on the *next* action that
|
|
13
|
+
* depends on a flagged subject.
|
|
14
|
+
* - This is a governance event, not an Observation. Like
|
|
15
|
+
* `reflection.completed@1`, the payload is the event payload directly and
|
|
16
|
+
* is NOT registered in the observation schema registry.
|
|
17
|
+
*
|
|
18
|
+
* Core owns the wire format only. The base class, the runner, and the
|
|
19
|
+
* concrete sentinels live in `@qmilab/lodestar-harness`.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* What a sentinel alert is *about*. Kept deliberately small — the four
|
|
23
|
+
* epistemic-chain nouns a v0 sentinel can point at.
|
|
24
|
+
*
|
|
25
|
+
* `belief` is the load-bearing kind for the eventual kernel hook: the
|
|
26
|
+
* `arbitrate` lookup scopes recent alerts to a candidate action's
|
|
27
|
+
* `belief_dependencies`, so a sentinel that names a belief gates the next
|
|
28
|
+
* action that leans on it. `tool_sequence` is a synthetic subject — its
|
|
29
|
+
* `id` identifies the *completion* of a matched sequence (the id of the
|
|
30
|
+
* final action in the run), since no single chain-noun owns the pattern.
|
|
31
|
+
*/
|
|
32
|
+
export declare const SentinelSubjectSchema: z.ZodObject<{
|
|
33
|
+
kind: z.ZodEnum<["belief", "action", "decision", "tool_sequence"]>;
|
|
34
|
+
id: z.ZodString;
|
|
35
|
+
}, "strip", z.ZodTypeAny, {
|
|
36
|
+
id: string;
|
|
37
|
+
kind: "belief" | "decision" | "action" | "tool_sequence";
|
|
38
|
+
}, {
|
|
39
|
+
id: string;
|
|
40
|
+
kind: "belief" | "decision" | "action" | "tool_sequence";
|
|
41
|
+
}>;
|
|
42
|
+
export type SentinelSubject = z.infer<typeof SentinelSubjectSchema>;
|
|
43
|
+
/**
|
|
44
|
+
* Triage weight. `critical` is reserved for patterns that, left unflagged,
|
|
45
|
+
* map onto a concrete attack (e.g. the read → external-egress → write
|
|
46
|
+
* exfiltration shape). `warning` is the default for "under-supported but
|
|
47
|
+
* not obviously hostile". `info` is for advisory signal a calibrator may
|
|
48
|
+
* later promote or demote.
|
|
49
|
+
*/
|
|
50
|
+
export declare const SentinelSeveritySchema: z.ZodEnum<["info", "warning", "critical"]>;
|
|
51
|
+
export type SentinelSeverity = z.infer<typeof SentinelSeveritySchema>;
|
|
52
|
+
/**
|
|
53
|
+
* The payload of a `sentinel.alerted@1` event.
|
|
54
|
+
*
|
|
55
|
+
* `observed_event_ids` lists exactly the events the sentinel read to reach
|
|
56
|
+
* this conclusion; they are also the alert envelope's `causal_parent_ids`,
|
|
57
|
+
* so `lodestar report` can walk back from an alert to the events that
|
|
58
|
+
* triggered it.
|
|
59
|
+
*
|
|
60
|
+
* `detail` is rule-specific structured context (offending belief ids, the
|
|
61
|
+
* matched tool run, the confidence that tripped the floor, …). It is a
|
|
62
|
+
* record rather than a discriminated union so a new sentinel can ship
|
|
63
|
+
* without a core schema bump; the human-readable `message` is always
|
|
64
|
+
* present so an alert is legible without decoding `detail`.
|
|
65
|
+
*
|
|
66
|
+
* Every field is required (no optionals besides `rationale_id`) so the
|
|
67
|
+
* event-log writer's canonical hash and `JSON.stringify` never disagree on
|
|
68
|
+
* a dropped `undefined` key — the same discipline the firewall audit
|
|
69
|
+
* events follow.
|
|
70
|
+
*/
|
|
71
|
+
export declare const SentinelAlertPayloadSchema: z.ZodObject<{
|
|
72
|
+
alert_id: z.ZodString;
|
|
73
|
+
sentinel_name: z.ZodString;
|
|
74
|
+
rule: z.ZodString;
|
|
75
|
+
severity: z.ZodEnum<["info", "warning", "critical"]>;
|
|
76
|
+
subject: z.ZodObject<{
|
|
77
|
+
kind: z.ZodEnum<["belief", "action", "decision", "tool_sequence"]>;
|
|
78
|
+
id: z.ZodString;
|
|
79
|
+
}, "strip", z.ZodTypeAny, {
|
|
80
|
+
id: string;
|
|
81
|
+
kind: "belief" | "decision" | "action" | "tool_sequence";
|
|
82
|
+
}, {
|
|
83
|
+
id: string;
|
|
84
|
+
kind: "belief" | "decision" | "action" | "tool_sequence";
|
|
85
|
+
}>;
|
|
86
|
+
message: z.ZodString;
|
|
87
|
+
observed_event_ids: z.ZodArray<z.ZodString, "many">;
|
|
88
|
+
detail: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
89
|
+
detected_at: z.ZodString;
|
|
90
|
+
/**
|
|
91
|
+
* Reserved. A sentinel may attach a generated Explanation id here, the
|
|
92
|
+
* way reflection proposals carry one. v0 sentinels rely on `message` +
|
|
93
|
+
* `detail` and leave this unset (omitted entirely, not `undefined`, so
|
|
94
|
+
* the canonical hash stays stable).
|
|
95
|
+
*/
|
|
96
|
+
rationale_id: z.ZodOptional<z.ZodString>;
|
|
97
|
+
}, "strip", z.ZodTypeAny, {
|
|
98
|
+
message: string;
|
|
99
|
+
subject: {
|
|
100
|
+
id: string;
|
|
101
|
+
kind: "belief" | "decision" | "action" | "tool_sequence";
|
|
102
|
+
};
|
|
103
|
+
detail: Record<string, unknown>;
|
|
104
|
+
observed_event_ids: string[];
|
|
105
|
+
alert_id: string;
|
|
106
|
+
sentinel_name: string;
|
|
107
|
+
rule: string;
|
|
108
|
+
severity: "info" | "warning" | "critical";
|
|
109
|
+
detected_at: string;
|
|
110
|
+
rationale_id?: string | undefined;
|
|
111
|
+
}, {
|
|
112
|
+
message: string;
|
|
113
|
+
subject: {
|
|
114
|
+
id: string;
|
|
115
|
+
kind: "belief" | "decision" | "action" | "tool_sequence";
|
|
116
|
+
};
|
|
117
|
+
detail: Record<string, unknown>;
|
|
118
|
+
observed_event_ids: string[];
|
|
119
|
+
alert_id: string;
|
|
120
|
+
sentinel_name: string;
|
|
121
|
+
rule: string;
|
|
122
|
+
severity: "info" | "warning" | "critical";
|
|
123
|
+
detected_at: string;
|
|
124
|
+
rationale_id?: string | undefined;
|
|
125
|
+
}>;
|
|
126
|
+
export type SentinelAlertPayload = z.infer<typeof SentinelAlertPayloadSchema>;
|
|
127
|
+
/**
|
|
128
|
+
* Event-type literal and version. Use the constants rather than the bare
|
|
129
|
+
* string so a future rename is grep-safe — same convention as
|
|
130
|
+
* `REFLECTION_COMPLETED_EVENT_TYPE`.
|
|
131
|
+
*/
|
|
132
|
+
export declare const SENTINEL_ALERTED_EVENT_TYPE: "sentinel.alerted";
|
|
133
|
+
export declare const SENTINEL_ALERTED_SCHEMA_VERSION: "1";
|
|
134
|
+
//# sourceMappingURL=sentinel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../../src/schemas/sentinel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;EAGhC,CAAA;AACF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAA;AAEnE;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,4CAA0C,CAAA;AAC7E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AAErE;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;IAkBrC;;;;;OAKG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEH,CAAA;AACF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAA;AAE7E;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,oBAA8B,CAAA;AACtE,eAAO,MAAM,+BAA+B,KAAe,CAAA"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { TimestampSchema } from "./common.js";
|
|
3
|
+
/**
|
|
4
|
+
* Sentinel alerts — the wire format a Sentinel emits when it pattern-matches
|
|
5
|
+
* a suspicious shape in the event stream.
|
|
6
|
+
*
|
|
7
|
+
* Design lock: `docs/architecture/sentinels.md` (and Q7 of
|
|
8
|
+
* `docs/architecture/reflection-pass.md`, which settled the execution model).
|
|
9
|
+
* The short version:
|
|
10
|
+
*
|
|
11
|
+
* - Sentinels are an async tail of the event stream. They never block the
|
|
12
|
+
* Action Kernel; they emit `sentinel.alerted@1` events and a future
|
|
13
|
+
* (additive) `arbitrate` hook honours them on the *next* action that
|
|
14
|
+
* depends on a flagged subject.
|
|
15
|
+
* - This is a governance event, not an Observation. Like
|
|
16
|
+
* `reflection.completed@1`, the payload is the event payload directly and
|
|
17
|
+
* is NOT registered in the observation schema registry.
|
|
18
|
+
*
|
|
19
|
+
* Core owns the wire format only. The base class, the runner, and the
|
|
20
|
+
* concrete sentinels live in `@qmilab/lodestar-harness`.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* What a sentinel alert is *about*. Kept deliberately small — the four
|
|
24
|
+
* epistemic-chain nouns a v0 sentinel can point at.
|
|
25
|
+
*
|
|
26
|
+
* `belief` is the load-bearing kind for the eventual kernel hook: the
|
|
27
|
+
* `arbitrate` lookup scopes recent alerts to a candidate action's
|
|
28
|
+
* `belief_dependencies`, so a sentinel that names a belief gates the next
|
|
29
|
+
* action that leans on it. `tool_sequence` is a synthetic subject — its
|
|
30
|
+
* `id` identifies the *completion* of a matched sequence (the id of the
|
|
31
|
+
* final action in the run), since no single chain-noun owns the pattern.
|
|
32
|
+
*/
|
|
33
|
+
export const SentinelSubjectSchema = z.object({
|
|
34
|
+
kind: z.enum(["belief", "action", "decision", "tool_sequence"]),
|
|
35
|
+
id: z.string().min(1),
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* Triage weight. `critical` is reserved for patterns that, left unflagged,
|
|
39
|
+
* map onto a concrete attack (e.g. the read → external-egress → write
|
|
40
|
+
* exfiltration shape). `warning` is the default for "under-supported but
|
|
41
|
+
* not obviously hostile". `info` is for advisory signal a calibrator may
|
|
42
|
+
* later promote or demote.
|
|
43
|
+
*/
|
|
44
|
+
export const SentinelSeveritySchema = z.enum(["info", "warning", "critical"]);
|
|
45
|
+
/**
|
|
46
|
+
* The payload of a `sentinel.alerted@1` event.
|
|
47
|
+
*
|
|
48
|
+
* `observed_event_ids` lists exactly the events the sentinel read to reach
|
|
49
|
+
* this conclusion; they are also the alert envelope's `causal_parent_ids`,
|
|
50
|
+
* so `lodestar report` can walk back from an alert to the events that
|
|
51
|
+
* triggered it.
|
|
52
|
+
*
|
|
53
|
+
* `detail` is rule-specific structured context (offending belief ids, the
|
|
54
|
+
* matched tool run, the confidence that tripped the floor, …). It is a
|
|
55
|
+
* record rather than a discriminated union so a new sentinel can ship
|
|
56
|
+
* without a core schema bump; the human-readable `message` is always
|
|
57
|
+
* present so an alert is legible without decoding `detail`.
|
|
58
|
+
*
|
|
59
|
+
* Every field is required (no optionals besides `rationale_id`) so the
|
|
60
|
+
* event-log writer's canonical hash and `JSON.stringify` never disagree on
|
|
61
|
+
* a dropped `undefined` key — the same discipline the firewall audit
|
|
62
|
+
* events follow.
|
|
63
|
+
*/
|
|
64
|
+
export const SentinelAlertPayloadSchema = z.object({
|
|
65
|
+
alert_id: z.string().min(1),
|
|
66
|
+
sentinel_name: z.string().min(1).describe("Stable name of the emitting sentinel."),
|
|
67
|
+
rule: z
|
|
68
|
+
.string()
|
|
69
|
+
.min(1)
|
|
70
|
+
.describe("Stable id of the specific rule/pattern that fired within the sentinel."),
|
|
71
|
+
severity: SentinelSeveritySchema,
|
|
72
|
+
subject: SentinelSubjectSchema,
|
|
73
|
+
message: z.string().min(1).describe("Human-readable account of what tripped the sentinel."),
|
|
74
|
+
observed_event_ids: z
|
|
75
|
+
.array(z.string())
|
|
76
|
+
.min(1)
|
|
77
|
+
.describe("The events the sentinel read to reach this alert; also the alert's causal parents."),
|
|
78
|
+
detail: z
|
|
79
|
+
.record(z.string(), z.unknown())
|
|
80
|
+
.describe("Rule-specific structured context. May be empty; never undefined."),
|
|
81
|
+
detected_at: TimestampSchema,
|
|
82
|
+
/**
|
|
83
|
+
* Reserved. A sentinel may attach a generated Explanation id here, the
|
|
84
|
+
* way reflection proposals carry one. v0 sentinels rely on `message` +
|
|
85
|
+
* `detail` and leave this unset (omitted entirely, not `undefined`, so
|
|
86
|
+
* the canonical hash stays stable).
|
|
87
|
+
*/
|
|
88
|
+
rationale_id: z.string().optional(),
|
|
89
|
+
});
|
|
90
|
+
/**
|
|
91
|
+
* Event-type literal and version. Use the constants rather than the bare
|
|
92
|
+
* string so a future rename is grep-safe — same convention as
|
|
93
|
+
* `REFLECTION_COMPLETED_EVENT_TYPE`.
|
|
94
|
+
*/
|
|
95
|
+
export const SENTINEL_ALERTED_EVENT_TYPE = "sentinel.alerted";
|
|
96
|
+
export const SENTINEL_ALERTED_SCHEMA_VERSION = "1";
|
|
97
|
+
//# sourceMappingURL=sentinel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentinel.js","sourceRoot":"","sources":["../../src/schemas/sentinel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE7C;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;IAC/D,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACtB,CAAC,CAAA;AAGF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAA;AAG7E;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC;IACjD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,uCAAuC,CAAC;IAClF,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,wEAAwE,CAAC;IACrF,QAAQ,EAAE,sBAAsB;IAChC,OAAO,EAAE,qBAAqB;IAC9B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,sDAAsD,CAAC;IAC3F,kBAAkB,EAAE,CAAC;SAClB,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;SACjB,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,oFAAoF,CAAC;IACjG,MAAM,EAAE,CAAC;SACN,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;SAC/B,QAAQ,CAAC,kEAAkE,CAAC;IAC/E,WAAW,EAAE,eAAe;IAC5B;;;;;OAKG;IACH,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACpC,CAAC,CAAA;AAGF;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,kBAA2B,CAAA;AACtE,MAAM,CAAC,MAAM,+BAA+B,GAAG,GAAY,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qmilab/lodestar-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Schemas and types for the Lodestar epistemic chain. Part of Lodestar, the trust layer for AI agents.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "QMI Lab <hello@qmilab.com>",
|
|
@@ -34,12 +34,7 @@
|
|
|
34
34
|
"default": "./dist/index.js"
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
|
-
"files": [
|
|
38
|
-
"dist",
|
|
39
|
-
"src",
|
|
40
|
-
"README.md",
|
|
41
|
-
"LICENSE"
|
|
42
|
-
],
|
|
37
|
+
"files": ["dist", "src", "README.md", "LICENSE"],
|
|
43
38
|
"publishConfig": {
|
|
44
39
|
"access": "public",
|
|
45
40
|
"provenance": true
|
package/src/index.ts
CHANGED
|
@@ -28,5 +28,23 @@ export * from "./schemas/revision.js"
|
|
|
28
28
|
// Event log envelope
|
|
29
29
|
export * from "./schemas/event.js"
|
|
30
30
|
|
|
31
|
+
// Reflection (Batch 4) — proposals and the reflection.completed@1 payload
|
|
32
|
+
export * from "./schemas/reflection.js"
|
|
33
|
+
|
|
34
|
+
// Calibration — the report wire format + the calibration.computed@1 payload
|
|
35
|
+
export * from "./schemas/calibration.js"
|
|
36
|
+
|
|
37
|
+
// Probe pack format (Batch 4) — the lodestar.probe-pack.json manifest contract
|
|
38
|
+
export * from "./schemas/probe-pack.js"
|
|
39
|
+
|
|
40
|
+
// Sentinels (Batch 4) — the sentinel.alerted@1 alert wire format
|
|
41
|
+
export * from "./schemas/sentinel.js"
|
|
42
|
+
|
|
43
|
+
// Action policy (Policy Kernel) — the Policy / PolicyRule document wire format
|
|
44
|
+
export * from "./schemas/policy.js"
|
|
45
|
+
|
|
46
|
+
// Approval workflow (Policy Kernel) — ApprovalRequest + approval.* event payloads
|
|
47
|
+
export * from "./schemas/approval.js"
|
|
48
|
+
|
|
31
49
|
// Schema registry
|
|
32
50
|
export * as registry from "./registry.js"
|
package/src/schemas/action.ts
CHANGED
|
@@ -60,10 +60,27 @@ export type ActionContract = z.infer<typeof ActionContractSchema>
|
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* Phases an action passes through.
|
|
63
|
+
*
|
|
64
|
+
* `pending_approval` is the parked state: arbitration returned a `hold`
|
|
65
|
+
* (the Policy Kernel's three-valued verdict — see
|
|
66
|
+
* `docs/architecture/policy-kernel.md`), so the action is neither approved
|
|
67
|
+
* nor rejected. An `ApprovalRequest` is opened and the world stays
|
|
68
|
+
* untouched — the two-phase discipline forbids `execute()` from
|
|
69
|
+
* `pending_approval` exactly as it forbids it from `proposed`. Only an
|
|
70
|
+
* Action-Kernel `resolve()` un-parks it: `approval.granted` → `approved`
|
|
71
|
+
* (which then runs the normal `execute()` gate, so TOCTOU revalidation
|
|
72
|
+
* still fires), `approval.denied` / `approval.expired` → `rejected`.
|
|
73
|
+
*
|
|
74
|
+
* Distinct from `halted`, which is a *terminal* mid-execution stop
|
|
75
|
+
* (`executing → halted`); `pending_approval` is a *pre-execution* wait.
|
|
76
|
+
*
|
|
77
|
+
* Additive (ratified 2026-06-03, `policy-kernel.md`): existing logs
|
|
78
|
+
* without this value still parse; readers gain one case.
|
|
63
79
|
*/
|
|
64
80
|
export const ActionPhaseSchema = z.enum([
|
|
65
81
|
"proposed",
|
|
66
82
|
"arbitrating",
|
|
83
|
+
"pending_approval",
|
|
67
84
|
"approved",
|
|
68
85
|
"rejected",
|
|
69
86
|
"executing",
|
|
@@ -100,7 +117,9 @@ export type AuditEvent = z.infer<typeof AuditEventSchema>
|
|
|
100
117
|
*
|
|
101
118
|
* Actions are the seventh link in the epistemic chain.
|
|
102
119
|
* The phase field tracks the action through propose → arbitrate
|
|
103
|
-
* → approved/rejected → executing
|
|
120
|
+
* → approved/rejected/pending_approval → executing
|
|
121
|
+
* → completed/failed/halted. A `pending_approval` action awaits an
|
|
122
|
+
* `ApprovalRequest` resolution before it can reach `approved`.
|
|
104
123
|
*
|
|
105
124
|
* Every Action carries an ActionContract. The Policy Kernel evaluates
|
|
106
125
|
* the contract against current trust assignments and approval requirements
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import { SignatureSchema } from "./actor.js"
|
|
3
|
+
import { TimestampSchema } from "./common.js"
|
|
4
|
+
import { RequiredAuthoritySchema } from "./policy.js"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The approval workflow wire formats — the first-class record of an action
|
|
8
|
+
* parked at `pending_approval` and the events that resolve it.
|
|
9
|
+
*
|
|
10
|
+
* Design lock: `docs/architecture/policy-kernel.md`, "The approval workflow".
|
|
11
|
+
* The discipline mirrors the sentinel / reflection governance events:
|
|
12
|
+
*
|
|
13
|
+
* - These are governance events, NOT Observations. Like `sentinel.alerted@1`,
|
|
14
|
+
* each payload is the event payload directly and is NOT registered in the
|
|
15
|
+
* observation schema registry.
|
|
16
|
+
* - Grant and deny are *distinct event types*, not one event with an
|
|
17
|
+
* `approved` flag. The type *is* the verdict, so a redundant boolean (which
|
|
18
|
+
* could disagree with the type on re-read) is omitted. When the resolution
|
|
19
|
+
* folds back into the action via the Action Kernel's `resolve()`, it lands
|
|
20
|
+
* in the action's existing `approval` field (`ApprovalEvent`), where a
|
|
21
|
+
* single boolean is the natural shape — so the stream view
|
|
22
|
+
* (type-discriminated) and the single-action view agree without duplicating
|
|
23
|
+
* the verdict on the wire.
|
|
24
|
+
* - No optional field is ever set to `undefined` — it is omitted entirely when
|
|
25
|
+
* unset (`deadline` and `reason` in particular), so the event-log writer's
|
|
26
|
+
* `canonicalHash` (undefined → null) and `JSON.stringify` (drops the key)
|
|
27
|
+
* cannot disagree on re-read.
|
|
28
|
+
*
|
|
29
|
+
* Core owns the wire format only. The lifecycle manager — opening a request on
|
|
30
|
+
* a hold, matching a resolution against `required_authority`, driving the
|
|
31
|
+
* Action-Kernel `resolve()` transition — lives in
|
|
32
|
+
* `@qmilab/lodestar-policy-kernel`.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The payload of an `approval.requested@1` event: a parked action awaiting a
|
|
37
|
+
* human (or auto-rule) verdict. `reason` is the matched rule's reason,
|
|
38
|
+
* verbatim. `required_authority` says what an approver must be (checked
|
|
39
|
+
* against the resolver's `Actor`); an empty object means any configured
|
|
40
|
+
* resolver may approve.
|
|
41
|
+
*
|
|
42
|
+
* `deadline` is the proxy's hold timeout (the MCP path cannot hold a
|
|
43
|
+
* `tools/call` open indefinitely without tripping client timeouts); it is
|
|
44
|
+
* *omitted entirely* in the in-process `guard.wrap()` path, where a hold can
|
|
45
|
+
* simply await the resolver — never set to `undefined`.
|
|
46
|
+
*/
|
|
47
|
+
export const ApprovalRequestSchema = z.object({
|
|
48
|
+
request_id: z.string().min(1),
|
|
49
|
+
action_id: z.string().min(1).describe("the parked action, at phase pending_approval"),
|
|
50
|
+
reason: z.string().min(1).describe("the matched rule's reason, verbatim"),
|
|
51
|
+
required_authority: RequiredAuthoritySchema.describe(
|
|
52
|
+
"what an approver must be; checked against the resolver's Actor. Empty object = any configured resolver",
|
|
53
|
+
),
|
|
54
|
+
requested_at: TimestampSchema,
|
|
55
|
+
deadline: TimestampSchema.optional().describe(
|
|
56
|
+
"ISO 8601 hold timeout (proxy path); omitted entirely in-process, never undefined",
|
|
57
|
+
),
|
|
58
|
+
})
|
|
59
|
+
export type ApprovalRequest = z.infer<typeof ApprovalRequestSchema>
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The payload of an `approval.granted@1` event. The event *type* is the
|
|
63
|
+
* verdict — there is no `approved` boolean. `reason` (the approver's note) is
|
|
64
|
+
* omitted entirely when unset.
|
|
65
|
+
*
|
|
66
|
+
* `signature` is an optional Ed25519 signature over the canonical resolution
|
|
67
|
+
* document (`{ request_id, action_id, kind, approver_id, reason?, at }`),
|
|
68
|
+
* produced by the approver's private key. When present it makes the granted
|
|
69
|
+
* event **self-verifying in the log**: a reader can later re-check the grant
|
|
70
|
+
* came from an operator-pinned approver key, not merely trust that the proxy
|
|
71
|
+
* verified it at promotion time. Its `signer_id` equals `approver_id` (the same
|
|
72
|
+
* actor that resolved). Omitted entirely when unset (never `undefined`), so the
|
|
73
|
+
* canonical-hash discipline above carries through; the cross-process proxy path
|
|
74
|
+
* requires it (a forged side-channel grant cannot un-park an action), while the
|
|
75
|
+
* in-process resolver path may omit it (same trusted process, no forgery
|
|
76
|
+
* surface). Hash + verification live in `@qmilab/lodestar-policy-kernel`.
|
|
77
|
+
*/
|
|
78
|
+
export const ApprovalGrantedPayloadSchema = z.object({
|
|
79
|
+
request_id: z.string().min(1),
|
|
80
|
+
action_id: z.string().min(1),
|
|
81
|
+
approver_id: z.string().min(1).describe("actor_id of the resolver"),
|
|
82
|
+
reason: z.string().min(1).optional().describe("approver's note; omitted entirely when unset"),
|
|
83
|
+
at: TimestampSchema,
|
|
84
|
+
signature: SignatureSchema.optional().describe(
|
|
85
|
+
"Ed25519 signature over the canonical resolution; signer_id === approver_id; omitted entirely when unset",
|
|
86
|
+
),
|
|
87
|
+
})
|
|
88
|
+
export type ApprovalGrantedPayload = z.infer<typeof ApprovalGrantedPayloadSchema>
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The payload of an `approval.denied@1` event. Identical shape to
|
|
92
|
+
* `approval.granted@1` — the verdict is carried by the event type, not a
|
|
93
|
+
* field. Defined as its own schema (rather than re-exporting one shared
|
|
94
|
+
* object) so the two event types stay independently evolvable. `signature`
|
|
95
|
+
* follows the same contract as the grant payload (a denial is also authority-
|
|
96
|
+
* bearing — it must not be forgeable into un-holding via a later grant either).
|
|
97
|
+
*/
|
|
98
|
+
export const ApprovalDeniedPayloadSchema = z.object({
|
|
99
|
+
request_id: z.string().min(1),
|
|
100
|
+
action_id: z.string().min(1),
|
|
101
|
+
approver_id: z.string().min(1).describe("actor_id of the resolver"),
|
|
102
|
+
reason: z.string().min(1).optional().describe("approver's note; omitted entirely when unset"),
|
|
103
|
+
at: TimestampSchema,
|
|
104
|
+
signature: SignatureSchema.optional().describe(
|
|
105
|
+
"Ed25519 signature over the canonical resolution; signer_id === approver_id; omitted entirely when unset",
|
|
106
|
+
),
|
|
107
|
+
})
|
|
108
|
+
export type ApprovalDeniedPayload = z.infer<typeof ApprovalDeniedPayloadSchema>
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The payload of an `approval.expired@1` event: the deadline passed with no
|
|
112
|
+
* human resolution. Carries no `approver_id` — no actor resolved it; the
|
|
113
|
+
* passage of the deadline did. The Action Kernel transitions the parked action
|
|
114
|
+
* to `rejected` on receipt (a timed-out hold is a soft denial the agent
|
|
115
|
+
* re-proposes; durable resume is deferred — `policy-kernel.md`).
|
|
116
|
+
*/
|
|
117
|
+
export const ApprovalExpiredPayloadSchema = z.object({
|
|
118
|
+
request_id: z.string().min(1),
|
|
119
|
+
action_id: z.string().min(1),
|
|
120
|
+
at: TimestampSchema,
|
|
121
|
+
})
|
|
122
|
+
export type ApprovalExpiredPayload = z.infer<typeof ApprovalExpiredPayloadSchema>
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Event-type literals and versions. Use the constants rather than the bare
|
|
126
|
+
* strings so a future rename is grep-safe — same convention as
|
|
127
|
+
* `SENTINEL_ALERTED_EVENT_TYPE` and `REFLECTION_COMPLETED_EVENT_TYPE`.
|
|
128
|
+
*/
|
|
129
|
+
export const APPROVAL_REQUESTED_EVENT_TYPE = "approval.requested" as const
|
|
130
|
+
export const APPROVAL_REQUESTED_SCHEMA_VERSION = "1" as const
|
|
131
|
+
export const APPROVAL_GRANTED_EVENT_TYPE = "approval.granted" as const
|
|
132
|
+
export const APPROVAL_GRANTED_SCHEMA_VERSION = "1" as const
|
|
133
|
+
export const APPROVAL_DENIED_EVENT_TYPE = "approval.denied" as const
|
|
134
|
+
export const APPROVAL_DENIED_SCHEMA_VERSION = "1" as const
|
|
135
|
+
export const APPROVAL_EXPIRED_EVENT_TYPE = "approval.expired" as const
|
|
136
|
+
export const APPROVAL_EXPIRED_SCHEMA_VERSION = "1" as const
|
package/src/schemas/belief.ts
CHANGED
|
@@ -19,7 +19,13 @@ import {
|
|
|
19
19
|
export const TruthStatusSchema = z.enum(["unverified", "supported", "contradicted", "superseded"])
|
|
20
20
|
export type TruthStatus = z.infer<typeof TruthStatusSchema>
|
|
21
21
|
|
|
22
|
-
export const RetrievalStatusSchema = z.enum([
|
|
22
|
+
export const RetrievalStatusSchema = z.enum([
|
|
23
|
+
"hidden",
|
|
24
|
+
"restricted",
|
|
25
|
+
"normal",
|
|
26
|
+
"privileged_only",
|
|
27
|
+
"blocked",
|
|
28
|
+
])
|
|
23
29
|
export type RetrievalStatus = z.infer<typeof RetrievalStatusSchema>
|
|
24
30
|
|
|
25
31
|
export const SecurityStatusSchema = z.enum(["clean", "suspicious", "quarantined", "malicious"])
|