@clossys/butler 0.1.4 → 0.1.6

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/CHANGELOG.md CHANGED
@@ -3,6 +3,40 @@
3
3
  All notable changes to this package are documented here. Format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
+ ## [0.1.6] - 2026-09-18
7
+
8
+ ### Added
9
+
10
+ - Stated the charter close condition in the README: independent consumer
11
+ evidence of `confirmed current intent rate`, computed by
12
+ `assessConfirmedCurrentIntentRate()`. An empty evaluated set is
13
+ indeterminate, never a perfect rate of 1. `checkConfirmationCompleteness`
14
+ and `checkCurrency` remain the gates they are; neither is this rate.
15
+ - Declared `foundry.assessment` against a new mapped `butler-rate-check`
16
+ bin with `invocation: "single-json-input"`. `butler-check` remains the
17
+ three-gate CLI and is not the assessment surface. Advisor remains the
18
+ only required first-day role.
19
+ - `butler-rate-check assessment.json`: prints the `confirmed current intent
20
+ rate` report and exits on the `0` / `1` / `2` ternary.
21
+
22
+ ### Notes
23
+
24
+ - This does not claim the position is closed. Qualification of `0.1.6` is
25
+ deferred under #833.
26
+
27
+ ## [0.1.5] - 2026-09-17
28
+
29
+ ### Fixed
30
+
31
+ - Removed a doc-comment citation in `src/schema.ts` that pointed at a
32
+ repository-root agent-policy file. `src/` is packed content and compiles
33
+ into `dist/`, so that comment reached an installed consumer while the file
34
+ it named did not: `files` carries `dist`, `src`, this changelog, the README
35
+ and the licence, and nothing from the repository root. The sentence now
36
+ states the hand-rolled-validation convention on its own, which is the part
37
+ a reader of the installed package can actually act on. No type, export,
38
+ signature, or runtime behaviour changed.
39
+
6
40
  ## [0.1.4] - 2026-09-16
7
41
 
8
42
  ### Fixed
@@ -16,6 +50,7 @@ All notable changes to this package are documented here. Format follows
16
50
  installs anonymously. A reader following the instruction as written
