@clossys/butler 0.1.1
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 +85 -0
- package/LICENSE +21 -0
- package/README.md +260 -0
- package/dist/audit-shape.check.d.ts +32 -0
- package/dist/audit-shape.check.d.ts.map +1 -0
- package/dist/audit-shape.check.js +7 -0
- package/dist/audit-shape.check.js.map +1 -0
- package/dist/cli.d.ts +54 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +426 -0
- package/dist/cli.js.map +1 -0
- package/dist/contract.d.ts +256 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +377 -0
- package/dist/contract.js.map +1 -0
- package/dist/inbound/index.d.ts +120 -0
- package/dist/inbound/index.d.ts.map +1 -0
- package/dist/inbound/index.js +125 -0
- package/dist/inbound/index.js.map +1 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/schema.d.ts +374 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +304 -0
- package/dist/schema.js.map +1 -0
- package/dist/validation.d.ts +74 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +140 -0
- package/dist/validation.js.map +1 -0
- package/dist/web/index.d.ts +5 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +25 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/internal/peer-version.d.ts +53 -0
- package/dist/web/internal/peer-version.d.ts.map +1 -0
- package/dist/web/internal/peer-version.js +136 -0
- package/dist/web/internal/peer-version.js.map +1 -0
- package/dist/web/useStandingWants.d.ts +75 -0
- package/dist/web/useStandingWants.d.ts.map +1 -0
- package/dist/web/useStandingWants.js +66 -0
- package/dist/web/useStandingWants.js.map +1 -0
- package/package.json +93 -0
- package/src/audit-shape.check.ts +37 -0
- package/src/cli.ts +445 -0
- package/src/contract.ts +534 -0
- package/src/inbound/index.ts +190 -0
- package/src/index.ts +113 -0
- package/src/schema.ts +622 -0
- package/src/validation.ts +172 -0
- package/src/web/index.ts +27 -0
- package/src/web/internal/peer-version.ts +159 -0
- package/src/web/useStandingWants.ts +139 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this package are documented here. Format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
|
|
6
|
+
## [0.1.1] - 2026-08-30
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- Updated the package's public repository, issue-tracker, and homepage metadata to the canonical Foundry repository. This change is not a publication or qualification claim.
|
|
11
|
+
|
|
12
|
+
## [0.1.0] - 2026-08-21
|
|
13
|
+
|
|
14
|
+
First release. This package is the butler role: everything about what a
|
|
15
|
+
person wants, now and standing.
|
|
16
|
+
|
|
17
|
+
This changelog starts here rather than carrying either donor's history,
|
|
18
|
+
which cites decisions and issues that would mean nothing — or the wrong
|
|
19
|
+
thing — to a reader who arrives at this package first.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- A want schema in three states, not two: `absent`, `denied`, and
|
|
24
|
+
`granted`, plus a `stale` evaluation status that is computed and never
|
|
25
|
+
stored. Absence is a value, so it can never be read as permission.
|
|
26
|
+
- `evaluateStandingInstruction`, which compares one stored answer against
|
|
27
|
+
the policy version in force **and** the clock. A row that exists, says
|
|
28
|
+
`granted`, and is a year past its own declared window comes back `stale`.
|
|
29
|
+
- `decideStandingChange` plus `recordReopened` and `recordStaleness`: the
|
|
30
|
+
pure decision core and the audit-event builders. Actor and subject are
|
|
31
|
+
separate parameters and separate fields, always, and
|
|
32
|
+
`src/audit-shape.check.ts` fails the build if the audit event ever gains
|
|
33
|
+
a personal-data-shaped key or loses that separation.
|
|
34
|
+
- Three gates, all reachable from the single `butler-check` bin:
|
|
35
|
+
`confirmation-completeness`, `currency`, and `withdrawal-parity`. Each
|
|
36
|
+
dispatches on `argv[0]` matching exactly — never on
|
|
37
|
+
`basename(process.argv[1])`, which would see `cli.js` and silently run
|
|
38
|
+
the wrong command wherever a gate is invoked by compiled path.
|
|
39
|
+
- The `0` / `1` / `2` exit contract, with `2` reachable on every gate by
|
|
40
|
+
more than one route — an unreadable or invalid record store, an empty
|
|
41
|
+
record set, a required declared value that was not supplied, and no gate
|
|
42
|
+
selected at all — and each route tested. A bare `butler-check` exits `2`:
|
|
43
|
+
nothing was selected, so nothing was checked. Only an explicitly
|
|
44
|
+
requested `--help` exits `0`.
|
|
45
|
+
- An `./inbound` subpath: admission on any channel as a pure function of
|
|
46
|
+
the caller's own signature verification and a host ledger's dedupe
|
|
47
|
+
answer. An unreachable ledger rejects rather than acknowledging, because
|
|
48
|
+
acking an event whose dedupe never ran would silently discard it.
|
|
49
|
+
- A `./web` subpath: `useStandingWants`, a currency-aware preference-surface
|
|
50
|
+
hook whose `withdraw` shares `grant`'s and `deny`'s exact call shape.
|
|
51
|
+
React is an optional peer, asserted at import time by `assertPeerVersion`
|
|
52
|
+
so an absent or incompatible version fails loudly and by name.
|
|
53
|
+
- **The published tarball carries this changelog.** `files` includes
|
|
54
|
+
`CHANGELOG.md`: a consumer reading the installed package should not have
|
|
55
|
+
to leave it to find out what changed.
|
|
56
|
+
|
|
57
|
+
### Design notes
|
|
58
|
+
|
|
59
|
+
- **Confidence is a first-class value with a declared floor.** An intent
|
|
60
|
+
carries its confidence on the record, and the floor is supplied by the
|
|
61
|
+
caller with no default anywhere in this package. A reading below the
|
|
62
|
+
floor may be confirmed or handed off; what it may never be is acted on
|
|
63
|
+
silently, and `handed-off` is a first-class disposition precisely so
|
|
64
|
+
declining to act is representable as a decision rather than as an
|
|
65
|
+
absence of one.
|
|
66
|
+
- **An inferred standing instruction is not binding until confirmed.** It
|
|
67
|
+
evaluates to `absent`, with its own reason, so relying on it is a
|
|
68
|
+
finding rather than a pass.
|
|
69
|
+
- **The currency window has no default.** A window this package invented
|
|
70
|
+
would be this package authoring one of the consumer's own values, and a
|
|
71
|
+
missing window silently read as "forever" is the open loop the currency
|
|
72
|
+
gate exists to close.
|
|
73
|
+
- **`invalidateDenialOnPolicyBump` has no default in either direction.**
|
|
74
|
+
Whether a policy bump invalidates a prior refusal is a jurisdiction
|
|
75
|
+
judgment, and this package answers no jurisdiction questions.
|
|
76
|
+
|
|
77
|
+
### Not included
|
|
78
|
+
|
|
79
|
+
- No topic vocabulary, no jurisdiction logic, no obligations, and no
|
|
80
|
+
values of any kind. Storage and audit are host-supplied ports and no
|
|
81
|
+
implementation of either ships here. No claim of legal compliance is
|
|
82
|
+
made anywhere.
|
|
83
|
+
- No person-attributable record is written into this repository. `subjectId`
|
|
84
|
+
and `actorId` are opaque host-owned references carrying no email, name,
|
|
85
|
+
phone number, address, or IP.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Calvin Hung
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# @clossys/butler
|
|
2
|
+
|
|
3
|
+
**Everything about what a person wants — now, and standing.**
|
|
4
|
+
|
|
5
|
+
The question this role answers, and no other role does:
|
|
6
|
+
|
|
7
|
+
> Do we have what this person wants — this request in their own
|
|
8
|
+
> confirmation, and their standing instructions, still current?
|
|
9
|
+
|
|
10
|
+
Ships the schema and the checkers; every consumer authors its own values.
|
|
11
|
+
|
|
12
|
+
## The loop this closes
|
|
13
|
+
|
|
14
|
+
A stated want is the setpoint. Acting on it is the act. Reading the
|
|
15
|
+
interpretation back, and asking whether the standing answer is still
|
|
16
|
+
current, is the observation. Unconfirmed intents and expired instructions
|
|
17
|
+
are the comparison. Re-asking is the correction.
|
|
18
|
+
|
|
19
|
+
A preference written to a row and never re-checked has no observation and
|
|
20
|
+
no comparison. It is an open loop, and closing it is the reason this
|
|
21
|
+
package exists.
|
|
22
|
+
|
|
23
|
+
## The defect this exists to prevent
|
|
24
|
+
|
|
25
|
+
A weaker tool checks that a consent row **exists**. It passes on a row
|
|
26
|
+
three policy versions old, and on a row a year past the window its own
|
|
27
|
+
author declared, because a boolean read of "granted" cannot see age.
|
|
28
|
+
Presence is not currency.
|
|
29
|
+
|
|
30
|
+
Two rules follow, and both are structural here rather than advisory:
|
|
31
|
+
|
|
32
|
+
1. **Consent is three states, never two.** `absent` (never asked) is a
|
|
33
|
+
distinct value from `denied` (asked, refused), and neither is a boolean.
|
|
34
|
+
Absence can therefore never be read as permission — including when the
|
|
35
|
+
record store is unreachable, when the subject was never asked, and when
|
|
36
|
+
the only thing on file is an inference nobody confirmed.
|
|
37
|
+
2. **A gate that cannot run must say so.** `butler-check` exits `2` when
|
|
38
|
+
the record store cannot be read or there was nothing to scan, and `2` is
|
|
39
|
+
never collapsed into `0` or `1`.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install @clossys/butler
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Installing from this registry needs a GitHub personal access token with
|
|
48
|
+
`read:packages` — see the repository root README.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { checkCurrency, evaluateStandingInstruction } from "@clossys/butler";
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## The three gates
|
|
55
|
+
|
|
56
|
+
All three are reachable from one installed bin, `butler-check`, dispatched
|
|
57
|
+
on the first argument matching a gate name exactly.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
butler-check confirmation-completeness ./intents.json ./confirmations.json --floor 0.8
|
|
61
|
+
butler-check currency ./instructions.json ./usages.json --invalidate-denial-on-policy-bump false
|
|
62
|
+
butler-check withdrawal-parity ./paths.json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### `confirmation-completeness`
|
|
66
|
+
|
|
67
|
+
Every acted-on intent has the subject's own confirmation record, or an
|
|
68
|
+
explicit below-floor hand-off. It fails on an intent dispositioned `acted`
|
|
69
|
+
with no confirmation at all, on one acted against a `misread` or `unclear`
|
|
70
|
+
read-back, on a reading below the declared floor acted on with neither a
|
|
71
|
+
hand-off nor a confirmation, and on a read-back answering an intent outside
|
|
72
|
+
the set being checked.
|
|
73
|
+
|
|
74
|
+
`--floor` is required and has no default. The number below which a reading
|
|
75
|
+
is too weak to act on is one of the consumer's own values.
|
|
76
|
+
|
|
77
|
+
### `currency`
|
|
78
|
+
|
|
79
|
+
No standing instruction is used past its declared window. It reads
|
|
80
|
+
**usages**, not instructions alone: a set of instructions nobody relies on
|
|
81
|
+
proves nothing, while a usage record is the loop actually being closed or
|
|
82
|
+
not. Each usage carries the policy version in force at the moment it
|
|
83
|
+
happened, so the gate replays a real decision rather than re-deriving one
|
|
84
|
+
against today's policy. It fails on a usage past the window, on a usage
|
|
85
|
+
after the answered policy version was superseded, on a usage of an
|
|
86
|
+
instruction with no answer on record — including an unconfirmed inference —
|
|
87
|
+
and on a usage naming an instruction outside the set being checked.
|
|
88
|
+
|
|
89
|
+
`--invalidate-denial-on-policy-bump` is required and has no default in
|
|
90
|
+
either direction. Whether a policy bump also invalidates a prior refusal is
|
|
91
|
+
a jurisdiction judgment this package does not make.
|
|
92
|
+
|
|
93
|
+
### `withdrawal-parity`
|
|
94
|
+
|
|
95
|
+
Withdrawing is no harder than granting. It compares the grant route and the
|
|
96
|
+
withdraw route a consumer measured itself, using three coarse countable
|
|
97
|
+
facts rather than a score, and fails when withdrawing takes more steps,
|
|
98
|
+
demands contacting a human that granting did not, demands an account that
|
|
99
|
+
granting did not, or is not offered at all.
|
|
100
|
+
|
|
101
|
+
Reopening is not a degraded path, and this gate is only the measurement
|
|
102
|
+
half of that. The API half is structural and lives in `./web`.
|
|
103
|
+
|
|
104
|
+
### Exit codes
|
|
105
|
+
|
|
106
|
+
| Code | Meaning |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| `0` | Ran against a non-empty record set and found nothing. |
|
|
109
|
+
| `1` | Ran and found at least one real violation. |
|
|
110
|
+
| `2` | Could not run: a missing, unreadable, unparseable or schema-invalid record store; an empty record set; a required declared value that was not supplied; or no gate selected at all. |
|
|
111
|
+
|
|
112
|
+
`2` is not a variant of failure. "I checked and it is fine" and "I never
|
|
113
|
+
checked" are different answers, and a gate that reports the second as the
|
|
114
|
+
first is worse than no gate.
|
|
115
|
+
|
|
116
|
+
A bare `butler-check` with no subcommand exits `2`, not `0`. Nothing was
|
|
117
|
+
selected, so nothing was checked, and a CI step with a dropped argument
|
|
118
|
+
must go red rather than green on the strength of having examined nothing.
|
|
119
|
+
An explicitly requested `--help` is the one exception, and exits `0`,
|
|
120
|
+
because asking for help is a run that did exactly what was asked.
|
|
121
|
+
|
|
122
|
+
## API
|
|
123
|
+
|
|
124
|
+
Everything below is exported from the package root.
|
|
125
|
+
|
|
126
|
+
### Evaluating and deciding
|
|
127
|
+
|
|
128
|
+
| Export | What it does |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| `evaluateStandingInstruction` | Compares one stored answer against the policy version in force and the clock. Returns `granted`, `denied`, `absent`, or `stale`. |
|
|
131
|
+
| `decideStandingChange` | The pure decision core for one change: returns the new instruction and its audit event. Takes the actor and the subject as separate arguments. |
|
|
132
|
+
| `recordReopened` | Builds the audit event for a subject reopening their preference surface, independent of any decision made inside it. |
|
|
133
|
+
| `recordStaleness` | Builds the audit event for an answer found stale, with the reason as the event type. |
|
|
134
|
+
|
|
135
|
+
### The gates, as pure functions
|
|
136
|
+
|
|
137
|
+
| Export | What it does |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| `checkConfirmationCompleteness` | Gate 1, over intents, confirmations, and a declared floor. |
|
|
140
|
+
| `checkCurrency` | Gate 2, over instructions, usages, and the caller's denial-invalidation decision. |
|
|
141
|
+
| `checkWithdrawalParity` | Gate 3, over measured preference paths. |
|
|
142
|
+
|
|
143
|
+
### Validators and guards
|
|
144
|
+
|
|
145
|
+
| Export | What it does |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| `validateStandingInstruction` | Validates one untyped standing instruction. |
|
|
148
|
+
| `validateStandingInstructions` | Validates an untyped array of them. |
|
|
149
|
+
| `validateIntentRecord` | Validates one untyped intent. |
|
|
150
|
+
| `validateIntentRecords` | Validates an untyped array of them. |
|
|
151
|
+
| `validateConfirmationRecord` | Validates one untyped read-back answer. |
|
|
152
|
+
| `validateConfirmationRecords` | Validates an untyped array of them. |
|
|
153
|
+
| `validateInstructionUsages` | Validates an untyped array of usage records. |
|
|
154
|
+
| `validatePreferencePaths` | Validates an untyped array of measured preference paths. |
|
|
155
|
+
| `validateConfidenceFloor` | Validates a declared confidence floor. |
|
|
156
|
+
| `validatePolicyVersion` | Validates a policy-version reference. |
|
|
157
|
+
| `isStandingInstruction` | Boolean guard over `validateStandingInstruction`. |
|
|
158
|
+
| `isIntentRecord` | Boolean guard over `validateIntentRecord`. |
|
|
159
|
+
| `isConfirmationRecord` | Boolean guard over `validateConfirmationRecord`. |
|
|
160
|
+
|
|
161
|
+
### Vocabularies
|
|
162
|
+
|
|
163
|
+
`STANDING_PROVENANCES`, `INTENT_DISPOSITIONS`, `CONFIRMATION_VERDICTS`, and
|
|
164
|
+
`STANDING_AUDIT_EVENT_TYPES` are the closed lists a caller validating
|
|
165
|
+
untyped input needs.
|
|
166
|
+
|
|
167
|
+
### Types
|
|
168
|
+
|
|
169
|
+
The record types are `StandingInstruction`, `StandingState`,
|
|
170
|
+
`StandingTopic`, `StandingProvenance`, `CurrencyWindow`, `PolicyVersion`,
|
|
171
|
+
`IntentRecord`, `IntentDisposition`, `ConfirmationRecord`,
|
|
172
|
+
`ConfirmationVerdict`, `ConfidenceFloor`, `InstructionUsage`,
|
|
173
|
+
`PreferencePath` and `PathCost`.
|
|
174
|
+
|
|
175
|
+
The evaluation and decision types are `StandingEvaluation`,
|
|
176
|
+
`StandingEvaluationPolicy`, `StandingAction`, `StandingAuditEvent` and
|
|
177
|
+
`StandingAuditEventType`.
|
|
178
|
+
|
|
179
|
+
The host-implemented ports are `StandingInstructionStore` and
|
|
180
|
+
`StandingAuditLedger`. No implementation of either ships here.
|
|
181
|
+
|
|
182
|
+
Each gate returns its own result type — `ConfirmationCompletenessResult`,
|
|
183
|
+
`CurrencyResult`, `WithdrawalParityResult` — carrying findings typed as
|
|
184
|
+
`ConfirmationFinding`, `CurrencyFinding` and `WithdrawalParityFinding`,
|
|
185
|
+
whose kinds are `ConfirmationFindingKind`, `CurrencyFindingKind` and
|
|
186
|
+
`WithdrawalParityFindingKind`, and whose non-clean outcomes are named by
|
|
187
|
+
`ConfirmationFailureReason`, `CurrencyFailureReason` and
|
|
188
|
+
`WithdrawalParityFailureReason`.
|
|
189
|
+
|
|
190
|
+
Validation surfaces `ValidationIssue`, `ValidationResult` and `Validator`.
|
|
191
|
+
|
|
192
|
+
## The `./inbound` subpath
|
|
193
|
+
|
|
194
|
+
Admission on any channel, and deliberately not an HTTP handler. The
|
|
195
|
+
consumer owns the route, the raw body, and signature verification —
|
|
196
|
+
signature schemes are provider-specific and this package must not pretend
|
|
197
|
+
to verify what it cannot exercise. This package owns the admission
|
|
198
|
+
decision: dedupe, ack/reject doctrine, replay tolerance, and ordering
|
|
199
|
+
tolerance, as a pure function of the caller's own verification result plus
|
|
200
|
+
a host ledger's dedupe answer.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { admitInboundEvent } from "@clossys/butler/inbound";
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The decision is never a bare boolean: acknowledging and processing are two
|
|
207
|
+
separate questions. A replay is an acknowledgement with `action: "ignore"`,
|
|
208
|
+
never an error. A rejection is reserved for a failed signature, so a
|
|
209
|
+
provider is never told to keep retrying data that will never become
|
|
210
|
+
processable. And a throwing ledger rejects the promise rather than
|
|
211
|
+
acknowledging — if durable dedupe could not run, nothing was durably
|
|
212
|
+
accepted, and saying otherwise would silently discard an event.
|
|
213
|
+
|
|
214
|
+
## The `./web` subpath
|
|
215
|
+
|
|
216
|
+
`useStandingWants` is a currency-aware preference-surface hook: it reads
|
|
217
|
+
every stored instruction for a subject once, then evaluates each requested
|
|
218
|
+
topic against the policy version in force and a caller-supplied `now`. An
|
|
219
|
+
instruction that exists, and was granted, and is past its own window comes
|
|
220
|
+
back `stale`, so a surface rendered from it re-asks rather than quietly
|
|
221
|
+
continuing.
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
import { useStandingWants } from "@clossys/butler/web";
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`withdraw` shares `grant`'s and `deny`'s exact call shape — one topic, one
|
|
228
|
+
promise, one function on the same object. That is withdrawal parity
|
|
229
|
+
enforced structurally at the API surface rather than asserted in prose.
|
|
230
|
+
|
|
231
|
+
React and React DOM are optional peers of this subpath specifically.
|
|
232
|
+
Importing the package root or `./inbound` never pulls in React. Importing
|
|
233
|
+
`./web` asserts the installed version against the declared range at import
|
|
234
|
+
time, so an absent or incompatible React fails loudly, by name, instead of
|
|
235
|
+
crashing later inside a hook with no version named as the cause.
|
|
236
|
+
|
|
237
|
+
## Requirements
|
|
238
|
+
|
|
239
|
+
Node 20 or newer. Zero runtime dependencies. React and React DOM are
|
|
240
|
+
optional peers, needed only by the `./web` subpath.
|
|
241
|
+
|
|
242
|
+
## What this package is not
|
|
243
|
+
|
|
244
|
+
- It carries no topics, no confidence floor, no currency window, no
|
|
245
|
+
jurisdiction logic, and no obligations. Every one of those is a value,
|
|
246
|
+
and values belong in each consumer's own repository.
|
|
247
|
+
- It makes **no claim of legal compliance**. It is record machinery, not
|
|
248
|
+
advice.
|
|
249
|
+
- It ships no storage and no audit implementation. Both are ports the host
|
|
250
|
+
implements, and no person-attributable record is written into this
|
|
251
|
+
repository.
|
|
252
|
+
- `subjectId` and `actorId` are opaque host-owned references. Neither ever
|
|
253
|
+
carries an email address, a name, a phone number, an address, or an IP,
|
|
254
|
+
and they are separate identifiers in every signature — an actor recording
|
|
255
|
+
a subject's own decision and an actor deciding on a subject's behalf must
|
|
256
|
+
stay distinguishable in the audit trail.
|
|
257
|
+
|
|
258
|
+
## Licence
|
|
259
|
+
|
|
260
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time proof that `StandingAuditEvent` (`./schema.ts`) carries no
|
|
3
|
+
* raw personal-data field — no email, name, phone, address, or IP — only
|
|
4
|
+
* the opaque `subjectId` and the separately-opaque `actorId`.
|
|
5
|
+
*
|
|
6
|
+
* Named `*.check.ts`, not `*.test.ts`, so it is part of the REAL `tsc` run
|
|
7
|
+
* (`npm run typecheck`) rather than only being transpiled, never
|
|
8
|
+
* type-checked, by vitest — see this repository's own contribution
|
|
9
|
+
* guide, "Type-level assertions live in `.check.ts(x)` files" entry, and
|
|
10
|
+
* `scripts/check-typechecked-assertions.mjs` for the gate that enforces it.
|
|
11
|
+
* Nothing here is ever imported by `index.ts` or any runtime code; its only
|
|
12
|
+
* job is to fail `tsc` if the contract regresses.
|
|
13
|
+
*
|
|
14
|
+
* `ExactKeys` fails to compile unless `Keys` is EXACTLY the allowed set —
|
|
15
|
+
* neither a subset (a required field silently dropped) nor a superset (a
|
|
16
|
+
* new field silently added, personal-data-shaped or not, without a human
|
|
17
|
+
* deciding it belongs here).
|
|
18
|
+
*
|
|
19
|
+
* `subjectId` and `actorId` are both in the allowed set, deliberately and
|
|
20
|
+
* separately. Merging them into one id would still pass a personal-data
|
|
21
|
+
* scan and would still be wrong: the whole point of this package is
|
|
22
|
+
* telling "the person asked for this" apart from "something acted on its
|
|
23
|
+
* own reading", and a single conflated identifier makes that distinction
|
|
24
|
+
* unrecoverable after the fact.
|
|
25
|
+
*/
|
|
26
|
+
import type { StandingAuditEvent } from "./schema.js";
|
|
27
|
+
type ExactKeys<Keys extends string, Allowed extends string> = [Allowed] extends [Keys] ? ([Keys] extends [Allowed] ? true : never) : never;
|
|
28
|
+
declare const ALLOWED_KEYS: readonly ["subjectId", "actorId", "topic", "type", "policyVersion", "occurredAt", "previousPolicyVersion"];
|
|
29
|
+
type AllowedKey = (typeof ALLOWED_KEYS)[number];
|
|
30
|
+
export declare const auditEventKeysAreExactlyTheAllowedSet: ExactKeys<keyof StandingAuditEvent, AllowedKey>;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=audit-shape.check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-shape.check.d.ts","sourceRoot":"","sources":["../src/audit-shape.check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,KAAK,SAAS,CAAC,IAAI,SAAS,MAAM,EAAE,OAAO,SAAS,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC;AAE3I,QAAA,MAAM,YAAY,4GAA6G,CAAC;AAChI,KAAK,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAMhD,eAAO,MAAM,qCAAqC,EAAE,SAAS,CAAC,MAAM,kBAAkB,EAAE,UAAU,CAAQ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const ALLOWED_KEYS = ["subjectId", "actorId", "topic", "type", "policyVersion", "occurredAt", "previousPolicyVersion"];
|
|
2
|
+
// If `StandingAuditEvent` ever gains or loses a key relative to
|
|
3
|
+
// `ALLOWED_KEYS` above, this assignment stops compiling — including if
|
|
4
|
+
// someone adds an `email`, `name`, `ip`, `phone`, or `address`-shaped
|
|
5
|
+
// field, and including if `actorId` is ever folded into `subjectId`.
|
|
6
|
+
export const auditEventKeysAreExactlyTheAllowedSet = true;
|
|
7
|
+
//# sourceMappingURL=audit-shape.check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-shape.check.js","sourceRoot":"","sources":["../src/audit-shape.check.ts"],"names":[],"mappings":"AA6BA,MAAM,YAAY,GAAG,CAAC,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,YAAY,EAAE,uBAAuB,CAAU,CAAC;AAGhI,gEAAgE;AAChE,uEAAuE;AACvE,sEAAsE;AACtE,qEAAqE;AACrE,MAAM,CAAC,MAAM,qCAAqC,GAAoD,IAAI,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `butler-check` — one bin, three gates, dispatched on `argv[0]` matching a
|
|
4
|
+
* subcommand name EXACTLY.
|
|
5
|
+
*
|
|
6
|
+
* Dispatch is on `argv[0]`, never on `basename(process.argv[1])`. A bin-name
|
|
7
|
+
* dispatch would see `cli.js` wherever this gate is invoked by its compiled
|
|
8
|
+
* path — which is exactly how this repository's own `npm run check` invokes
|
|
9
|
+
* other packages' gates — and would silently run the wrong command, or none.
|
|
10
|
+
*
|
|
11
|
+
* Presentation only: parse argv, load JSON files, run the pure checkers in
|
|
12
|
+
* `contract.ts`, print a report, pick an exit code. Every decision worth
|
|
13
|
+
* testing lives in `contract.ts` and is tested there directly, against
|
|
14
|
+
* plain values, with no filesystem involved.
|
|
15
|
+
*
|
|
16
|
+
* EXIT CODES — the contract a consumer's CI depends on (this repository's
|
|
17
|
+
* own contribution guide, "Gate CLIs exit `0` clean, `1` findings, `2`
|
|
18
|
+
* could not run"):
|
|
19
|
+
*
|
|
20
|
+
* 0 — ran cleanly against a non-empty record set and found nothing.
|
|
21
|
+
* 1 — ran cleanly and found at least one real violation.
|
|
22
|
+
* 2 — could not run. Kept strictly distinct from `1`, because "I checked
|
|
23
|
+
* and it is fine" and "I never checked" are different answers and a
|
|
24
|
+
* gate that reports the second as the first is worse than no gate.
|
|
25
|
+
*
|
|
26
|
+
* `2` is genuinely reachable here, on every subcommand, and each route is
|
|
27
|
+
* tested:
|
|
28
|
+
* - a record store that cannot be read — missing file, unreadable file,
|
|
29
|
+
* a directory where a file was named, invalid JSON, or JSON that does
|
|
30
|
+
* not validate against this package's own schema;
|
|
31
|
+
* - nothing to scan — an empty record set, which is not a clean run, it
|
|
32
|
+
* is a run that examined nothing;
|
|
33
|
+
* - a required declared value that was not supplied — the confidence
|
|
34
|
+
* floor, or the denial-invalidation policy. Neither has a default
|
|
35
|
+
* anywhere in this package, and a run missing one declines rather than
|
|
36
|
+
* inventing one of the consumer's own values;
|
|
37
|
+
* - NO GATE SELECTED AT ALL. A bare `butler-check` with no subcommand is
|
|
38
|
+
* a run that never happened, and it exits `2`, not `0` — see `main()`'s
|
|
39
|
+
* own comment for why an explicitly requested `--help` is the one
|
|
40
|
+
* argument-shaped `0` here and a dropped argument is not.
|
|
41
|
+
*/
|
|
42
|
+
/** Exported for `cli.test.ts` — anything wrong with the arguments themselves always maps to exit code 2, never 1. */
|
|
43
|
+
export declare class CliInputError extends Error {
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Exported (unlike a typical CLI `main`) so `cli.test.ts` can exercise the
|
|
47
|
+
* whole argv-to-exit-code contract directly, against real `mkdtemp` temp
|
|
48
|
+
* directories, without spawning a subprocess per case. Takes `argv` as a
|
|
49
|
+
* parameter rather than reading `process.argv` itself for exactly that
|
|
50
|
+
* reason — `run()` below is the only caller that reads the real
|
|
51
|
+
* `process.argv`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function main(argv: string[]): number;
|
|
54
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AA2EH,qHAAqH;AACrH,qBAAa,aAAc,SAAQ,KAAK;CAAG;AAkQ3C;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAyB3C"}
|