@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 +35 -0
- package/README.md +41 -1
- package/dist/confirmed-current-intent-rate-cli.d.ts +10 -0
- package/dist/confirmed-current-intent-rate-cli.d.ts.map +1 -0
- package/dist/confirmed-current-intent-rate-cli.js +59 -0
- package/dist/confirmed-current-intent-rate-cli.js.map +1 -0
- package/dist/confirmed-current-intent-rate.d.ts +23 -0
- package/dist/confirmed-current-intent-rate.d.ts.map +1 -0
- package/dist/confirmed-current-intent-rate.js +149 -0
- package/dist/confirmed-current-intent-rate.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +5 -5
- package/dist/schema.js +5 -5
- package/package.json +9 -2
- package/src/confirmed-current-intent-rate-cli.ts +55 -0
- package/src/confirmed-current-intent-rate.ts +192 -0
- package/src/index.ts +11 -1
- package/src/schema.ts +5 -5
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":";AACA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC;AAEtF,MAAM,KAAK,GAAG,qRAAqR,CAAC;AAEpS,MAAM,OAAO,uBAAwB,SAAQ,KAAK;CAAG;AAErD,+EAA+E;AAC/E,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,uBAAuB,CAAC,oBAAoB,IAAI,kBAAkB,CAAC,CAAC;IACzG,IAAI,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE;YAAE,MAAM,IAAI,uBAAuB,CAAC,oBAAoB,IAAI,iBAAiB,CAAC,CAAC;QAC/G,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,uBAAuB;YAAE,MAAM,KAAK,CAAC;QAC1D,MAAM,IAAI,uBAAuB,CAAC,oBAAoB,IAAI,yBAAyB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC/I,CAAC;AACH,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,IAAI,CAAC,IAAuB;IAC1C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;QACpE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnB,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,uBAAuB,CAAC,8CAA8C,CAAC,CAAC;IACzG,MAAM,MAAM,GAAG,gCAAgC,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAW,CAAC,CAAC,CAAC;IAC7F,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7C,OAAO,MAAM,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChF,CAAC;AAED,SAAS,GAAG;IACV,IAAI,CAAC;QACH,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,sBAAsB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC9F,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,kBAAkB,CAAC,SAAiB,EAAE,QAA4B;IAChF,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACzC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,KAAK,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,IAAI,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAAE,GAAG,EAAE,CAAC"}
|
|
@@ -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":"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"}
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
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
|
|
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
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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.
|
|
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
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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 {
|