@envseal/core 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/paths.d.ts +9 -0
- package/dist/paths.js +59 -0
- package/package.json +5 -5
package/dist/paths.d.ts
CHANGED
|
@@ -14,4 +14,13 @@ export declare function loadOrCreateSalt(paths: ProjectPaths): Buffer;
|
|
|
14
14
|
export declare const HOOK_HEARTBEAT_FILE = "hook-heartbeat";
|
|
15
15
|
/** ISO timestamp of the hook's last observed run for a project, or null. */
|
|
16
16
|
export declare function readHookHeartbeat(root: string): string | null;
|
|
17
|
+
export declare const HOOK_DECISIONS_FILE = "hook-decisions";
|
|
18
|
+
export interface HookDecisions {
|
|
19
|
+
allow: number;
|
|
20
|
+
deny: number;
|
|
21
|
+
}
|
|
22
|
+
/** Cumulative hook decisions for a project, or null when unknown. */
|
|
23
|
+
export declare function readHookDecisions(root: string): HookDecisions | null;
|
|
24
|
+
/** Increment the allow or deny counter. Never throws; never touches decisions. */
|
|
25
|
+
export declare function recordHookDecision(root: string, allow: boolean): void;
|
|
17
26
|
//# sourceMappingURL=paths.d.ts.map
|
package/dist/paths.js
CHANGED
|
@@ -102,4 +102,63 @@ export function readHookHeartbeat(root) {
|
|
|
102
102
|
return null;
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
// --- Hook decision counters --------------------------------------------------
|
|
106
|
+
//
|
|
107
|
+
// The heartbeat proves the hook RAN; it says nothing about whether the matcher
|
|
108
|
+
// ever fires. These cumulative allow/deny counters answer that next question:
|
|
109
|
+
// a hook that runs but decides wrongly (or is neutered to allow everything)
|
|
110
|
+
// shows allow > 0 with deny stuck at 0. They prove the matcher FIRES, not that
|
|
111
|
+
// every decision is right — a same-uid agent can rewrite them, so they are
|
|
112
|
+
// observational and advisory, exactly like the heartbeat.
|
|
113
|
+
//
|
|
114
|
+
// Deliberately a SECOND file, not a second line in the heartbeat: older
|
|
115
|
+
// readers parse the heartbeat as a bare timestamp, and an unreadable new
|
|
116
|
+
// format would regress them to "never ran". An absent counter file reads as
|
|
117
|
+
// null ("unknown", pre-counters hook), never as zero.
|
|
118
|
+
//
|
|
119
|
+
// Counts are approximate under concurrent hook invocations (read-modify-write
|
|
120
|
+
// can lose an increment). Direction survives imprecision: deny > 0 still
|
|
121
|
+
// proves a deny happened.
|
|
122
|
+
export const HOOK_DECISIONS_FILE = 'hook-decisions';
|
|
123
|
+
function isHookDecisions(value) {
|
|
124
|
+
if (typeof value !== 'object' || value === null)
|
|
125
|
+
return false;
|
|
126
|
+
const rec = value;
|
|
127
|
+
return (typeof rec.allow === 'number' &&
|
|
128
|
+
Number.isInteger(rec.allow) &&
|
|
129
|
+
rec.allow >= 0 &&
|
|
130
|
+
typeof rec.deny === 'number' &&
|
|
131
|
+
Number.isInteger(rec.deny) &&
|
|
132
|
+
rec.deny >= 0);
|
|
133
|
+
}
|
|
134
|
+
/** Cumulative hook decisions for a project, or null when unknown. */
|
|
135
|
+
export function readHookDecisions(root) {
|
|
136
|
+
try {
|
|
137
|
+
const raw = readFileSync(join(resolve(root), '.envseal', HOOK_DECISIONS_FILE), 'utf8');
|
|
138
|
+
const parsed = JSON.parse(raw);
|
|
139
|
+
return isHookDecisions(parsed) ? parsed : null;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/** Increment the allow or deny counter. Never throws; never touches decisions. */
|
|
146
|
+
export function recordHookDecision(root, allow) {
|
|
147
|
+
try {
|
|
148
|
+
const stateDir = join(resolve(root), '.envseal');
|
|
149
|
+
const marker = join(stateDir, HOOK_DECISIONS_FILE);
|
|
150
|
+
const current = readHookDecisions(root) ?? { allow: 0, deny: 0 };
|
|
151
|
+
if (allow) {
|
|
152
|
+
current.allow += 1;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
current.deny += 1;
|
|
156
|
+
}
|
|
157
|
+
mkdirSync(stateDir, { recursive: true, mode: 0o700 });
|
|
158
|
+
writeFileSync(marker, `${JSON.stringify(current)}\n`, { mode: 0o600 });
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Observational only — an unwritable counter changes nothing.
|
|
162
|
+
}
|
|
163
|
+
}
|
|
105
164
|
//# sourceMappingURL=paths.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@envseal/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"jsonc-parser": "^3.3.1",
|
|
24
24
|
"ulid": "^2.3.0",
|
|
25
|
-
"@envseal/
|
|
26
|
-
"@envseal/
|
|
27
|
-
"@envseal/
|
|
28
|
-
"@envseal/prompters": "0.1.
|
|
25
|
+
"@envseal/registry": "0.1.8",
|
|
26
|
+
"@envseal/protocol": "0.1.8",
|
|
27
|
+
"@envseal/detector": "0.1.8",
|
|
28
|
+
"@envseal/prompters": "0.1.8"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"fast-check": "^3.23.1"
|