@polydeukes/core 0.4.0 → 0.5.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/config.d.ts +61 -34
- package/dist/config.js +158 -109
- package/dist/exit-codes.d.ts +9 -9
- package/dist/exit-codes.js +9 -9
- package/dist/fail-policy.d.ts +7 -7
- package/dist/fail-policy.js +7 -7
- package/dist/index.d.ts +12 -14
- package/dist/index.js +6 -7
- package/dist/is-plain-object.d.ts +1 -1
- package/dist/is-plain-object.js +1 -1
- package/dist/protected-paths.d.ts +3 -3
- package/dist/protected-paths.js +3 -3
- package/dist/telemetry.d.ts +15 -17
- package/dist/telemetry.js +21 -23
- package/dist/transcript.d.ts +16 -19
- package/dist/transcript.js +11 -14
- package/package.json +1 -1
- package/schema/polydeukes.schema.json +30 -4
package/dist/fail-policy.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* fail-policy — the failure-kind → fail-mode policy table
|
|
2
|
+
* fail-policy — the failure-kind → fail-mode policy table.
|
|
3
3
|
*
|
|
4
4
|
* Pure and total: classifying a failure and mapping it to an exit code never
|
|
5
5
|
* performs I/O and never throws. The single source of truth for "which failures
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
/** How a failure resolves: 'open' passes the call through, 'closed' blocks it. */
|
|
9
9
|
export type FailMode = 'open' | 'closed';
|
|
10
10
|
/**
|
|
11
|
-
* The registered failure kinds
|
|
11
|
+
* The registered failure kinds. Gate-integrity failures
|
|
12
12
|
* (evidence-absence / input-parse / undecidable-structure) fail closed;
|
|
13
13
|
* observability failures fail open so measurement loss never holds work hostage.
|
|
14
14
|
*/
|
|
@@ -16,14 +16,14 @@ export type FailureKind = 'evidence-absence' | 'input-parse' | 'undecidable-stru
|
|
|
16
16
|
/**
|
|
17
17
|
* Resolve a failure kind to its {@link FailMode} via the policy table.
|
|
18
18
|
*
|
|
19
|
-
* fail-closed default
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* fail-closed default: any unregistered kind — including '' and prototype-pollution
|
|
20
|
+
* keys — resolves to 'closed'. "Cannot classify" means block. Pure and total: never
|
|
21
|
+
* throws, no I/O, no logging.
|
|
22
22
|
*/
|
|
23
23
|
export declare function resolveFailMode(kind: string): FailMode;
|
|
24
24
|
/**
|
|
25
|
-
* Map a {@link FailMode} to the covenant protocol's exit code
|
|
25
|
+
* Map a {@link FailMode} to the covenant protocol's exit code:
|
|
26
26
|
* 'open' → {@link EXIT_UPHOLD}, 'closed' → {@link EXIT_BREAK_BLOCKING}.
|
|
27
|
-
* Reuses
|
|
27
|
+
* Reuses the protocol's constants — no independent numeric literals here.
|
|
28
28
|
*/
|
|
29
29
|
export declare function failModeToExitCode(mode: FailMode): 0 | 2;
|
package/dist/fail-policy.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* fail-policy — the failure-kind → fail-mode policy table
|
|
2
|
+
* fail-policy — the failure-kind → fail-mode policy table.
|
|
3
3
|
*
|
|
4
4
|
* Pure and total: classifying a failure and mapping it to an exit code never
|
|
5
5
|
* performs I/O and never throws. The single source of truth for "which failures
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { EXIT_BREAK_BLOCKING, EXIT_UPHOLD } from './exit-codes.js';
|
|
9
9
|
/**
|
|
10
|
-
* Policy table
|
|
10
|
+
* Policy table. Null-prototype so lookups can never reach
|
|
11
11
|
* Object.prototype members ('__proto__', 'toString', …) — those must resolve
|
|
12
12
|
* to the fail-closed default, not to an inherited function.
|
|
13
13
|
*/
|
|
@@ -20,17 +20,17 @@ const FAIL_POLICY = Object.assign(Object.create(null), {
|
|
|
20
20
|
/**
|
|
21
21
|
* Resolve a failure kind to its {@link FailMode} via the policy table.
|
|
22
22
|
*
|
|
23
|
-
* fail-closed default
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* fail-closed default: any unregistered kind — including '' and prototype-pollution
|
|
24
|
+
* keys — resolves to 'closed'. "Cannot classify" means block. Pure and total: never
|
|
25
|
+
* throws, no I/O, no logging.
|
|
26
26
|
*/
|
|
27
27
|
export function resolveFailMode(kind) {
|
|
28
28
|
return FAIL_POLICY[kind] ?? 'closed';
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* Map a {@link FailMode} to the covenant protocol's exit code
|
|
31
|
+
* Map a {@link FailMode} to the covenant protocol's exit code:
|
|
32
32
|
* 'open' → {@link EXIT_UPHOLD}, 'closed' → {@link EXIT_BREAK_BLOCKING}.
|
|
33
|
-
* Reuses
|
|
33
|
+
* Reuses the protocol's constants — no independent numeric literals here.
|
|
34
34
|
*/
|
|
35
35
|
export function failModeToExitCode(mode) {
|
|
36
36
|
return mode === 'open' ? EXIT_UPHOLD : EXIT_BREAK_BLOCKING;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @polydeukes/core — the thin, domain- and agent-agnostic core.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* appendRecordFailOpen — the fail-open wrapper promoted by CORE-05).
|
|
4
|
+
* Alpha. Carries the covenant protocol, the ROI telemetry collector, and the config
|
|
5
|
+
* schema. Pure types and functions, except telemetry's confined I/O functions
|
|
6
|
+
* (appendRecord / readRecords / appendRecordFailOpen).
|
|
8
7
|
* See https://github.com/huskyhoochu/polydeukes
|
|
9
8
|
*/
|
|
10
|
-
export { ConfigValidationError, DEFAULT_TELEMETRY_LOG_PATH, type DisciplineEntry, type DisciplineForbid, defineConfig, type LanguageProfile, type PolydeukesConfig, type ResolvedConfig, type ResolvedLanguageProfile, } from './config.js';
|
|
9
|
+
export { ConfigValidationError, DEFAULT_TELEMETRY_LOG_PATH, type DisciplineDraft, type DisciplineEntry, type DisciplineForbid, defineConfig, type EnforceLevel, type LanguageProfile, type PolydeukesConfig, type ResolvedConfig, type ResolvedLanguageProfile, } from './config.js';
|
|
11
10
|
export { EXIT_BREAK_BLOCKING, EXIT_BREAK_NON_BLOCKING, EXIT_UPHOLD, } from './exit-codes.js';
|
|
12
11
|
export { type FailMode, type FailureKind, failModeToExitCode, resolveFailMode, } from './fail-policy.js';
|
|
13
12
|
export { isPlainObject } from './is-plain-object.js';
|
|
@@ -15,7 +14,7 @@ export { normalizeProtectedPaths } from './protected-paths.js';
|
|
|
15
14
|
export { aggregateGain, appendRecord, appendRecordFailOpen, formatRecordLine, type GainSummary, parseRecordLine, readRecords, runGain, type TelemetryEvent, type TelemetryRecord, } from './telemetry.js';
|
|
16
15
|
export { type CanonicalTranscript, noopTranscript, type SubagentInvocation, type TranscriptToolCall, type TranscriptUserMessage, transcriptFromInput, } from './transcript.js';
|
|
17
16
|
/**
|
|
18
|
-
* `FileChange` — one file's mutation evidence around the judged call
|
|
17
|
+
* `FileChange` — one file's mutation evidence around the judged call.
|
|
19
18
|
*
|
|
20
19
|
* Agent-neutral, discriminated by `kind`: a deletion is first-class evidence rather
|
|
21
20
|
* than an unrepresentable case, and impossible states (a deletion with resulting
|
|
@@ -39,14 +38,13 @@ export type FileChange = {
|
|
|
39
38
|
pre?: string;
|
|
40
39
|
};
|
|
41
40
|
/**
|
|
42
|
-
* `CovenantInput` — the agent-neutral input IR a covenant judges
|
|
41
|
+
* `CovenantInput` — the agent-neutral input IR a covenant judges.
|
|
43
42
|
*
|
|
44
43
|
* Adapters up-translate their own agent payloads into this shape and pipe it as
|
|
45
44
|
* stdin-JSON. The vocabulary carries no agent/tool literals; concrete tool or
|
|
46
45
|
* subagent names are *values* an adapter fills in, never part of the core's type.
|
|
47
|
-
* Evidence has exactly one home
|
|
48
|
-
*
|
|
49
|
-
* can stand in for it.
|
|
46
|
+
* Evidence has exactly one home, the call element it belongs to: `fileChange` absent
|
|
47
|
+
* means "this call is unproven", and no sibling call's evidence can stand in for it.
|
|
50
48
|
*/
|
|
51
49
|
export type CovenantInput = {
|
|
52
50
|
toolCalls: {
|
|
@@ -62,7 +60,7 @@ export type CovenantInput = {
|
|
|
62
60
|
}[];
|
|
63
61
|
};
|
|
64
62
|
/**
|
|
65
|
-
* `CovenantVerdict` — the result a covenant body produces
|
|
63
|
+
* `CovenantVerdict` — the result a covenant body produces.
|
|
66
64
|
*
|
|
67
65
|
* Either the promise was upheld, or it was broken with a human-readable reason.
|
|
68
66
|
* Maps to an exit code via {@link verdictToExitCode}.
|
|
@@ -76,7 +74,7 @@ export type CovenantVerdict = {
|
|
|
76
74
|
/**
|
|
77
75
|
* Deserialize stdin-JSON into a {@link CovenantInput} (the protocol's reverse direction).
|
|
78
76
|
*
|
|
79
|
-
* fail-closed
|
|
77
|
+
* fail-closed: this never throws. Any failure — unparseable JSON, an empty
|
|
80
78
|
* payload, a parsed value that is not an object, or a missing required collection —
|
|
81
79
|
* resolves to a blocking `{ ok: false, exitCode: 2 }`. "Cannot judge" means block,
|
|
82
80
|
* so an unjudgeable input can never be mistaken for a valid one.
|
|
@@ -89,7 +87,7 @@ export declare function parseInput(stdinJson: string): {
|
|
|
89
87
|
exitCode: 2;
|
|
90
88
|
};
|
|
91
89
|
/**
|
|
92
|
-
* Flatten every call's evidence into one array in call order
|
|
90
|
+
* Flatten every call's evidence into one array in call order.
|
|
93
91
|
*
|
|
94
92
|
* The one traversal for consumers that need no attribution (discipline scope, delta
|
|
95
93
|
* judging): calls without evidence are skipped, never substituted for.
|
|
@@ -98,7 +96,7 @@ export declare function allFileChanges(input: CovenantInput): FileChange[];
|
|
|
98
96
|
/**
|
|
99
97
|
* Map a {@link CovenantVerdict} to an exit code (the protocol's forward direction).
|
|
100
98
|
*
|
|
101
|
-
* Responsibility boundary
|
|
99
|
+
* Responsibility boundary: the body emits `0` when upheld and `1` when
|
|
102
100
|
* broken — never the blocking `2`. Translating `1` into `2` is the wrapper's policy.
|
|
103
101
|
*/
|
|
104
102
|
export declare function verdictToExitCode(verdict: CovenantVerdict): 0 | 1;
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @polydeukes/core — the thin, domain- and agent-agnostic core.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* appendRecordFailOpen — the fail-open wrapper promoted by CORE-05).
|
|
4
|
+
* Alpha. Carries the covenant protocol, the ROI telemetry collector, and the config
|
|
5
|
+
* schema. Pure types and functions, except telemetry's confined I/O functions
|
|
6
|
+
* (appendRecord / readRecords / appendRecordFailOpen).
|
|
8
7
|
* See https://github.com/huskyhoochu/polydeukes
|
|
9
8
|
*/
|
|
10
9
|
import { EXIT_BREAK_BLOCKING, EXIT_BREAK_NON_BLOCKING, EXIT_UPHOLD } from './exit-codes.js';
|
|
@@ -19,7 +18,7 @@ export { noopTranscript, transcriptFromInput, } from './transcript.js';
|
|
|
19
18
|
/**
|
|
20
19
|
* Deserialize stdin-JSON into a {@link CovenantInput} (the protocol's reverse direction).
|
|
21
20
|
*
|
|
22
|
-
* fail-closed
|
|
21
|
+
* fail-closed: this never throws. Any failure — unparseable JSON, an empty
|
|
23
22
|
* payload, a parsed value that is not an object, or a missing required collection —
|
|
24
23
|
* resolves to a blocking `{ ok: false, exitCode: 2 }`. "Cannot judge" means block,
|
|
25
24
|
* so an unjudgeable input can never be mistaken for a valid one.
|
|
@@ -44,7 +43,7 @@ export function parseInput(stdinJson) {
|
|
|
44
43
|
return { ok: true, value: candidate };
|
|
45
44
|
}
|
|
46
45
|
/**
|
|
47
|
-
* Flatten every call's evidence into one array in call order
|
|
46
|
+
* Flatten every call's evidence into one array in call order.
|
|
48
47
|
*
|
|
49
48
|
* The one traversal for consumers that need no attribution (discipline scope, delta
|
|
50
49
|
* judging): calls without evidence are skipped, never substituted for.
|
|
@@ -60,7 +59,7 @@ export function allFileChanges(input) {
|
|
|
60
59
|
/**
|
|
61
60
|
* Map a {@link CovenantVerdict} to an exit code (the protocol's forward direction).
|
|
62
61
|
*
|
|
63
|
-
* Responsibility boundary
|
|
62
|
+
* Responsibility boundary: the body emits `0` when upheld and `1` when
|
|
64
63
|
* broken — never the blocking `2`. Translating `1` into `2` is the wrapper's policy.
|
|
65
64
|
*/
|
|
66
65
|
export function verdictToExitCode(verdict) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `isPlainObject` — the workspace's single canonical plain-object predicate.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* typeof `object`, non-null, not an array.
|
|
5
5
|
*/
|
|
6
6
|
export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
package/dist/is-plain-object.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `isPlainObject` — the workspace's single canonical plain-object predicate.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* typeof `object`, non-null, not an array.
|
|
5
5
|
*/
|
|
6
6
|
export function isPlainObject(value) {
|
|
7
7
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Protected-path normalization — the `protectedPaths` list normalized into the literal
|
|
3
|
-
* path strings the dispatcher contract expects
|
|
4
|
-
*
|
|
3
|
+
* path strings the dispatcher contract expects. Pure string transformation — zero file
|
|
4
|
+
* I/O, no glob expansion, no path resolution.
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
|
-
* Normalize the protection surface from a config-shaped spec
|
|
7
|
+
* Normalize the protection surface from a config-shaped spec.
|
|
8
8
|
*
|
|
9
9
|
* Processing order: trim each entry → strip a leading `./` → strip a trailing `/` → drop
|
|
10
10
|
* empty-equivalent entries → dedupe on the normalized value, keeping the first occurrence.
|
package/dist/protected-paths.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Protected-path normalization — the `protectedPaths` list normalized into the literal
|
|
3
|
-
* path strings the dispatcher contract expects
|
|
4
|
-
*
|
|
3
|
+
* path strings the dispatcher contract expects. Pure string transformation — zero file
|
|
4
|
+
* I/O, no glob expansion, no path resolution.
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
|
-
* Normalize the protection surface from a config-shaped spec
|
|
7
|
+
* Normalize the protection surface from a config-shaped spec.
|
|
8
8
|
*
|
|
9
9
|
* Processing order: trim each entry → strip a leading `./` → strip a trailing `/` → drop
|
|
10
10
|
* empty-equivalent entries → dedupe on the normalized value, keeping the first occurrence.
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ROI telemetry — the single shared collector and its `gain` aggregation
|
|
2
|
+
* ROI telemetry — the single shared collector and its `gain` aggregation.
|
|
3
3
|
*
|
|
4
|
-
* One record is one line of 4-field TSV
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* aggregation are pure. This is the sole collector: later work calls this API rather
|
|
8
|
-
* than building its own logger.
|
|
4
|
+
* One record is one line of 4-field TSV; one append is one write call. I/O is confined to
|
|
5
|
+
* exactly two functions — {@link appendRecord} (the only write) and {@link readRecords}
|
|
6
|
+
* (the only read). Formatting, parsing, and aggregation are pure.
|
|
9
7
|
*/
|
|
10
8
|
/**
|
|
11
9
|
* The six telemetry events. `witnessed` is a first-class event, not a flag on `passed`:
|
|
@@ -21,7 +19,7 @@
|
|
|
21
19
|
*/
|
|
22
20
|
export type TelemetryEvent = 'passed' | 'blocked' | 'witnessed' | 'advised' | 'skipped' | 'unattributed';
|
|
23
21
|
/**
|
|
24
|
-
* `TelemetryRecord` — one measured covenant outcome
|
|
22
|
+
* `TelemetryRecord` — one measured covenant outcome.
|
|
25
23
|
*
|
|
26
24
|
* `subject` is the judged target (a file path, etc.); `-` is the documented sentinel
|
|
27
25
|
* for "no subject", carried round-trip like any other value.
|
|
@@ -54,13 +52,13 @@ export declare function formatRecordLine(record: TelemetryRecord): string;
|
|
|
54
52
|
*/
|
|
55
53
|
export declare function parseRecordLine(line: string): TelemetryRecord | null;
|
|
56
54
|
/**
|
|
57
|
-
* Append one record to the log at `path` — the only write I/O
|
|
55
|
+
* Append one record to the log at `path` — the only write I/O.
|
|
58
56
|
*
|
|
59
57
|
* Exactly one {@link appendFileSync} call per record, writing {@link formatRecordLine}
|
|
60
58
|
* verbatim. Relying on POSIX `O_APPEND` single-write semantics, concurrent appends do
|
|
61
59
|
* not interleave lines.
|
|
62
60
|
*
|
|
63
|
-
* fail-open
|
|
61
|
+
* fail-open: any fs failure — bad path, permissions, disk — returns
|
|
64
62
|
* `{ ok: false }` and never throws. This is deliberately the opposite direction of the
|
|
65
63
|
* covenant path's fail-closed: the worst outcome of telemetry is a missing datum, never
|
|
66
64
|
* a blocked workflow.
|
|
@@ -69,16 +67,16 @@ export declare function appendRecord(path: string, record: TelemetryRecord): {
|
|
|
69
67
|
ok: boolean;
|
|
70
68
|
};
|
|
71
69
|
/**
|
|
72
|
-
* Append one telemetry record fail-open, timestamping it here
|
|
70
|
+
* Append one telemetry record fail-open, timestamping it here.
|
|
73
71
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
72
|
+
* {@link appendRecord} is deliberately mkdir-free — for it an absent directory is just a
|
|
73
|
+
* fail-open `{ ok: false }` — so this wrapper carries the parent-directory guarantee. The
|
|
74
|
+
* mkdir and the append share one try block, and a failure of either never alters the
|
|
75
|
+
* caller's verdict and never propagates.
|
|
78
76
|
*/
|
|
79
77
|
export declare function appendRecordFailOpen(telemetryPath: string, record: Omit<TelemetryRecord, 'timestamp'>): void;
|
|
80
78
|
/**
|
|
81
|
-
* Read every record from the log at `path` — the only read I/O
|
|
79
|
+
* Read every record from the log at `path` — the only read I/O.
|
|
82
80
|
*
|
|
83
81
|
* fail-open: an absent file or any read error returns `{ records: [], skipped: 0 }`
|
|
84
82
|
* (an absent log means "nothing collected yet"), never throwing. Corrupt lines
|
|
@@ -90,14 +88,14 @@ export declare function readRecords(path: string): {
|
|
|
90
88
|
skipped: number;
|
|
91
89
|
};
|
|
92
90
|
/**
|
|
93
|
-
* Aggregate records into per-label event counts (
|
|
91
|
+
* Aggregate records into per-label event counts (pure).
|
|
94
92
|
*
|
|
95
93
|
* Each label gets its own counter across all six events, so a corrupt or missing
|
|
96
94
|
* event never bleeds counts between labels.
|
|
97
95
|
*/
|
|
98
96
|
export declare function aggregateGain(records: TelemetryRecord[]): GainSummary;
|
|
99
97
|
/**
|
|
100
|
-
* `gain` entry point — read the log at `path`, aggregate, and render
|
|
98
|
+
* `gain` entry point — read the log at `path`, aggregate, and render.
|
|
101
99
|
*
|
|
102
100
|
* Composes {@link readRecords} + {@link aggregateGain} + a pure renderer. An absent or
|
|
103
101
|
* empty log yields `no telemetry collected`; a corrupt line is skipped upstream, reported
|
package/dist/telemetry.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ROI telemetry — the single shared collector and its `gain` aggregation
|
|
2
|
+
* ROI telemetry — the single shared collector and its `gain` aggregation.
|
|
3
3
|
*
|
|
4
|
-
* One record is one line of 4-field TSV
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* aggregation are pure. This is the sole collector: later work calls this API rather
|
|
8
|
-
* than building its own logger.
|
|
4
|
+
* One record is one line of 4-field TSV; one append is one write call. I/O is confined to
|
|
5
|
+
* exactly two functions — {@link appendRecord} (the only write) and {@link readRecords}
|
|
6
|
+
* (the only read). Formatting, parsing, and aggregation are pure.
|
|
9
7
|
*/
|
|
10
8
|
import { appendFileSync, mkdirSync, readFileSync } from 'node:fs';
|
|
11
9
|
import { dirname } from 'node:path';
|
|
@@ -22,15 +20,15 @@ const VALID_EVENTS = [
|
|
|
22
20
|
* The event name `witnessed` was written under before the rename — a read-only migration
|
|
23
21
|
* seam, never a value this module emits.
|
|
24
22
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
23
|
+
* A log written before the rename still carries the old name, and rejecting those rows as
|
|
24
|
+
* corrupt would discard the measurement rather than migrate it. Compatibility runs one way —
|
|
25
|
+
* {@link formatRecordLine} has no path back to this name — and the match is the exact
|
|
26
|
+
* literal, so a genuinely corrupt field is still rejected rather than coerced into a
|
|
27
|
+
* fabricated record.
|
|
30
28
|
*/
|
|
31
29
|
const LEGACY_WITNESSED_EVENT = 'bypassed';
|
|
32
30
|
/**
|
|
33
|
-
* Replace tab/newline/carriage-return with single spaces
|
|
31
|
+
* Replace tab/newline/carriage-return with single spaces.
|
|
34
32
|
*
|
|
35
33
|
* Without this, a tab or newline inside a field would fabricate extra TSV fields or
|
|
36
34
|
* extra lines — a record is always exactly one line.
|
|
@@ -73,13 +71,13 @@ export function parseRecordLine(line) {
|
|
|
73
71
|
return { timestamp, event: resolved, label, subject };
|
|
74
72
|
}
|
|
75
73
|
/**
|
|
76
|
-
* Append one record to the log at `path` — the only write I/O
|
|
74
|
+
* Append one record to the log at `path` — the only write I/O.
|
|
77
75
|
*
|
|
78
76
|
* Exactly one {@link appendFileSync} call per record, writing {@link formatRecordLine}
|
|
79
77
|
* verbatim. Relying on POSIX `O_APPEND` single-write semantics, concurrent appends do
|
|
80
78
|
* not interleave lines.
|
|
81
79
|
*
|
|
82
|
-
* fail-open
|
|
80
|
+
* fail-open: any fs failure — bad path, permissions, disk — returns
|
|
83
81
|
* `{ ok: false }` and never throws. This is deliberately the opposite direction of the
|
|
84
82
|
* covenant path's fail-closed: the worst outcome of telemetry is a missing datum, never
|
|
85
83
|
* a blocked workflow.
|
|
@@ -94,12 +92,12 @@ export function appendRecord(path, record) {
|
|
|
94
92
|
}
|
|
95
93
|
}
|
|
96
94
|
/**
|
|
97
|
-
* Append one telemetry record fail-open, timestamping it here
|
|
95
|
+
* Append one telemetry record fail-open, timestamping it here.
|
|
98
96
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
97
|
+
* {@link appendRecord} is deliberately mkdir-free — for it an absent directory is just a
|
|
98
|
+
* fail-open `{ ok: false }` — so this wrapper carries the parent-directory guarantee. The
|
|
99
|
+
* mkdir and the append share one try block, and a failure of either never alters the
|
|
100
|
+
* caller's verdict and never propagates.
|
|
103
101
|
*/
|
|
104
102
|
export function appendRecordFailOpen(telemetryPath, record) {
|
|
105
103
|
try {
|
|
@@ -111,7 +109,7 @@ export function appendRecordFailOpen(telemetryPath, record) {
|
|
|
111
109
|
}
|
|
112
110
|
}
|
|
113
111
|
/**
|
|
114
|
-
* Read every record from the log at `path` — the only read I/O
|
|
112
|
+
* Read every record from the log at `path` — the only read I/O.
|
|
115
113
|
*
|
|
116
114
|
* fail-open: an absent file or any read error returns `{ records: [], skipped: 0 }`
|
|
117
115
|
* (an absent log means "nothing collected yet"), never throwing. Corrupt lines
|
|
@@ -143,7 +141,7 @@ export function readRecords(path) {
|
|
|
143
141
|
return { records, skipped };
|
|
144
142
|
}
|
|
145
143
|
/**
|
|
146
|
-
* Aggregate records into per-label event counts (
|
|
144
|
+
* Aggregate records into per-label event counts (pure).
|
|
147
145
|
*
|
|
148
146
|
* Each label gets its own counter across all six events, so a corrupt or missing
|
|
149
147
|
* event never bleeds counts between labels.
|
|
@@ -169,7 +167,7 @@ export function aggregateGain(records) {
|
|
|
169
167
|
* Render a {@link GainSummary} into human-readable lines (pure).
|
|
170
168
|
*
|
|
171
169
|
* Each label is mentioned with its passed/blocked/witnessed/advised/skipped/unattributed
|
|
172
|
-
* counts; each is a distinct column, never folded into another
|
|
170
|
+
* counts; each is a distinct column, never folded into another. A non-zero
|
|
173
171
|
* corrupt-line count is reported rather than hidden — silent skipping would mask log
|
|
174
172
|
* corruption.
|
|
175
173
|
*
|
|
@@ -191,7 +189,7 @@ function renderGain(summary, skipped) {
|
|
|
191
189
|
return lines.join('\n');
|
|
192
190
|
}
|
|
193
191
|
/**
|
|
194
|
-
* `gain` entry point — read the log at `path`, aggregate, and render
|
|
192
|
+
* `gain` entry point — read the log at `path`, aggregate, and render.
|
|
195
193
|
*
|
|
196
194
|
* Composes {@link readRecords} + {@link aggregateGain} + a pure renderer. An absent or
|
|
197
195
|
* empty log yields `no telemetry collected`; a corrupt line is skipped upstream, reported
|
package/dist/transcript.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `CanonicalTranscript` — the agent-neutral session-query seam
|
|
2
|
+
* `CanonicalTranscript` — the agent-neutral session-query seam.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* query vocabulary. Pure types and functions, zero I/O.
|
|
4
|
+
* This seam does not replace `CovenantInput`. The IR is the *data* a covenant judges;
|
|
5
|
+
* `CanonicalTranscript` is the *behavioral seam* that queries session data, and the IR is
|
|
6
|
+
* one source it can wrap. Concrete transcript formats stay in adapters; the core knows only
|
|
7
|
+
* the query vocabulary. Pure types and functions, zero I/O.
|
|
9
8
|
*/
|
|
10
9
|
import type { CovenantInput } from './index.js';
|
|
11
10
|
/** One subagent invocation observed in the session. `kind` is an adapter-supplied value. */
|
|
@@ -13,7 +12,7 @@ export type SubagentInvocation = {
|
|
|
13
12
|
kind: string;
|
|
14
13
|
};
|
|
15
14
|
/**
|
|
16
|
-
* One user message observed in the session
|
|
15
|
+
* One user message observed in the session.
|
|
17
16
|
*
|
|
18
17
|
* `timestampMs` is epoch milliseconds. Its absence means the source cannot prove
|
|
19
18
|
* freshness — the fail-closed signal a witness consumer must treat as "not fresh".
|
|
@@ -23,10 +22,10 @@ export type TranscriptUserMessage = {
|
|
|
23
22
|
timestampMs?: number;
|
|
24
23
|
};
|
|
25
24
|
/**
|
|
26
|
-
* One tool call observed in the session
|
|
27
|
-
*
|
|
25
|
+
* One tool call observed in the session. `name` and `args` are adapter-supplied values —
|
|
26
|
+
* the core knows the query vocabulary, never a tool's name.
|
|
28
27
|
*
|
|
29
|
-
* `succeeded` is three-valued
|
|
28
|
+
* `succeeded` is three-valued: `true` = it ran and reported success,
|
|
30
29
|
* `false` = it ran and reported an error, was blocked, or was refused, and absent = the
|
|
31
30
|
* provider cannot observe results at all. A consumer that treats the call as evidence
|
|
32
31
|
* accepts only `true`, so the latter two share a disposition while staying diagnosable.
|
|
@@ -37,7 +36,7 @@ export type TranscriptToolCall = {
|
|
|
37
36
|
succeeded?: boolean;
|
|
38
37
|
};
|
|
39
38
|
/**
|
|
40
|
-
* `CanonicalTranscript` — what a covenant may ask about the session
|
|
39
|
+
* `CanonicalTranscript` — what a covenant may ask about the session.
|
|
41
40
|
*
|
|
42
41
|
* Synchronous by design (covenant bodies are short-lived CLI processes) and
|
|
43
42
|
* verdict-free: the seam carries facts only; TTL filtering and token matching belong
|
|
@@ -52,13 +51,13 @@ export type CanonicalTranscript = {
|
|
|
52
51
|
findToolCalls(name?: string): TranscriptToolCall[];
|
|
53
52
|
};
|
|
54
53
|
/**
|
|
55
|
-
* The injection-absent default
|
|
54
|
+
* The injection-absent default: every query answers "nothing happened".
|
|
56
55
|
* A witness consumer naturally converges to fail-closed — no evidence, no skip — and
|
|
57
56
|
* so does a precedent consumer (no evidence, gate stays shut).
|
|
58
57
|
*/
|
|
59
58
|
export declare const noopTranscript: CanonicalTranscript;
|
|
60
59
|
/**
|
|
61
|
-
* Wrap a {@link CovenantInput} as a {@link CanonicalTranscript}
|
|
60
|
+
* Wrap a {@link CovenantInput} as a {@link CanonicalTranscript}.
|
|
62
61
|
*
|
|
63
62
|
* Exposes `subagentSpawns` as invocations (filtered when a kind is given) and
|
|
64
63
|
* `userMessages` with `timestampMs` omitted — the bare IR cannot prove freshness,
|
|
@@ -66,11 +65,9 @@ export declare const noopTranscript: CanonicalTranscript;
|
|
|
66
65
|
* Order preserved; the input is never mutated, and every query returns fresh
|
|
67
66
|
* objects so consumers never hold live aliases into the shared IR.
|
|
68
67
|
*
|
|
69
|
-
* `findToolCalls` projects each call down to `{ name, args }` only:
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* being judged right now, so they have not run, and a call can never be its own
|
|
74
|
-
* precedent (COVENANT-13b §4.1).
|
|
68
|
+
* `findToolCalls` projects each call down to `{ name, args }` only: a call element also
|
|
69
|
+
* carries `fileChange` evidence, and evidence is judgment input, not session history — the
|
|
70
|
+
* two vocabularies stay separate. `succeeded` stays absent because these calls are the ones
|
|
71
|
+
* being judged right now: they have not run, and a call can never be its own precedent.
|
|
75
72
|
*/
|
|
76
73
|
export declare function transcriptFromInput(input: CovenantInput): CanonicalTranscript;
|
package/dist/transcript.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `CanonicalTranscript` — the agent-neutral session-query seam
|
|
2
|
+
* `CanonicalTranscript` — the agent-neutral session-query seam.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* query vocabulary. Pure types and functions, zero I/O.
|
|
4
|
+
* This seam does not replace `CovenantInput`. The IR is the *data* a covenant judges;
|
|
5
|
+
* `CanonicalTranscript` is the *behavioral seam* that queries session data, and the IR is
|
|
6
|
+
* one source it can wrap. Concrete transcript formats stay in adapters; the core knows only
|
|
7
|
+
* the query vocabulary. Pure types and functions, zero I/O.
|
|
9
8
|
*/
|
|
10
9
|
/**
|
|
11
|
-
* The injection-absent default
|
|
10
|
+
* The injection-absent default: every query answers "nothing happened".
|
|
12
11
|
* A witness consumer naturally converges to fail-closed — no evidence, no skip — and
|
|
13
12
|
* so does a precedent consumer (no evidence, gate stays shut).
|
|
14
13
|
*/
|
|
@@ -18,7 +17,7 @@ export const noopTranscript = {
|
|
|
18
17
|
findToolCalls: () => [],
|
|
19
18
|
};
|
|
20
19
|
/**
|
|
21
|
-
* Wrap a {@link CovenantInput} as a {@link CanonicalTranscript}
|
|
20
|
+
* Wrap a {@link CovenantInput} as a {@link CanonicalTranscript}.
|
|
22
21
|
*
|
|
23
22
|
* Exposes `subagentSpawns` as invocations (filtered when a kind is given) and
|
|
24
23
|
* `userMessages` with `timestampMs` omitted — the bare IR cannot prove freshness,
|
|
@@ -26,12 +25,10 @@ export const noopTranscript = {
|
|
|
26
25
|
* Order preserved; the input is never mutated, and every query returns fresh
|
|
27
26
|
* objects so consumers never hold live aliases into the shared IR.
|
|
28
27
|
*
|
|
29
|
-
* `findToolCalls` projects each call down to `{ name, args }` only:
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* being judged right now, so they have not run, and a call can never be its own
|
|
34
|
-
* precedent (COVENANT-13b §4.1).
|
|
28
|
+
* `findToolCalls` projects each call down to `{ name, args }` only: a call element also
|
|
29
|
+
* carries `fileChange` evidence, and evidence is judgment input, not session history — the
|
|
30
|
+
* two vocabularies stay separate. `succeeded` stays absent because these calls are the ones
|
|
31
|
+
* being judged right now: they have not run, and a call can never be its own precedent.
|
|
35
32
|
*/
|
|
36
33
|
export function transcriptFromInput(input) {
|
|
37
34
|
return {
|
package/package.json
CHANGED