17
51
  would create a credential this package never asks for. (#924)
18
52
 
53
+
19
54
  ## [0.1.3] - 2026-09-02
20
55
 
21
56
  ### Fixed
package/README.md CHANGED
@@ -52,10 +52,48 @@ GitHub token or other credential.
52
52
  import { checkCurrency, evaluateStandingInstruction } from "@clossys/butler";
53
53
  ```
54
54
 
55
+ ## Confirmed current intent rate
56
+
57
+ Independent consumer evidence shows the position's owned metric meets its
58
+ setpoint over the declared review cadence. The owned metric is `confirmed
59
+ current intent rate`, computed by `assessConfirmedCurrentIntentRate()`. An
60
+ empty evaluated set is `indeterminate`, never a perfect rate of 1.
61
+ `checkConfirmationCompleteness` and `checkCurrency` remain the gates they
62
+ are; neither is this rate. This package does not measure consumer evidence
63
+ and does not close the loop. A green run of this package's tests is not a
64
+ close.
65
+
66
+ ```ts
67
+ import { assessConfirmedCurrentIntentRate } from "@clossys/butler";
68
+
69
+ const report = assessConfirmedCurrentIntentRate(input);
70
+ ```
71
+
72
+ ```bash
73
+ butler-rate-check assessment.json
74
+ ```
75
+
76
+ The command prints JSON and exits `0` for satisfied, `1` for violated, and
77
+ `2` for indeterminate, unreadable, or invalid input.
78
+
79
+ This package declares that command as its first-day assessment surface in
80
+ its own manifest:
81
+
82
+ ```json
83
+ "foundry": { "assessment": { "bin": "butler-rate-check", "invocation": "single-json-input" } }
84
+ ```
85
+
86
+ Onboarding discovers that declaration from the installed manifest and never
87
+ infers a surface. `butler-check` remains the three-gate CLI and is not the
88
+ assessment surface. Butler is not a required first-day role; Advisor
89
+ remains the only required first-day assessment.
90
+
55
91
  ## The three gates
56
92
 
57
93
  All three are reachable from one installed bin, `butler-check`, dispatched
58
- on the first argument matching a gate name exactly.
94
+ on the first argument matching a gate name exactly. The charter assessment
95
+ is a second mapped bin, `butler-rate-check`, and is not a fourth gate on
96
+ this dispatcher.
59
97
 
60
98
  ```bash
61
99
  butler-check confirmation-completeness ./intents.json ./confirmations.json --floor 0.8
@@ -140,6 +178,8 @@ Everything below is exported from the package root.
140
178
  | `checkConfirmationCompleteness` | Gate 1, over intents, confirmations, and a declared floor. |
141
179
  | `checkCurrency` | Gate 2, over instructions, usages, and the caller's denial-invalidation decision. |
142
180
  | `checkWithdrawalParity` | Gate 3, over measured preference paths. |
181
+ | `assessConfirmedCurrentIntentRate` | Charter close metric. Returns `ConfirmedCurrentIntentRateAssessment` from consumer-supplied independent observations. Not `checkConfirmationCompleteness` or `checkCurrency`. |
182
+ | `ConfirmedCurrentIntentRateAssessment`, `ConfirmedCurrentIntentRateFinding`, `ConfirmedCurrentIntentRateState` | Its result shape |
143
183
 
144
184
  ### Validators and guards
145
185
 
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ export declare class ButlerRateCliInputError extends Error {
3
+ }
4
+ /** Reads caller-owned assessment evidence without treating it as validated. */
5
+ export declare function readButlerAssessmentJson(path: string): unknown;
6
+ /** Testable CLI dispatcher. Invalid arguments throw; the executable maps them to exit 2. */
7
+ export declare function main(argv: readonly string[]): number;
8
+ /** Resolves an npm/POSIX bin symlink before deciding whether this module is the entrypoint. */
9
+ export declare function isDirectInvocation(moduleUrl: string, argvPath: string | undefined): boolean;
10
+ //# sourceMappingURL=confirmed-current-intent-rate-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirmed-current-intent-rate-cli.d.ts","sourceRoot":"","sources":["../src/confirmed-current-intent-rate-cli.ts"],"names":[],"mappings":";AAQA,qBAAa,uBAAwB,SAAQ,KAAK;CAAG;AAErD,+EAA+E;AAC/E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAU9D;AAED,4FAA4F;AAC5F,wBAAgB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CASpD;AAWD,+FAA+F;AAC/F,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAO3F"}
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { assessConfirmedCurrentIntentRate } from "./confirmed-current-intent-rate.js";
6
+ const USAGE = `Usage: butler-rate-check <assessment.json>\n\nAssess confirmed current intent rate from consumer-supplied independent observations.\nExit codes: 0 = satisfied, 1 = violated, 2 = indeterminate or unreadable.\nbutler-check remains the three-gate CLI and is not this assessment.`;
7
+ export class ButlerRateCliInputError extends Error {
8
+ }
9
+ /** Reads caller-owned assessment evidence without treating it as validated. */
10
+ export function readButlerAssessmentJson(path) {
11
+ const resolved = resolve(path);
12
+ if (!existsSync(resolved))
13
+ throw new ButlerRateCliInputError(`assessment file "${path}" does not exist`);
14
+ try {
15
+ if (!statSync(resolved).isFile())
16
+ throw new ButlerRateCliInputError(`assessment file "${path}" is not a file`);
17
+ return JSON.parse(readFileSync(resolved, "utf8"));
18
+ }
19
+ catch (cause) {
20
+ if (cause instanceof ButlerRateCliInputError)
21
+ throw cause;
22
+ throw new ButlerRateCliInputError(`assessment file "${path}" is unreadable JSON: ${cause instanceof Error ? cause.message : String(cause)}`);
23
+ }
24
+ }
25
+ /** Testable CLI dispatcher. Invalid arguments throw; the executable maps them to exit 2. */
26
+ export function main(argv) {
27
+ if (argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h")) {
28
+ console.log(USAGE);
29
+ return 0;
30
+ }
31
+ if (argv.length !== 1)
32
+ throw new ButlerRateCliInputError("exactly one assessment.json file is required");
33
+ const report = assessConfirmedCurrentIntentRate(readButlerAssessmentJson(argv[0]));
34
+ console.log(JSON.stringify(report, null, 2));
35
+ return report.state === "satisfied" ? 0 : report.state === "violated" ? 1 : 2;
36
+ }
37
+ function run() {
38
+ try {
39
+ process.exitCode = main(process.argv.slice(2));
40
+ }
41
+ catch (cause) {
42
+ console.error(`butler-rate-check: ${cause instanceof Error ? cause.message : String(cause)}`);
43
+ process.exitCode = 2;
44
+ }
45
+ }
46
+ /** Resolves an npm/POSIX bin symlink before deciding whether this module is the entrypoint. */
47
+ export function isDirectInvocation(moduleUrl, argvPath) {
48
+ if (argvPath === undefined)
49
+ return false;
50
+ try {
51
+ return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(resolve(argvPath));
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
57
+ if (isDirectInvocation(import.meta.url, process.argv[1]))
58
+ run();
59
+ //# sourceMappingURL=confirmed-current-intent-rate-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirmed-current-intent-rate-cli.js","sourceRoot":"","sources":["../src/confirmed-current-intent-rate-cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export type ConfirmedCurrentIntentRateState = "satisfied" | "violated" | "indeterminate";
2
+ export interface ConfirmedCurrentIntentRateFinding {
3
+ readonly rule: string;
4
+ readonly severity: "error";
5
+ readonly message: string;
6
+ readonly path?: string;
7
+ }
8
+ export interface ConfirmedCurrentIntentRateAssessment {
9
+ readonly metric: "confirmed current intent rate";
10
+ readonly state: ConfirmedCurrentIntentRateState;
11
+ readonly rate: number | null;
12
+ readonly evaluatedIntents: number;
13
+ readonly confirmedCurrentIntents: number;
14
+ readonly findings: readonly ConfirmedCurrentIntentRateFinding[];
15
+ readonly proposedPositions: readonly unknown[];
16
+ }
17
+ /**
18
+ * Computes `confirmed current intent rate`: evaluated acted requests and
19
+ * standing-instruction uses backed by current subject confirmation / all
20
+ * acted requests and standing-instruction uses evaluated.
21
+ */
22
+ export declare function assessConfirmedCurrentIntentRate(input: unknown): ConfirmedCurrentIntentRateAssessment;
23
+ //# sourceMappingURL=confirmed-current-intent-rate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirmed-current-intent-rate.d.ts","sourceRoot":"","sources":["../src/confirmed-current-intent-rate.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,+BAA+B,GAAG,WAAW,GAAG,UAAU,GAAG,eAAe,CAAC;AAEzF,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oCAAoC;IACnD,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,KAAK,EAAE,+BAA+B,CAAC;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,SAAS,iCAAiC,EAAE,CAAC;IAChE,QAAQ,CAAC,iBAAiB,EAAE,SAAS,OAAO,EAAE,CAAC;CAChD;AAwDD;;;;GAIG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,OAAO,GAAG,oCAAoC,CAmGrG"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Computes the Butler charter metric `confirmed current intent rate` from
3
+ * consumer-supplied independent observations.
4
+ *
5
+ * This module does not invent observations and does not relabel
6
+ * `checkConfirmationCompleteness` or `checkCurrency`. An empty evaluated
7
+ * set is indeterminate, never a perfect rate of 1. Withdrawal-parity is
8
+ * not this rate.
9
+ */
10
+ const METRIC = "confirmed current intent rate";
11
+ const RESERVED_OBSERVERS = new Set(["butler", "@clossys/butler"]);
12
+ const PROPOSED_POSITIONS = [];
13
+ const KINDS = new Set(["acted-request", "standing-instruction-use"]);
14
+ function record(value) {
15
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16
+ }
17
+ function text(value) {
18
+ return typeof value === "string" && value.trim().length > 0;
19
+ }
20
+ function timestamp(value) {
21
+ return text(value) && !Number.isNaN(Date.parse(value));
22
+ }
23
+ function finding(rule, message, path) {
24
+ return path === undefined ? { rule, severity: "error", message } : { rule, severity: "error", message, path };
25
+ }
26
+ function evidenceCounts(value) {
27
+ if (!Array.isArray(value) || value.length === 0)
28
+ return false;
29
+ return value.every((item) => record(item) && text(item.id) && text(item.description));
30
+ }
31
+ function reservedObserver(value) {
32
+ return RESERVED_OBSERVERS.has(value.trim().toLowerCase());
33
+ }
34
+ function report(state, evaluatedIntents, confirmedCurrentIntents, rate, findings) {
35
+ return {
36
+ metric: METRIC,
37
+ state,
38
+ rate,
39
+ evaluatedIntents,
40
+ confirmedCurrentIntents,
41
+ findings,
42
+ proposedPositions: PROPOSED_POSITIONS,
43
+ };
44
+ }
45
+ function isConfirmedCurrent(entry) {
46
+ return entry.confirmed && entry.current;
47
+ }
48
+ /**
49
+ * Computes `confirmed current intent rate`: evaluated acted requests and
50
+ * standing-instruction uses backed by current subject confirmation / all
51
+ * acted requests and standing-instruction uses evaluated.
52
+ */
53
+ export function assessConfirmedCurrentIntentRate(input) {
54
+ if (!record(input)) {
55
+ return report("indeterminate", 0, 0, null, [finding("assessment-shape", "Assessment input must be an object.", "$")]);
56
+ }
57
+ const findings = [];
58
+ if (!timestamp(input.asOf)) {
59
+ findings.push(finding("assessment-as-of", "asOf must be an interpretable timestamp.", "asOf"));
60
+ }
61
+ const declaredIntents = input.declaredIntents;
62
+ if (!Array.isArray(declaredIntents)) {
63
+ findings.push(finding("declared-intents-required", "declaredIntents must be an array of { id, kind } objects.", "declaredIntents"));
64
+ return report("indeterminate", 0, 0, null, findings);
65
+ }
66
+ const declaredIds = [];
67
+ const declaredSet = new Set();
68
+ declaredIntents.forEach((item, index) => {
69
+ const path = `declaredIntents[${index}]`;
70
+ if (!record(item) || !text(item.id)) {
71
+ findings.push(finding("intent-id-required", "id must be a non-empty string.", `${path}.id`));
72
+ return;
73
+ }
74
+ if (!text(item.kind) || !KINDS.has(item.kind)) {
75
+ findings.push(finding("intent-kind-required", 'kind must be "acted-request" or "standing-instruction-use".', `${path}.kind`));
76
+ return;
77
+ }
78
+ if (declaredSet.has(item.id)) {
79
+ findings.push(finding("duplicate-intent-id", `Duplicate id "${item.id}".`, `${path}.id`));
80
+ return;
81
+ }
82
+ declaredSet.add(item.id);
83
+ declaredIds.push(item.id);
84
+ });
85
+ if (declaredIds.length === 0) {
86
+ findings.push(finding("declared-intents-empty", "No declared intents are available for the confirmed-current-intent metric; the rate is indeterminate, never 1.", "declaredIntents"));
87
+ }
88
+ const observations = input.observations;
89
+ if (observations !== undefined && !Array.isArray(observations)) {
90
+ findings.push(finding("observations-shape", "observations must be an array.", "observations"));
91
+ }
92
+ const counting = new Map();
93
+ if (Array.isArray(observations)) {
94
+ observations.forEach((item, index) => {
95
+ const path = `observations[${index}]`;
96
+ if (!record(item)) {
97
+ findings.push(finding("observation-shape", "An observation must be an object.", path));
98
+ return;
99
+ }
100
+ if (text(item.observerRef) && reservedObserver(item.observerRef)) {
101
+ findings.push(finding("self-observation", "Butler cannot observe itself; reserved observerRef values are not independent.", `${path}.observerRef`));
102
+ return;
103
+ }
104
+ if (text(item.intentId) && !declaredSet.has(item.intentId)) {
105
+ findings.push(finding("unknown-intent", `Observation intentId "${item.intentId}" is not a declared intent.`, `${path}.intentId`));
106
+ return;
107
+ }
108
+ if (item.independent !== true)
109
+ return;
110
+ if (typeof item.confirmed !== "boolean" || typeof item.current !== "boolean")
111
+ return;
112
+ if (!text(item.observerRef) || !evidenceCounts(item.evidence) || !text(item.intentId) || !declaredSet.has(item.intentId))
113
+ return;
114
+ const bucket = counting.get(item.intentId) ?? [];
115
+ bucket.push({ confirmed: item.confirmed, current: item.current });
116
+ counting.set(item.intentId, bucket);
117
+ });
118
+ }
119
+ let evaluatedIntents = 0;
120
+ let confirmedCurrentIntents = 0;
121
+ for (const id of declaredIds) {
122
+ const observed = counting.get(id) ?? [];
123
+ if (observed.length === 0) {
124
+ findings.push(finding("intent-unevaluated", `Declared intent "${id}" has no counting independent observation.`, `declaredIntents.${id}`));
125
+ continue;
126
+ }
127
+ evaluatedIntents += 1;
128
+ const allConfirmedCurrent = observed.every((entry) => isConfirmedCurrent(entry));
129
+ if (allConfirmedCurrent)
130
+ confirmedCurrentIntents += 1;
131
+ else {
132
+ findings.push(finding("intent-not-confirmed-current", `Declared intent "${id}" was independently observed without current subject confirmation.`, `declaredIntents.${id}`));
133
+ }
134
+ if (observed.length > 1) {
135
+ const first = observed[0];
136
+ if (observed.some((entry) => entry.confirmed !== first.confirmed || entry.current !== first.current)) {
137
+ findings.push(finding("observation-disagreement", `Counting observations for "${id}" disagree.`, `observations.${id}`));
138
+ }
139
+ }
140
+ }
141
+ const rate = evaluatedIntents === 0 ? null : confirmedCurrentIntents / evaluatedIntents;
142
+ if (evaluatedIntents === 0)
143
+ return report("indeterminate", 0, 0, null, findings);
144
+ const coverageComplete = evaluatedIntents === declaredIds.length;
145
+ const noErrorFindings = findings.length === 0;
146
+ const state = coverageComplete && confirmedCurrentIntents === evaluatedIntents && noErrorFindings ? "satisfied" : "violated";
147
+ return report(state, evaluatedIntents, confirmedCurrentIntents, rate, findings);
148
+ }
149
+ //# sourceMappingURL=confirmed-current-intent-rate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirmed-current-intent-rate.js","sourceRoot":"","sources":["../src/confirmed-current-intent-rate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,MAAM,GAAG,+BAAwC,CAAC;AACxD,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAClE,MAAM,kBAAkB,GAAuB,EAAE,CAAC;AAClD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,eAAe,EAAE,0BAA0B,CAAC,CAAC,CAAC;AAuBrE,SAAS,MAAM,CAAC,KAAc;IAC5B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,IAAI,CAAC,KAAc;IAC1B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,OAAO,CAAC,IAAY,EAAE,OAAe,EAAE,IAAa;IAC3D,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAChH,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9D,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;AACxF,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa;IACrC,OAAO,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,MAAM,CACb,KAAsC,EACtC,gBAAwB,EACxB,uBAA+B,EAC/B,IAAmB,EACnB,QAAsD;IAEtD,OAAO;QACL,MAAM,EAAE,MAAM;QACd,KAAK;QACL,IAAI;QACJ,gBAAgB;QAChB,uBAAuB;QACvB,QAAQ;QACR,iBAAiB,EAAE,kBAAkB;KACtC,CAAC;AACJ,CAAC;AAOD,SAAS,kBAAkB,CAAC,KAA0B;IACpD,OAAO,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gCAAgC,CAAC,KAAc;IAC7D,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACnB,OAAO,MAAM,CAAC,eAAe,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,kBAAkB,EAAE,qCAAqC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACxH,CAAC;IAED,MAAM,QAAQ,GAAwC,EAAE,CAAC;IACzD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,0CAA0C,EAAE,MAAM,CAAC,CAAC,CAAC;IACjG,CAAC;IAED,MAAM,eAAe,GAAG,KAAK,CAAC,eAAe,CAAC;IAC9C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;QACpC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,2BAA2B,EAAE,2DAA2D,EAAE,iBAAiB,CAAC,CAAC,CAAC;QACpI,OAAO,MAAM,CAAC,eAAe,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IACtC,eAAe,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACtC,MAAM,IAAI,GAAG,mBAAmB,KAAK,GAAG,CAAC;QACzC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YACpC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,gCAAgC,EAAE,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC;YAC7F,OAAO;QACT,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9C,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,6DAA6D,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC;YAC9H,OAAO;QACT,CAAC;QACD,IAAI,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,iBAAiB,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC;YAC1F,OAAO;QACT,CAAC;QACD,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzB,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,wBAAwB,EAAE,gHAAgH,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACxL,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;IACxC,IAAI,YAAY,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QAC/D,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,gCAAgC,EAAE,cAAc,CAAC,CAAC,CAAC;IACjG,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAiC,CAAC;IAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QAChC,YAAY,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YACnC,MAAM,IAAI,GAAG,gBAAgB,KAAK,GAAG,CAAC;YACtC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,mCAAmC,EAAE,IAAI,CAAC,CAAC,CAAC;gBACvF,OAAO;YACT,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;gBACjE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,gFAAgF,EAAE,GAAG,IAAI,cAAc,CAAC,CAAC,CAAC;gBACpJ,OAAO;YACT,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC3D,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,yBAAyB,IAAI,CAAC,QAAQ,6BAA6B,EAAE,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC;gBAClI,OAAO;YACT,CAAC;YACD,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI;gBAAE,OAAO;YACtC,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,SAAS;gBAAE,OAAO;YACrF,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,OAAO;YACjI,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACjD,MAAM,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;YAClE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,gBAAgB,GAAG,CAAC,CAAC;IACzB,IAAI,uBAAuB,GAAG,CAAC,CAAC;IAChC,KAAK,MAAM,EAAE,IAAI,WAAW,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,oBAAoB,EAAE,4CAA4C,EAAE,mBAAmB,EAAE,EAAE,CAAC,CAAC,CAAC;YAC1I,SAAS;QACX,CAAC;QACD,gBAAgB,IAAI,CAAC,CAAC;QACtB,MAAM,mBAAmB,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;QACjF,IAAI,mBAAmB;YAAE,uBAAuB,IAAI,CAAC,CAAC;aACjD,CAAC;YACJ,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,8BAA8B,EAAE,oBAAoB,EAAE,oEAAoE,EAAE,mBAAmB,EAAE,EAAE,CAAC,CAAC,CAAC;QAC9K,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC;YAC3B,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;gBACrG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,0BAA0B,EAAE,8BAA8B,EAAE,aAAa,EAAE,gBAAgB,EAAE,EAAE,CAAC,CAAC,CAAC;YAC1H,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,gBAAgB,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,uBAAuB,GAAG,gBAAgB,CAAC;IACxF,IAAI,gBAAgB,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,eAAe,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IACjF,MAAM,gBAAgB,GAAG,gBAAgB,KAAK,WAAW,CAAC,MAAM,CAAC;IACjE,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC;IAC9C,MAAM,KAAK,GACT,gBAAgB,IAAI,uBAAuB,KAAK,gBAAgB,IAAI,eAAe,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC;IACjH,OAAO,MAAM,CAAC,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAClF,CAAC"}
package/dist/index.d.ts CHANGED
@@ -29,7 +29,11 @@
29
29
  * `checkCurrency`, and `checkWithdrawalParity`. Each is a pure
30
30
  * function returning a three-state result, and `cli.ts` folds those
31
31
  * onto the `0`/`1`/`2` exit contract without ever collapsing "could
32
- * not run" into either "clean" or "findings".
32
+ * not run" into either "clean" or "findings". The charter metric
33
+ * `confirmed current intent rate` is computed by
34
+ * `assessConfirmedCurrentIntentRate` from consumer-supplied
35
+ * independent observations and is not either gate. `butler-rate-check`
36
+ * is the assessment surface; `butler-check` remains the three-gate CLI.
33
37
  *
34
38
  * Two subpaths sit beside this one. `./inbound` is admission — whether an
35
39
  * event arriving on a channel should be acknowledged and processed at all,
@@ -45,6 +49,8 @@
45
49
  export { CONFIRMATION_VERDICTS, INTENT_DISPOSITIONS, STANDING_AUDIT_EVENT_TYPES, STANDING_PROVENANCES, isConfirmationRecord, isIntentRecord, isStandingInstruction, validateConfidenceFloor, validateConfirmationRecord, validateConfirmationRecords, validateInstructionUsages, validateIntentRecord, validateIntentRecords, validatePolicyVersion, validatePreferencePaths, validateStandingInstruction, validateStandingInstructions, } from "./schema.js";
46
50
  export type { ConfidenceFloor, ConfirmationRecord, ConfirmationVerdict, CurrencyWindow, InstructionUsage, IntentDisposition, IntentRecord, PathCost, PolicyVersion, PreferencePath, StandingAction, StandingAuditEvent, StandingAuditEventType, StandingAuditLedger, StandingEvaluation, StandingEvaluationPolicy, StandingInstruction, StandingInstructionStore, StandingProvenance, StandingState, StandingTopic, } from "./schema.js";
47
51
  export { checkConfirmationCompleteness, checkCurrency, checkWithdrawalParity, decideStandingChange, evaluateStandingInstruction, recordReopened, recordStaleness, } from "./contract.js";
52
+ export { assessConfirmedCurrentIntentRate } from "./confirmed-current-intent-rate.js";
53
+ export type { ConfirmedCurrentIntentRateAssessment, ConfirmedCurrentIntentRateFinding, ConfirmedCurrentIntentRateState, } from "./confirmed-current-intent-rate.js";
48
54
  export type { ConfirmationCompletenessResult, ConfirmationFailureReason, ConfirmationFinding, ConfirmationFindingKind, CurrencyFailureReason, CurrencyFinding, CurrencyFindingKind, CurrencyResult, WithdrawalParityFailureReason, WithdrawalParityFinding, WithdrawalParityFindingKind, WithdrawalParityResult, } from "./contract.js";
49
55
  export type { ValidationIssue, ValidationResult, Validator } from "./validation.js";
50
56
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,0BAA0B,EAC1B,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,yBAAyB,EACzB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,kBAAkB,EAClB,aAAa,EACb,aAAa,GACd,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,6BAA6B,EAC7B,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,2BAA2B,EAC3B,cAAc,EACd,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,8BAA8B,EAC9B,yBAAyB,EACzB,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,6BAA6B,EAC7B,uBAAuB,EACvB,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,0BAA0B,EAC1B,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,yBAAyB,EACzB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,kBAAkB,EAClB,aAAa,EACb,aAAa,GACd,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,6BAA6B,EAC7B,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,2BAA2B,EAC3B,cAAc,EACd,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC;AACtF,YAAY,EACV,oCAAoC,EACpC,iCAAiC,EACjC,+BAA+B,GAChC,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EACV,8BAA8B,EAC9B,yBAAyB,EACzB,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,6BAA6B,EAC7B,uBAAuB,EACvB,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -29,7 +29,11 @@
29
29
  * `checkCurrency`, and `checkWithdrawalParity`. Each is a pure
30
30
  * function returning a three-state result, and `cli.ts` folds those
31
31
  * onto the `0`/`1`/`2` exit contract without ever collapsing "could
32
- * not run" into either "clean" or "findings".
32
+ * not run" into either "clean" or "findings". The charter metric
33
+ * `confirmed current intent rate` is computed by
34
+ * `assessConfirmedCurrentIntentRate` from consumer-supplied
35
+ * independent observations and is not either gate. `butler-rate-check`
36
+ * is the assessment surface; `butler-check` remains the three-gate CLI.
33
37
  *
34
38
  * Two subpaths sit beside this one. `./inbound` is admission — whether an
35
39
  * event arriving on a channel should be acknowledged and processed at all,
@@ -44,4 +48,5 @@
44
48
  */
45
49
  export { CONFIRMATION_VERDICTS, INTENT_DISPOSITIONS, STANDING_AUDIT_EVENT_TYPES, STANDING_PROVENANCES, isConfirmationRecord, isIntentRecord, isStandingInstruction, validateConfidenceFloor, validateConfirmationRecord, validateConfirmationRecords, validateInstructionUsages, validateIntentRecord, validateIntentRecords, validatePolicyVersion, validatePreferencePaths, validateStandingInstruction, validateStandingInstructions, } from "./schema.js";
46
50
  export { checkConfirmationCompleteness, checkCurrency, checkWithdrawalParity, decideStandingChange, evaluateStandingInstruction, recordReopened, recordStaleness, } from "./contract.js";
51
+ export { assessConfirmedCurrentIntentRate } from "./confirmed-current-intent-rate.js";
47
52
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,0BAA0B,EAC1B,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,yBAAyB,EACzB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AAyBrB,OAAO,EACL,6BAA6B,EAC7B,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,2BAA2B,EAC3B,cAAc,EACd,eAAe,GAChB,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,0BAA0B,EAC1B,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,yBAAyB,EACzB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AAyBrB,OAAO,EACL,6BAA6B,EAC7B,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,2BAA2B,EAC3B,cAAc,EACd,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC"}
package/dist/schema.d.ts CHANGED
@@ -41,11 +41,11 @@
41
41
  * carries nothing else.
42
42
  *
43
43
  * Validation here is hand-rolled over `unknown`, with no schema library,
44
- * matching every other package in this workspace (see the root
45
- * `AGENTS.md`). These validators exist for the boundary where records
46
- * arrive as untyped JSON — a preference-centre route body, a file the CLI
47
- * reads, a value read back out of a host's own store — before anything
48
- * downstream is allowed to trust them.
44
+ * matching every other package in this workspace. These validators exist
45
+ * for the boundary where records arrive as untyped JSON — a
46
+ * preference-centre route body, a file the CLI reads, a value read back out
47
+ * of a host's own store — before anything downstream is allowed to trust
48
+ * them.
49
49
  */
50
50
  import { type ValidationResult } from "./validation.js";
51
51
  /**
package/dist/schema.js CHANGED
@@ -41,11 +41,11 @@
41
41
  * carries nothing else.
42
42
  *
43
43
  * Validation here is hand-rolled over `unknown`, with no schema library,
44
- * matching every other package in this workspace (see the root
45
- * `AGENTS.md`). These validators exist for the boundary where records
46
- * arrive as untyped JSON — a preference-centre route body, a file the CLI
47
- * reads, a value read back out of a host's own store — before anything
48
- * downstream is allowed to trust them.
44
+ * matching every other package in this workspace. These validators exist
45
+ * for the boundary where records arrive as untyped JSON — a
46
+ * preference-centre route body, a file the CLI reads, a value read back out
47
+ * of a host's own store — before anything downstream is allowed to trust
48
+ * them.
49
49
  */
50
50
  import { isOneOf, isPlainObject, pushIssue, optionalTimestamp, requireArrayOf, requireBoolean, requireNumber, requireString, requireTimestamp, } from "./validation.js";
51
51
  /** Every provenance value, for a caller validating untyped input. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clossys/butler",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,7 +22,8 @@
22
22
  }
23
23
  },
24
24
  "bin": {
25
- "butler-check": "dist/cli.js"
25
+ "butler-check": "dist/cli.js",
26
+ "butler-rate-check": "dist/confirmed-current-intent-rate-cli.js"
26
27
  },
27
28
  "files": [
28
29
  "dist",
@@ -33,6 +34,12 @@
33
34
  "LICENSE"
34
35
  ],
35
36
  "sideEffects": false,
37
+ "foundry": {
38
+ "assessment": {
39
+ "bin": "butler-rate-check",
40
+ "invocation": "single-json-input"
41
+ }
42
+ },
36
43
  "description": "The butler role: do we have what this person wants — this request in their own confirmation, and their standing instructions, still current? Dependency-free machinery for a consumer's own want records: a three-state standing model (absent/denied/granted) plus a stale evaluation status, structured intents carrying a confidence read against a declared floor, host-supplied storage and audit ports, an isolated ./inbound admission subpath, an optional ./web subpath whose React peer is asserted at import time, and three gates behind one butler-check bin — confirmation-completeness, which fails when an acted-on intent has no confirmation record, when it was acted on against a misread or unclear read-back, or when an intent below the declared confidence floor was acted on with no explicit hand-off; currency, which fails when a standing instruction is relied on past its declared window, after a policy version superseded it, or when nothing was on record to rely on at all; and withdrawal-parity, which fails when withdrawing takes more steps than granting or demands a contact or an account the matching grant path never required. Ships the schema and the checkers; every consumer authors its own values.",
37
44
  "keywords": [
38
45
  "butler",
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { assessConfirmedCurrentIntentRate } from "./confirmed-current-intent-rate.js";
6
+
7
+ const USAGE = `Usage: butler-rate-check <assessment.json>\n\nAssess confirmed current intent rate from consumer-supplied independent observations.\nExit codes: 0 = satisfied, 1 = violated, 2 = indeterminate or unreadable.\nbutler-check remains the three-gate CLI and is not this assessment.`;
8
+
9
+ export class ButlerRateCliInputError extends Error {}
10
+
11
+ /** Reads caller-owned assessment evidence without treating it as validated. */
12
+ export function readButlerAssessmentJson(path: string): unknown {
13
+ const resolved = resolve(path);
14
+ if (!existsSync(resolved)) throw new ButlerRateCliInputError(`assessment file "${path}" does not exist`);
15
+ try {
16
+ if (!statSync(resolved).isFile()) throw new ButlerRateCliInputError(`assessment file "${path}" is not a file`);
17
+ return JSON.parse(readFileSync(resolved, "utf8"));
18
+ } catch (cause) {
19
+ if (cause instanceof ButlerRateCliInputError) throw cause;
20
+ throw new ButlerRateCliInputError(`assessment file "${path}" is unreadable JSON: ${cause instanceof Error ? cause.message : String(cause)}`);
21
+ }
22
+ }
23
+
24
+ /** Testable CLI dispatcher. Invalid arguments throw; the executable maps them to exit 2. */
25
+ export function main(argv: readonly string[]): number {
26
+ if (argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h")) {
27
+ console.log(USAGE);
28
+ return 0;
29
+ }
30
+ if (argv.length !== 1) throw new ButlerRateCliInputError("exactly one assessment.json file is required");
31
+ const report = assessConfirmedCurrentIntentRate(readButlerAssessmentJson(argv[0] as string));
32
+ console.log(JSON.stringify(report, null, 2));
33
+ return report.state === "satisfied" ? 0 : report.state === "violated" ? 1 : 2;
34
+ }
35
+
36
+ function run(): void {
37
+ try {
38
+ process.exitCode = main(process.argv.slice(2));
39
+ } catch (cause) {
40
+ console.error(`butler-rate-check: ${cause instanceof Error ? cause.message : String(cause)}`);
41
+ process.exitCode = 2;
42
+ }
43
+ }
44
+
45
+ /** Resolves an npm/POSIX bin symlink before deciding whether this module is the entrypoint. */
46
+ export function isDirectInvocation(moduleUrl: string, argvPath: string | undefined): boolean {
47
+ if (argvPath === undefined) return false;
48
+ try {
49
+ return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(resolve(argvPath));
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ if (isDirectInvocation(import.meta.url, process.argv[1])) run();
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Computes the Butler charter metric `confirmed current intent rate` from
3
+ * consumer-supplied independent observations.
4
+ *
5
+ * This module does not invent observations and does not relabel
6
+ * `checkConfirmationCompleteness` or `checkCurrency`. An empty evaluated
7
+ * set is indeterminate, never a perfect rate of 1. Withdrawal-parity is
8
+ * not this rate.
9
+ */
10
+ const METRIC = "confirmed current intent rate" as const;
11
+ const RESERVED_OBSERVERS = new Set(["butler", "@clossys/butler"]);
12
+ const PROPOSED_POSITIONS: readonly unknown[] = [];
13
+ const KINDS = new Set(["acted-request", "standing-instruction-use"]);
14
+
15
+ export type ConfirmedCurrentIntentRateState = "satisfied" | "violated" | "indeterminate";
16
+
17
+ export interface ConfirmedCurrentIntentRateFinding {
18
+ readonly rule: string;
19
+ readonly severity: "error";
20
+ readonly message: string;
21
+ readonly path?: string;
22
+ }
23
+
24
+ export interface ConfirmedCurrentIntentRateAssessment {
25
+ readonly metric: "confirmed current intent rate";
26
+ readonly state: ConfirmedCurrentIntentRateState;
27
+ readonly rate: number | null;
28
+ readonly evaluatedIntents: number;
29
+ readonly confirmedCurrentIntents: number;
30
+ readonly findings: readonly ConfirmedCurrentIntentRateFinding[];
31
+ readonly proposedPositions: readonly unknown[];
32
+ }
33
+
34
+ type UnknownRecord = Record<string, unknown>;
35
+
36
+ function record(value: unknown): value is UnknownRecord {
37
+ return typeof value === "object" && value !== null && !Array.isArray(value);
38
+ }
39
+
40
+ function text(value: unknown): value is string {
41
+ return typeof value === "string" && value.trim().length > 0;
42
+ }
43
+
44
+ function timestamp(value: unknown): value is string {
45
+ return text(value) && !Number.isNaN(Date.parse(value));
46
+ }
47
+
48
+ function finding(rule: string, message: string, path?: string): ConfirmedCurrentIntentRateFinding {
49
+ return path === undefined ? { rule, severity: "error", message } : { rule, severity: "error", message, path };
50
+ }
51
+
52
+ function evidenceCounts(value: unknown): boolean {
53
+ if (!Array.isArray(value) || value.length === 0) return false;
54
+ return value.every((item) => record(item) && text(item.id) && text(item.description));
55
+ }
56
+
57
+ function reservedObserver(value: string): boolean {
58
+ return RESERVED_OBSERVERS.has(value.trim().toLowerCase());
59
+ }
60
+
61
+ function report(
62
+ state: ConfirmedCurrentIntentRateState,
63
+ evaluatedIntents: number,
64
+ confirmedCurrentIntents: number,
65
+ rate: number | null,
66
+ findings: readonly ConfirmedCurrentIntentRateFinding[],
67
+ ): ConfirmedCurrentIntentRateAssessment {
68
+ return {
69
+ metric: METRIC,
70
+ state,
71
+ rate,
72
+ evaluatedIntents,
73
+ confirmedCurrentIntents,
74
+ findings,
75
+ proposedPositions: PROPOSED_POSITIONS,
76
+ };
77
+ }
78
+
79
+ interface CountingObservation {
80
+ readonly confirmed: boolean;
81
+ readonly current: boolean;
82
+ }
83
+
84
+ function isConfirmedCurrent(entry: CountingObservation): boolean {
85
+ return entry.confirmed && entry.current;
86
+ }
87
+
88
+ /**
89
+ * Computes `confirmed current intent rate`: evaluated acted requests and
90
+ * standing-instruction uses backed by current subject confirmation / all
91
+ * acted requests and standing-instruction uses evaluated.
92
+ */
93
+ export function assessConfirmedCurrentIntentRate(input: unknown): ConfirmedCurrentIntentRateAssessment {
94
+ if (!record(input)) {
95
+ return report("indeterminate", 0, 0, null, [finding("assessment-shape", "Assessment input must be an object.", "$")]);
96
+ }
97
+
98
+ const findings: ConfirmedCurrentIntentRateFinding[] = [];
99
+ if (!timestamp(input.asOf)) {
100
+ findings.push(finding("assessment-as-of", "asOf must be an interpretable timestamp.", "asOf"));
101
+ }
102
+
103
+ const declaredIntents = input.declaredIntents;
104
+ if (!Array.isArray(declaredIntents)) {
105
+ findings.push(finding("declared-intents-required", "declaredIntents must be an array of { id, kind } objects.", "declaredIntents"));
106
+ return report("indeterminate", 0, 0, null, findings);
107
+ }
108
+
109
+ const declaredIds: string[] = [];
110
+ const declaredSet = new Set<string>();
111
+ declaredIntents.forEach((item, index) => {
112
+ const path = `declaredIntents[${index}]`;
113
+ if (!record(item) || !text(item.id)) {
114
+ findings.push(finding("intent-id-required", "id must be a non-empty string.", `${path}.id`));
115
+ return;
116
+ }
117
+ if (!text(item.kind) || !KINDS.has(item.kind)) {
118
+ findings.push(finding("intent-kind-required", 'kind must be "acted-request" or "standing-instruction-use".', `${path}.kind`));
119
+ return;
120
+ }
121
+ if (declaredSet.has(item.id)) {
122
+ findings.push(finding("duplicate-intent-id", `Duplicate id "${item.id}".`, `${path}.id`));
123
+ return;
124
+ }
125
+ declaredSet.add(item.id);
126
+ declaredIds.push(item.id);
127
+ });
128
+
129
+ if (declaredIds.length === 0) {
130
+ findings.push(finding("declared-intents-empty", "No declared intents are available for the confirmed-current-intent metric; the rate is indeterminate, never 1.", "declaredIntents"));
131
+ }
132
+
133
+ const observations = input.observations;
134
+ if (observations !== undefined && !Array.isArray(observations)) {
135
+ findings.push(finding("observations-shape", "observations must be an array.", "observations"));
136
+ }
137
+
138
+ const counting = new Map<string, CountingObservation[]>();
139
+ if (Array.isArray(observations)) {
140
+ observations.forEach((item, index) => {
141
+ const path = `observations[${index}]`;
142
+ if (!record(item)) {
143
+ findings.push(finding("observation-shape", "An observation must be an object.", path));
144
+ return;
145
+ }
146
+ if (text(item.observerRef) && reservedObserver(item.observerRef)) {
147
+ findings.push(finding("self-observation", "Butler cannot observe itself; reserved observerRef values are not independent.", `${path}.observerRef`));
148
+ return;
149
+ }
150
+ if (text(item.intentId) && !declaredSet.has(item.intentId)) {
151
+ findings.push(finding("unknown-intent", `Observation intentId "${item.intentId}" is not a declared intent.`, `${path}.intentId`));
152
+ return;
153
+ }
154
+ if (item.independent !== true) return;
155
+ if (typeof item.confirmed !== "boolean" || typeof item.current !== "boolean") return;
156
+ if (!text(item.observerRef) || !evidenceCounts(item.evidence) || !text(item.intentId) || !declaredSet.has(item.intentId)) return;
157
+ const bucket = counting.get(item.intentId) ?? [];
158
+ bucket.push({ confirmed: item.confirmed, current: item.current });
159
+ counting.set(item.intentId, bucket);
160
+ });
161
+ }
162
+
163
+ let evaluatedIntents = 0;
164
+ let confirmedCurrentIntents = 0;
165
+ for (const id of declaredIds) {
166
+ const observed = counting.get(id) ?? [];
167
+ if (observed.length === 0) {
168
+ findings.push(finding("intent-unevaluated", `Declared intent "${id}" has no counting independent observation.`, `declaredIntents.${id}`));
169
+ continue;
170
+ }
171
+ evaluatedIntents += 1;
172
+ const allConfirmedCurrent = observed.every((entry) => isConfirmedCurrent(entry));
173
+ if (allConfirmedCurrent) confirmedCurrentIntents += 1;
174
+ else {
175
+ findings.push(finding("intent-not-confirmed-current", `Declared intent "${id}" was independently observed without current subject confirmation.`, `declaredIntents.${id}`));
176
+ }
177
+ if (observed.length > 1) {
178
+ const first = observed[0]!;
179
+ if (observed.some((entry) => entry.confirmed !== first.confirmed || entry.current !== first.current)) {
180
+ findings.push(finding("observation-disagreement", `Counting observations for "${id}" disagree.`, `observations.${id}`));
181
+ }
182
+ }
183
+ }
184
+
185
+ const rate = evaluatedIntents === 0 ? null : confirmedCurrentIntents / evaluatedIntents;
186
+ if (evaluatedIntents === 0) return report("indeterminate", 0, 0, null, findings);
187
+ const coverageComplete = evaluatedIntents === declaredIds.length;
188
+ const noErrorFindings = findings.length === 0;
189
+ const state: ConfirmedCurrentIntentRateState =
190
+ coverageComplete && confirmedCurrentIntents === evaluatedIntents && noErrorFindings ? "satisfied" : "violated";
191
+ return report(state, evaluatedIntents, confirmedCurrentIntents, rate, findings);
192
+ }
package/src/index.ts CHANGED
@@ -29,7 +29,11 @@
29
29
  * `checkCurrency`, and `checkWithdrawalParity`. Each is a pure
30
30
  * function returning a three-state result, and `cli.ts` folds those
31
31
  * onto the `0`/`1`/`2` exit contract without ever collapsing "could
32
- * not run" into either "clean" or "findings".
32
+ * not run" into either "clean" or "findings". The charter metric
33
+ * `confirmed current intent rate` is computed by
34
+ * `assessConfirmedCurrentIntentRate` from consumer-supplied
35
+ * independent observations and is not either gate. `butler-rate-check`
36
+ * is the assessment surface; `butler-check` remains the three-gate CLI.
33
37
  *
34
38
  * Two subpaths sit beside this one. `./inbound` is admission — whether an
35
39
  * event arriving on a channel should be acknowledged and processed at all,
@@ -95,6 +99,12 @@ export {
95
99
  recordReopened,
96
100
  recordStaleness,
97
101
  } from "./contract.js";
102
+ export { assessConfirmedCurrentIntentRate } from "./confirmed-current-intent-rate.js";
103
+ export type {
104
+ ConfirmedCurrentIntentRateAssessment,
105
+ ConfirmedCurrentIntentRateFinding,
106
+ ConfirmedCurrentIntentRateState,
107
+ } from "./confirmed-current-intent-rate.js";
98
108
  export type {
99
109
  ConfirmationCompletenessResult,
100
110
  ConfirmationFailureReason,
package/src/schema.ts CHANGED
@@ -41,11 +41,11 @@
41
41
  * carries nothing else.
42
42
  *
43
43
  * Validation here is hand-rolled over `unknown`, with no schema library,
44
- * matching every other package in this workspace (see the root
45
- * `AGENTS.md`). These validators exist for the boundary where records
46
- * arrive as untyped JSON — a preference-centre route body, a file the CLI
47
- * reads, a value read back out of a host's own store — before anything
48
- * downstream is allowed to trust them.
44
+ * matching every other package in this workspace. These validators exist
45
+ * for the boundary where records arrive as untyped JSON — a
46
+ * preference-centre route body, a file the CLI reads, a value read back out
47
+ * of a host's own store — before anything downstream is allowed to trust
48
+ * them.
49
49
  */
50
50
 
51
51
  import {