@intutic/sync-daemon 1.10.0 → 1.10.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/README.md +2 -2
- package/dist/agentReporter.d.ts +205 -0
- package/dist/agentReporter.d.ts.map +1 -0
- package/dist/agentReporter.js +394 -0
- package/dist/agentReporter.js.map +1 -0
- package/dist/configReader.d.ts +39 -2
- package/dist/configReader.d.ts.map +1 -1
- package/dist/configReader.js +56 -2
- package/dist/configReader.js.map +1 -1
- package/dist/configWriter.d.ts +51 -1
- package/dist/configWriter.d.ts.map +1 -1
- package/dist/configWriter.js +490 -63
- package/dist/configWriter.js.map +1 -1
- package/dist/harness/antigravityHooks.d.ts.map +1 -1
- package/dist/harness/antigravityHooks.js +33 -55
- package/dist/harness/antigravityHooks.js.map +1 -1
- package/dist/harness/claudeCodeHooks.d.ts +47 -13
- package/dist/harness/claudeCodeHooks.d.ts.map +1 -1
- package/dist/harness/claudeCodeHooks.js +316 -54
- package/dist/harness/claudeCodeHooks.js.map +1 -1
- package/dist/harness/claudeDesktopHooks.d.ts.map +1 -1
- package/dist/harness/claudeDesktopHooks.js +35 -48
- package/dist/harness/claudeDesktopHooks.js.map +1 -1
- package/dist/harness/clineHooks.d.ts.map +1 -1
- package/dist/harness/clineHooks.js +42 -34
- package/dist/harness/clineHooks.js.map +1 -1
- package/dist/harness/codexHooks.d.ts +36 -0
- package/dist/harness/codexHooks.d.ts.map +1 -0
- package/dist/harness/codexHooks.js +227 -0
- package/dist/harness/codexHooks.js.map +1 -0
- package/dist/harness/continueHooks.d.ts +45 -5
- package/dist/harness/continueHooks.d.ts.map +1 -1
- package/dist/harness/continueHooks.js +225 -5
- package/dist/harness/continueHooks.js.map +1 -1
- package/dist/harness/cursorHooks.d.ts +31 -0
- package/dist/harness/cursorHooks.d.ts.map +1 -1
- package/dist/harness/cursorHooks.js +78 -49
- package/dist/harness/cursorHooks.js.map +1 -1
- package/dist/harness/dshHooks.d.ts +198 -0
- package/dist/harness/dshHooks.d.ts.map +1 -0
- package/dist/harness/dshHooks.js +569 -0
- package/dist/harness/dshHooks.js.map +1 -0
- package/dist/harness/gateBody.d.ts +422 -0
- package/dist/harness/gateBody.d.ts.map +1 -0
- package/dist/harness/gateBody.js +1816 -0
- package/dist/harness/gateBody.js.map +1 -0
- package/dist/harness/gateKind.d.ts +164 -0
- package/dist/harness/gateKind.d.ts.map +1 -0
- package/dist/harness/gateKind.js +143 -0
- package/dist/harness/gateKind.js.map +1 -0
- package/dist/harness/githubCopilotHooks.d.ts +54 -0
- package/dist/harness/githubCopilotHooks.d.ts.map +1 -0
- package/dist/harness/githubCopilotHooks.js +218 -0
- package/dist/harness/githubCopilotHooks.js.map +1 -0
- package/dist/harness/gooseHooks.d.ts.map +1 -1
- package/dist/harness/gooseHooks.js +67 -37
- package/dist/harness/gooseHooks.js.map +1 -1
- package/dist/harness/grokHooks.d.ts +77 -0
- package/dist/harness/grokHooks.d.ts.map +1 -0
- package/dist/harness/grokHooks.js +350 -0
- package/dist/harness/grokHooks.js.map +1 -0
- package/dist/harness/hermesHooks.d.ts.map +1 -1
- package/dist/harness/hermesHooks.js +26 -55
- package/dist/harness/hermesHooks.js.map +1 -1
- package/dist/harness/holdRedaction.d.ts +69 -0
- package/dist/harness/holdRedaction.d.ts.map +1 -0
- package/dist/harness/holdRedaction.js +211 -0
- package/dist/harness/holdRedaction.js.map +1 -0
- package/dist/harness/jetbrainsXmlConfig.d.ts +51 -0
- package/dist/harness/jetbrainsXmlConfig.d.ts.map +1 -0
- package/dist/harness/jetbrainsXmlConfig.js +151 -0
- package/dist/harness/jetbrainsXmlConfig.js.map +1 -0
- package/dist/harness/mcpAutoWrite.d.ts +100 -3
- package/dist/harness/mcpAutoWrite.d.ts.map +1 -1
- package/dist/harness/mcpAutoWrite.js +712 -90
- package/dist/harness/mcpAutoWrite.js.map +1 -1
- package/dist/harness/museHooks.d.ts +53 -0
- package/dist/harness/museHooks.d.ts.map +1 -0
- package/dist/harness/museHooks.js +273 -0
- package/dist/harness/museHooks.js.map +1 -0
- package/dist/harness/n8nHooks.d.ts +25 -9
- package/dist/harness/n8nHooks.d.ts.map +1 -1
- package/dist/harness/n8nHooks.js +174 -9
- package/dist/harness/n8nHooks.js.map +1 -1
- package/dist/harness/openCodeHooks.d.ts +93 -0
- package/dist/harness/openCodeHooks.d.ts.map +1 -0
- package/dist/harness/openCodeHooks.js +248 -0
- package/dist/harness/openCodeHooks.js.map +1 -0
- package/dist/harness/openWebuiHooks.d.ts.map +1 -1
- package/dist/harness/openWebuiHooks.js +56 -11
- package/dist/harness/openWebuiHooks.js.map +1 -1
- package/dist/harness/openclawHooks.d.ts.map +1 -1
- package/dist/harness/openclawHooks.js +30 -54
- package/dist/harness/openclawHooks.js.map +1 -1
- package/dist/harness/openhandsHooks.d.ts.map +1 -1
- package/dist/harness/openhandsHooks.js +35 -41
- package/dist/harness/openhandsHooks.js.map +1 -1
- package/dist/harness/piHooks.d.ts.map +1 -1
- package/dist/harness/piHooks.js +31 -58
- package/dist/harness/piHooks.js.map +1 -1
- package/dist/harness/protectedPaths.d.ts +353 -0
- package/dist/harness/protectedPaths.d.ts.map +1 -0
- package/dist/harness/protectedPaths.js +805 -0
- package/dist/harness/protectedPaths.js.map +1 -0
- package/dist/harness/rooCodeHooks.d.ts.map +1 -1
- package/dist/harness/rooCodeHooks.js +34 -48
- package/dist/harness/rooCodeHooks.js.map +1 -1
- package/dist/harness/windsurfHooks.d.ts +72 -0
- package/dist/harness/windsurfHooks.d.ts.map +1 -1
- package/dist/harness/windsurfHooks.js +172 -58
- package/dist/harness/windsurfHooks.js.map +1 -1
- package/dist/harness/windsurfJetBrainsProxy.d.ts +78 -0
- package/dist/harness/windsurfJetBrainsProxy.d.ts.map +1 -0
- package/dist/harness/windsurfJetBrainsProxy.js +159 -0
- package/dist/harness/windsurfJetBrainsProxy.js.map +1 -0
- package/dist/hashReporter.js +1 -1
- package/dist/hashReporter.js.map +1 -1
- package/dist/index.d.ts +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- package/dist/integrityStore.d.ts +0 -8
- package/dist/integrityStore.d.ts.map +1 -1
- package/dist/integrityStore.js +6 -29
- package/dist/integrityStore.js.map +1 -1
- package/dist/lib/approvedBypasses.d.ts +76 -0
- package/dist/lib/approvedBypasses.d.ts.map +1 -0
- package/dist/lib/approvedBypasses.js +140 -0
- package/dist/lib/approvedBypasses.js.map +1 -0
- package/dist/lib/complianceProbes.d.ts.map +1 -1
- package/dist/lib/complianceProbes.js +5 -0
- package/dist/lib/complianceProbes.js.map +1 -1
- package/dist/lib/decisionsDigest.d.ts +99 -0
- package/dist/lib/decisionsDigest.d.ts.map +1 -0
- package/dist/lib/decisionsDigest.js +199 -0
- package/dist/lib/decisionsDigest.js.map +1 -0
- package/dist/lib/egressPolicy.d.ts +69 -0
- package/dist/lib/egressPolicy.d.ts.map +1 -0
- package/dist/lib/egressPolicy.js +126 -0
- package/dist/lib/egressPolicy.js.map +1 -0
- package/dist/lib/gitWorktrees.d.ts +140 -0
- package/dist/lib/gitWorktrees.d.ts.map +1 -0
- package/dist/lib/gitWorktrees.js +191 -0
- package/dist/lib/gitWorktrees.js.map +1 -0
- package/dist/lib/policySnapshot.d.ts +224 -0
- package/dist/lib/policySnapshot.d.ts.map +1 -0
- package/dist/lib/policySnapshot.js +637 -0
- package/dist/lib/policySnapshot.js.map +1 -0
- package/dist/lib/processPoller.d.ts +44 -0
- package/dist/lib/processPoller.d.ts.map +1 -1
- package/dist/lib/processPoller.js +176 -1
- package/dist/lib/processPoller.js.map +1 -1
- package/dist/lib/runtimeEnv.d.ts +8 -0
- package/dist/lib/runtimeEnv.d.ts.map +1 -1
- package/dist/lib/runtimeEnv.js +3 -0
- package/dist/lib/runtimeEnv.js.map +1 -1
- package/dist/localTrajectoryHeuristics.d.ts +51 -0
- package/dist/localTrajectoryHeuristics.d.ts.map +1 -0
- package/dist/localTrajectoryHeuristics.js +90 -0
- package/dist/localTrajectoryHeuristics.js.map +1 -0
- package/dist/sessionReporter.d.ts +70 -0
- package/dist/sessionReporter.d.ts.map +1 -0
- package/dist/sessionReporter.js +140 -0
- package/dist/sessionReporter.js.map +1 -0
- package/dist/skillWriter.d.ts +1 -1
- package/dist/skillWriter.d.ts.map +1 -1
- package/dist/skillWriter.js +5 -2
- package/dist/skillWriter.js.map +1 -1
- package/dist/syncLoop.d.ts +105 -3
- package/dist/syncLoop.d.ts.map +1 -1
- package/dist/syncLoop.js +397 -65
- package/dist/syncLoop.js.map +1 -1
- package/dist/trajectoryMonitor.d.ts +2 -0
- package/dist/trajectoryMonitor.d.ts.map +1 -1
- package/dist/trajectoryMonitor.js +57 -0
- package/dist/trajectoryMonitor.js.map +1 -1
- package/dist/watcher/driftWatcher.d.ts.map +1 -1
- package/dist/watcher/driftWatcher.js +12 -3
- package/dist/watcher/driftWatcher.js.map +1 -1
- package/dist/watcher/settingsGuard.d.ts +23 -0
- package/dist/watcher/settingsGuard.d.ts.map +1 -1
- package/dist/watcher/settingsGuard.js +202 -17
- package/dist/watcher/settingsGuard.js.map +1 -1
- package/package.json +37 -6
- package/dist/brainIndexer.d.ts +0 -51
- package/dist/brainIndexer.d.ts.map +0 -1
- package/dist/brainIndexer.js +0 -148
- package/dist/brainIndexer.js.map +0 -1
- package/dist/harness/cursorHooksJson.d.ts +0 -29
- package/dist/harness/cursorHooksJson.d.ts.map +0 -1
- package/dist/harness/cursorHooksJson.js +0 -130
- package/dist/harness/cursorHooksJson.js.map +0 -1
- package/dist/harness/gooseHooksWriter.d.ts +0 -27
- package/dist/harness/gooseHooksWriter.d.ts.map +0 -1
- package/dist/harness/gooseHooksWriter.js +0 -147
- package/dist/harness/gooseHooksWriter.js.map +0 -1
- package/dist/harness/openhandsHooksWriter.d.ts +0 -23
- package/dist/harness/openhandsHooksWriter.d.ts.map +0 -1
- package/dist/harness/openhandsHooksWriter.js +0 -146
- package/dist/harness/openhandsHooksWriter.js.map +0 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* approvedBypasses.ts — ships approved `review_before` bypasses *to* the
|
|
3
|
+
* machine, mirroring how `policySnapshot.ts` ships policy.
|
|
4
|
+
*
|
|
5
|
+
* # Why a separate file from policySnapshot.ts
|
|
6
|
+
*
|
|
7
|
+
* A bypass entry is not a rule: it is a one-time, per-call approval keyed on
|
|
8
|
+
* (workspace, SOP rule id, normalised tool name, hashed command/target), with
|
|
9
|
+
* its own TTL, and it is additive in the *opposite* direction from a policy
|
|
10
|
+
* rule — a policy snapshot can only turn an allow into a block, this cache can
|
|
11
|
+
* only turn one already-held call into an allow, for the one caller who
|
|
12
|
+
* already approved it. Folding the two into one artifact would make either
|
|
13
|
+
* "what does this line do" or "is this additive in the safe direction"
|
|
14
|
+
* dependent on which column you are looking at.
|
|
15
|
+
*
|
|
16
|
+
* # The contract with the gate
|
|
17
|
+
*
|
|
18
|
+
* `.intutic/hooks/approved-bypasses.jsonl` — same directory as the policy
|
|
19
|
+
* snapshot, and for the same reason: `.intutic/hooks` is already in
|
|
20
|
+
* `UNIVERSAL_PROTECTED_PATHS`, so every gate already refuses an agent's own
|
|
21
|
+
* attempt to write here. Two header lines carry integrity, exactly like the
|
|
22
|
+
* `.rules` projection: `#digest` (sha256 of the JSONL body, first 32 hex
|
|
23
|
+
* chars) and `#workspace` (refused if it does not match the gate's own
|
|
24
|
+
* workspace id). Unlike the policy snapshot there is no `#generated` age
|
|
25
|
+
* check — each line carries its own `expiresAt`, and the gate compares that
|
|
26
|
+
* against wall-clock time on every read. A stale FILE degrades to "fewer
|
|
27
|
+
* valid bypasses" (fail closed toward the hold); it can never resurrect an
|
|
28
|
+
* entry whose own TTL has passed.
|
|
29
|
+
*
|
|
30
|
+
* Only `review_before` gates read this today (claudeCodeHooks.ts's
|
|
31
|
+
* `_intuticApprovedBypass`) — see that module's docstring for why the other
|
|
32
|
+
* harness writers are not wired to it: none of them implement `review_before`
|
|
33
|
+
* holds at all, so there is nothing for a bypass to apply to there yet.
|
|
34
|
+
*
|
|
35
|
+
* @module
|
|
36
|
+
*/
|
|
37
|
+
import * as fs from 'node:fs/promises';
|
|
38
|
+
import * as path from 'node:path';
|
|
39
|
+
import * as os from 'node:os';
|
|
40
|
+
import { createHash } from 'node:crypto';
|
|
41
|
+
import { createLogger } from '@intutic/logger';
|
|
42
|
+
const log = createLogger('sync-approved-bypasses');
|
|
43
|
+
export const DEFAULT_BYPASS_CACHE_DIR = path.join(os.homedir(), '.intutic', 'hooks');
|
|
44
|
+
export const APPROVED_BYPASSES_FILE = 'approved-bypasses.jsonl';
|
|
45
|
+
/**
|
|
46
|
+
* Trims trailing `/` characters without a regex.
|
|
47
|
+
*
|
|
48
|
+
* `controlPlaneUrl` is operator-configured, not remotely attacker-controlled
|
|
49
|
+
* — but CodeQL's static analysis flags `/\/+$/` as a polynomial-time pattern
|
|
50
|
+
* on external input regardless of practical exploitability here, and the
|
|
51
|
+
* identical pattern already existed unflagged in `onboarding.ts` and
|
|
52
|
+
* `exec.ts` (fixed alongside this one). A loop is O(n), cannot be
|
|
53
|
+
* mis-classified as a ReDoS shape by any static analyzer, and needs no
|
|
54
|
+
* exemption to justify.
|
|
55
|
+
*/
|
|
56
|
+
function trimTrailingSlashes(s) {
|
|
57
|
+
let end = s.length;
|
|
58
|
+
while (end > 0 && s.charCodeAt(end - 1) === 47 /* '/' */)
|
|
59
|
+
end--;
|
|
60
|
+
return s.slice(0, end);
|
|
61
|
+
}
|
|
62
|
+
/** Fetches the workspace's currently-approved bypasses. Returns null on any
|
|
63
|
+
* failure — the caller keeps the previous cache file rather than replacing
|
|
64
|
+
* it with nothing, same rule `fetchResolvedPolicy` follows. */
|
|
65
|
+
export async function fetchApprovedBypasses(opts) {
|
|
66
|
+
const url = `${trimTrailingSlashes(opts.controlPlaneUrl)}/api/v1/decisions/approved-bypasses` +
|
|
67
|
+
`?workspaceId=${encodeURIComponent(opts.workspaceId)}`;
|
|
68
|
+
try {
|
|
69
|
+
const res = await fetch(url, {
|
|
70
|
+
headers: { Authorization: `Bearer ${opts.apiKey}` },
|
|
71
|
+
signal: AbortSignal.timeout(10_000),
|
|
72
|
+
});
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
log.warn({ action: 'bypass_fetch_failed', status: res.status }, 'approved-bypasses returned non-OK');
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const body = (await res.json());
|
|
78
|
+
if (typeof body !== 'object' || body === null)
|
|
79
|
+
return null;
|
|
80
|
+
const rec = body;
|
|
81
|
+
const rows = Array.isArray(rec.bypasses) ? rec.bypasses : [];
|
|
82
|
+
return rows.filter((r) => {
|
|
83
|
+
if (typeof r !== 'object' || r === null)
|
|
84
|
+
return false;
|
|
85
|
+
const x = r;
|
|
86
|
+
return (typeof x.workspaceId === 'string' &&
|
|
87
|
+
typeof x.sopRuleId === 'string' &&
|
|
88
|
+
typeof x.toolNameNormalized === 'string' &&
|
|
89
|
+
typeof x.targetHash === 'string' &&
|
|
90
|
+
typeof x.holdId === 'string' &&
|
|
91
|
+
typeof x.decidedBy === 'string' &&
|
|
92
|
+
typeof x.decidedAt === 'string' &&
|
|
93
|
+
typeof x.expiresAt === 'string');
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
log.warn({ action: 'bypass_fetch_failed', err }, 'approved-bypasses unreachable');
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Writes the cache file atomically, digest and workspace header included —
|
|
102
|
+
* same convention as `writePolicySnapshot`. Exported so a test can assert
|
|
103
|
+
* the on-disk shape directly. */
|
|
104
|
+
export async function writeApprovedBypasses(entries, workspaceId, cacheDir = DEFAULT_BYPASS_CACHE_DIR) {
|
|
105
|
+
const lines = entries.map((e) => JSON.stringify(e));
|
|
106
|
+
const digest = createHash('sha256').update(lines.join('\n')).digest('hex').slice(0, 32);
|
|
107
|
+
const text = `# Intutic approved review_before bypasses — auto-generated. DO NOT EDIT.\n` +
|
|
108
|
+
`#digest ${digest}\n` +
|
|
109
|
+
`#workspace ${workspaceId}\n` +
|
|
110
|
+
`#generated ${new Date().toISOString()}\n` +
|
|
111
|
+
(lines.length > 0 ? lines.join('\n') + '\n' : '');
|
|
112
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
113
|
+
const target = path.join(cacheDir, APPROVED_BYPASSES_FILE);
|
|
114
|
+
const tmp = target + '.tmp';
|
|
115
|
+
await fs.writeFile(tmp, text, { encoding: 'utf-8' });
|
|
116
|
+
// Same read-only-by-rename posture as the policy snapshot: the daemon
|
|
117
|
+
// never needs write access to the file itself after this, only to replace
|
|
118
|
+
// it wholesale on the next cycle.
|
|
119
|
+
await fs.chmod(tmp, 0o444);
|
|
120
|
+
await fs.rename(tmp, target);
|
|
121
|
+
log.debug({ action: 'approved_bypasses_written', digest, count: entries.length, dir: cacheDir }, 'Approved-bypass cache refreshed');
|
|
122
|
+
return { digest, count: entries.length };
|
|
123
|
+
}
|
|
124
|
+
/** Fetch and write in one call. Safe to run on every sync cycle. Never
|
|
125
|
+
* throws — a failed poll must not take down the sync loop, and leaves the
|
|
126
|
+
* previous cache in place (stale bypasses still expire on their own TTL,
|
|
127
|
+
* which is what makes that safe). */
|
|
128
|
+
export async function refreshApprovedBypasses(opts) {
|
|
129
|
+
const entries = await fetchApprovedBypasses(opts);
|
|
130
|
+
if (entries === null)
|
|
131
|
+
return null;
|
|
132
|
+
try {
|
|
133
|
+
return await writeApprovedBypasses(entries, opts.workspaceId, opts.cacheDir ?? DEFAULT_BYPASS_CACHE_DIR);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
log.warn({ action: 'approved_bypasses_write_failed', err }, 'Could not write approved-bypasses cache');
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=approvedBypasses.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approvedBypasses.js","sourceRoot":"","sources":["../../src/lib/approvedBypasses.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"complianceProbes.d.ts","sourceRoot":"","sources":["../../src/lib/complianceProbes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"complianceProbes.d.ts","sourceRoot":"","sources":["../../src/lib/complianceProbes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AASH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE;QACT,KAAK,EAAE,gBAAgB,CAAA;QACvB,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,WAAW,EAAE,MAAM,CAAA;QACnB,SAAS,EAAE,MAAM,CAAA;QACjB,UAAU,EAAE,MAAM,CAAA;KACnB,CAAA;CACF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,SAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAqF5G;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,SAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAiBjG"}
|
|
@@ -78,6 +78,11 @@ export function checkHostContainment(hostname, workspaceId, port = 443) {
|
|
|
78
78
|
resolve({
|
|
79
79
|
host: hostname,
|
|
80
80
|
contained: true,
|
|
81
|
+
// `||`, not `??`, and deliberately. `??` only falls through on
|
|
82
|
+
// null/undefined, so an ErrnoException carrying `code: ''` would report
|
|
83
|
+
// `error: ''` and discard the message that says what actually happened.
|
|
84
|
+
// An empty code is the one case where these differ, and falling through
|
|
85
|
+
// is the behaviour worth having.
|
|
81
86
|
error: err.code || err.message,
|
|
82
87
|
});
|
|
83
88
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"complianceProbes.js","sourceRoot":"","sources":["../../src/lib/complianceProbes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"complianceProbes.js","sourceRoot":"","sources":["../../src/lib/complianceProbes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* decisionsDigest.ts — governed decisions log.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `egressPolicy.ts`'s thin-projection pattern (fetch a bounded,
|
|
5
|
+
* read-only projection from the control plane every sync cycle, write it
|
|
6
|
+
* locally) for a different payload: the last ~20 governance decisions
|
|
7
|
+
* (adjudications, approved/rejected decisions, resolved incidents,
|
|
8
|
+
* settings changes), rendered as one-line summaries SERVER-SIDE by
|
|
9
|
+
* `GET /api/v1/workspace/decisions-digest`. This module never parses or
|
|
10
|
+
* re-renders decision content — it only writes what it is given.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately narrow, same boundary the control-plane route documents:
|
|
13
|
+
* governance-decision records only, never conversational memory or general
|
|
14
|
+
* context management (this repo pivoted away from that harness product —
|
|
15
|
+
* see this repo's own CLAUDE.md history, and `apps/docs/guide/decisions-log.md`).
|
|
16
|
+
*
|
|
17
|
+
* Opt-in via `WorkspaceSettings.decisionsLogEnabled` (default off). The
|
|
18
|
+
* CALLER is responsible for that gate — `refreshDecisionsDigest` always
|
|
19
|
+
* fetches and writes when invoked, so `syncLoop.ts` only calls it when the
|
|
20
|
+
* workspace has opted in. When disabled, the caller simply stops calling
|
|
21
|
+
* this module; existing files are left as they were last written rather than
|
|
22
|
+
* being force-deleted (a developer's editor may have that file open) or
|
|
23
|
+
* overwritten with a stale/misleading "disabled" placeholder.
|
|
24
|
+
*
|
|
25
|
+
* Two files:
|
|
26
|
+
*
|
|
27
|
+
* 1. `.intutic/DECISIONS.md` — the full bounded record (all entries the
|
|
28
|
+
* digest returns), regenerated whole every cycle with `configWriter.ts`'s
|
|
29
|
+
* pattern (i): a DO-NOT-EDIT header + `atomicWrite`. Gitignored — runtime
|
|
30
|
+
* artifacts stay untracked, the same rule this repo's own CLAUDE.md doc
|
|
31
|
+
* comment states for daemon-generated governance files.
|
|
32
|
+
* 2. A bounded, marker-delimited section (last ~10 entries) idempotently
|
|
33
|
+
* injected into the `claude-code` harness's own regenerated config file
|
|
34
|
+
* (`HARNESS_FILES['claude-code']`, i.e. `CLAUDE.md`) — this is what makes
|
|
35
|
+
* the digest something the agent actually reads without needing to know
|
|
36
|
+
* `.intutic/DECISIONS.md` exists. A marker pair
|
|
37
|
+
* (`INTUTIC:DECISIONS_LOG:START`/`END`) is replaced in place on every
|
|
38
|
+
* cycle, never re-appended.
|
|
39
|
+
*
|
|
40
|
+
* Deliberately does NOT reuse `configWriter.ts`'s own `fileHeader()` for the
|
|
41
|
+
* DECISIONS.md header — that helper stamps `newIso()` (wall-clock "Last
|
|
42
|
+
* sync"), which would make the file churn every cycle even when the digest
|
|
43
|
+
* itself hasn't changed. The header here instead derives its timestamp from
|
|
44
|
+
* the digest's own newest entry, so identical input renders an identical
|
|
45
|
+
* file — see the render-idempotence test.
|
|
46
|
+
*
|
|
47
|
+
* @module
|
|
48
|
+
*/
|
|
49
|
+
import { HarnessType } from '@intutic/shared-types';
|
|
50
|
+
/** Relative to the workspace root. */
|
|
51
|
+
export declare const DECISIONS_LOG_RELATIVE_PATH: string;
|
|
52
|
+
/** One digest entry, exactly as `GET /api/v1/workspace/decisions-digest` returns it — the summary line is already rendered server-side. */
|
|
53
|
+
export interface DecisionsDigestEntry {
|
|
54
|
+
id: string;
|
|
55
|
+
kind: string;
|
|
56
|
+
timestamp: string;
|
|
57
|
+
summary: string;
|
|
58
|
+
}
|
|
59
|
+
export interface DecisionsDigestResponse {
|
|
60
|
+
workspaceId: string;
|
|
61
|
+
entries: DecisionsDigestEntry[];
|
|
62
|
+
}
|
|
63
|
+
export interface DecisionsDigestOptions {
|
|
64
|
+
controlPlaneUrl: string;
|
|
65
|
+
apiKey: string;
|
|
66
|
+
workspaceId: string;
|
|
67
|
+
workspaceRoot: string;
|
|
68
|
+
/** Active harnesses this cycle — the bounded section is only injected when `claude-code` is among them. */
|
|
69
|
+
harnesses: HarnessType[];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Fetch this workspace's decisions digest. Returns null on any failure — the
|
|
73
|
+
* caller keeps whatever was written last cycle rather than replacing it with
|
|
74
|
+
* nothing, the same rule `egressPolicy.ts`'s fetch follows.
|
|
75
|
+
*/
|
|
76
|
+
export declare function fetchDecisionsDigest(opts: Pick<DecisionsDigestOptions, 'controlPlaneUrl' | 'apiKey' | 'workspaceId'>): Promise<DecisionsDigestResponse | null>;
|
|
77
|
+
/** Renders the full bounded record written to `.intutic/DECISIONS.md`. */
|
|
78
|
+
export declare function renderDecisionsMarkdown(entries: DecisionsDigestEntry[]): string;
|
|
79
|
+
/** Renders the bounded, marker-delimited section injected into the harness config file — the newest `limit` entries only. */
|
|
80
|
+
export declare function renderBoundedSection(entries: DecisionsDigestEntry[], limit?: number): string;
|
|
81
|
+
/**
|
|
82
|
+
* Idempotently inject/replace the bounded section in `content`. If the
|
|
83
|
+
* markers are present, everything between them (inclusive) is replaced — the
|
|
84
|
+
* mechanism the `applyConfigEdits` SkillOpt writer already uses elsewhere in
|
|
85
|
+
* this file's sibling module, `configWriter.ts` — never duplicated. If
|
|
86
|
+
* absent, the section is appended once, with a separating blank line.
|
|
87
|
+
*/
|
|
88
|
+
export declare function injectBoundedSection(content: string, section: string): string;
|
|
89
|
+
/**
|
|
90
|
+
* Fetch and write both files in one call, mirroring `refreshEgressPolicy`.
|
|
91
|
+
* Never throws — a failed poll must not take down the sync loop.
|
|
92
|
+
*
|
|
93
|
+
* Does NOT itself check `WorkspaceSettings.decisionsLogEnabled` — see this
|
|
94
|
+
* module's own doc comment for why that gate belongs to the caller.
|
|
95
|
+
*/
|
|
96
|
+
export declare function refreshDecisionsDigest(opts: DecisionsDigestOptions): Promise<{
|
|
97
|
+
entriesWritten: number;
|
|
98
|
+
} | null>;
|
|
99
|
+
//# sourceMappingURL=decisionsDigest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decisionsDigest.d.ts","sourceRoot":"","sources":["../../src/lib/decisionsDigest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAKH,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAKnD,sCAAsC;AACtC,eAAO,MAAM,2BAA2B,QAAwC,CAAA;AAKhF,2IAA2I;AAC3I,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,oBAAoB,EAAE,CAAA;CAChC;AAED,MAAM,WAAW,sBAAsB;IACrC,eAAe,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,2GAA2G;IAC3G,SAAS,EAAE,WAAW,EAAE,CAAA;CACzB;AAyBD;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,IAAI,CAAC,sBAAsB,EAAE,iBAAiB,GAAG,QAAQ,GAAG,aAAa,CAAC,GAC/E,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CA0BzC;AAkBD,0EAA0E;AAC1E,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,CAO/E;AAED,6HAA6H;AAC7H,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,EAAE,EAAE,KAAK,SAAK,GAAG,MAAM,CAOxF;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAQ7E;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAmC5C"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* decisionsDigest.ts — governed decisions log.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `egressPolicy.ts`'s thin-projection pattern (fetch a bounded,
|
|
5
|
+
* read-only projection from the control plane every sync cycle, write it
|
|
6
|
+
* locally) for a different payload: the last ~20 governance decisions
|
|
7
|
+
* (adjudications, approved/rejected decisions, resolved incidents,
|
|
8
|
+
* settings changes), rendered as one-line summaries SERVER-SIDE by
|
|
9
|
+
* `GET /api/v1/workspace/decisions-digest`. This module never parses or
|
|
10
|
+
* re-renders decision content — it only writes what it is given.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately narrow, same boundary the control-plane route documents:
|
|
13
|
+
* governance-decision records only, never conversational memory or general
|
|
14
|
+
* context management (this repo pivoted away from that harness product —
|
|
15
|
+
* see this repo's own CLAUDE.md history, and `apps/docs/guide/decisions-log.md`).
|
|
16
|
+
*
|
|
17
|
+
* Opt-in via `WorkspaceSettings.decisionsLogEnabled` (default off). The
|
|
18
|
+
* CALLER is responsible for that gate — `refreshDecisionsDigest` always
|
|
19
|
+
* fetches and writes when invoked, so `syncLoop.ts` only calls it when the
|
|
20
|
+
* workspace has opted in. When disabled, the caller simply stops calling
|
|
21
|
+
* this module; existing files are left as they were last written rather than
|
|
22
|
+
* being force-deleted (a developer's editor may have that file open) or
|
|
23
|
+
* overwritten with a stale/misleading "disabled" placeholder.
|
|
24
|
+
*
|
|
25
|
+
* Two files:
|
|
26
|
+
*
|
|
27
|
+
* 1. `.intutic/DECISIONS.md` — the full bounded record (all entries the
|
|
28
|
+
* digest returns), regenerated whole every cycle with `configWriter.ts`'s
|
|
29
|
+
* pattern (i): a DO-NOT-EDIT header + `atomicWrite`. Gitignored — runtime
|
|
30
|
+
* artifacts stay untracked, the same rule this repo's own CLAUDE.md doc
|
|
31
|
+
* comment states for daemon-generated governance files.
|
|
32
|
+
* 2. A bounded, marker-delimited section (last ~10 entries) idempotently
|
|
33
|
+
* injected into the `claude-code` harness's own regenerated config file
|
|
34
|
+
* (`HARNESS_FILES['claude-code']`, i.e. `CLAUDE.md`) — this is what makes
|
|
35
|
+
* the digest something the agent actually reads without needing to know
|
|
36
|
+
* `.intutic/DECISIONS.md` exists. A marker pair
|
|
37
|
+
* (`INTUTIC:DECISIONS_LOG:START`/`END`) is replaced in place on every
|
|
38
|
+
* cycle, never re-appended.
|
|
39
|
+
*
|
|
40
|
+
* Deliberately does NOT reuse `configWriter.ts`'s own `fileHeader()` for the
|
|
41
|
+
* DECISIONS.md header — that helper stamps `newIso()` (wall-clock "Last
|
|
42
|
+
* sync"), which would make the file churn every cycle even when the digest
|
|
43
|
+
* itself hasn't changed. The header here instead derives its timestamp from
|
|
44
|
+
* the digest's own newest entry, so identical input renders an identical
|
|
45
|
+
* file — see the render-idempotence test.
|
|
46
|
+
*
|
|
47
|
+
* @module
|
|
48
|
+
*/
|
|
49
|
+
import * as fs from 'node:fs/promises';
|
|
50
|
+
import * as path from 'node:path';
|
|
51
|
+
import { createLogger } from '@intutic/logger';
|
|
52
|
+
import { HarnessType } from '@intutic/shared-types';
|
|
53
|
+
import { HARNESS_FILES, atomicWrite } from '../configWriter.js';
|
|
54
|
+
const log = createLogger('sync-decisions-digest');
|
|
55
|
+
/** Relative to the workspace root. */
|
|
56
|
+
export const DECISIONS_LOG_RELATIVE_PATH = path.join('.intutic', 'DECISIONS.md');
|
|
57
|
+
const SECTION_START = '<!-- INTUTIC:DECISIONS_LOG:START -->';
|
|
58
|
+
const SECTION_END = '<!-- INTUTIC:DECISIONS_LOG:END -->';
|
|
59
|
+
/**
|
|
60
|
+
* Trims trailing `/` without a regex — see the identical helper in
|
|
61
|
+
* `egressPolicy.ts`/`approvedBypasses.ts`: `/\/+$/` is flagged by CodeQL as a
|
|
62
|
+
* polynomial-time pattern on external input, and a loop sidesteps the whole
|
|
63
|
+
* category.
|
|
64
|
+
*/
|
|
65
|
+
function trimTrailingSlashes(s) {
|
|
66
|
+
let end = s.length;
|
|
67
|
+
while (end > 0 && s.charCodeAt(end - 1) === 47 /* '/' */)
|
|
68
|
+
end--;
|
|
69
|
+
return s.slice(0, end);
|
|
70
|
+
}
|
|
71
|
+
function isDigestEntry(value) {
|
|
72
|
+
if (typeof value !== 'object' || value === null)
|
|
73
|
+
return false;
|
|
74
|
+
const e = value;
|
|
75
|
+
return (typeof e.id === 'string' &&
|
|
76
|
+
typeof e.kind === 'string' &&
|
|
77
|
+
typeof e.timestamp === 'string' &&
|
|
78
|
+
typeof e.summary === 'string');
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Fetch this workspace's decisions digest. Returns null on any failure — the
|
|
82
|
+
* caller keeps whatever was written last cycle rather than replacing it with
|
|
83
|
+
* nothing, the same rule `egressPolicy.ts`'s fetch follows.
|
|
84
|
+
*/
|
|
85
|
+
export async function fetchDecisionsDigest(opts) {
|
|
86
|
+
const url = `${trimTrailingSlashes(opts.controlPlaneUrl)}/api/v1/workspace/decisions-digest` +
|
|
87
|
+
`?workspaceId=${encodeURIComponent(opts.workspaceId)}`;
|
|
88
|
+
try {
|
|
89
|
+
const res = await fetch(url, {
|
|
90
|
+
headers: { Authorization: `Bearer ${opts.apiKey}`, 'x-workspace-id': opts.workspaceId },
|
|
91
|
+
signal: AbortSignal.timeout(10_000),
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
log.warn({ action: 'decisions_digest_fetch_failed', status: res.status }, 'decisions-digest returned non-OK');
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const body = (await res.json());
|
|
98
|
+
if (typeof body !== 'object' || body === null)
|
|
99
|
+
return null;
|
|
100
|
+
const rec = body;
|
|
101
|
+
const workspaceId = typeof rec.workspaceId === 'string' ? rec.workspaceId : opts.workspaceId;
|
|
102
|
+
const entries = Array.isArray(rec.entries) ? rec.entries.filter(isDigestEntry) : [];
|
|
103
|
+
return { workspaceId, entries };
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
log.warn({ action: 'decisions_digest_fetch_failed', err }, 'decisions-digest unreachable');
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Deterministic header: no wall-clock stamp, so identical input renders an identical file. */
|
|
111
|
+
function renderHeader(entries) {
|
|
112
|
+
const latest = entries[0]?.timestamp ?? 'never';
|
|
113
|
+
return [
|
|
114
|
+
'# Intutic Governed Decisions Log (auto-generated)',
|
|
115
|
+
'# DO NOT EDIT — managed by intutic sync daemon',
|
|
116
|
+
`# Most recent entry: ${latest}`,
|
|
117
|
+
'#',
|
|
118
|
+
'# Governance-decision records only (adjudications, approved/rejected',
|
|
119
|
+
'# decisions, resolved incidents, settings changes) — NOT conversational',
|
|
120
|
+
'# memory or general context management.',
|
|
121
|
+
'',
|
|
122
|
+
'',
|
|
123
|
+
].join('\n');
|
|
124
|
+
}
|
|
125
|
+
/** Renders the full bounded record written to `.intutic/DECISIONS.md`. */
|
|
126
|
+
export function renderDecisionsMarkdown(entries) {
|
|
127
|
+
const header = renderHeader(entries);
|
|
128
|
+
if (entries.length === 0) {
|
|
129
|
+
return `${header}_No governance decisions recorded yet._\n`;
|
|
130
|
+
}
|
|
131
|
+
const lines = entries.map((e) => `- ${e.timestamp} — ${e.summary}`);
|
|
132
|
+
return header + lines.join('\n') + '\n';
|
|
133
|
+
}
|
|
134
|
+
/** Renders the bounded, marker-delimited section injected into the harness config file — the newest `limit` entries only. */
|
|
135
|
+
export function renderBoundedSection(entries, limit = 10) {
|
|
136
|
+
const bounded = entries.slice(0, limit);
|
|
137
|
+
const lines = bounded.length > 0
|
|
138
|
+
? bounded.map((e) => `- ${e.timestamp} — ${e.summary}`)
|
|
139
|
+
: ['_No governance decisions recorded yet._'];
|
|
140
|
+
return [SECTION_START, '## Recent Governed Decisions', '', ...lines, SECTION_END].join('\n');
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Idempotently inject/replace the bounded section in `content`. If the
|
|
144
|
+
* markers are present, everything between them (inclusive) is replaced — the
|
|
145
|
+
* mechanism the `applyConfigEdits` SkillOpt writer already uses elsewhere in
|
|
146
|
+
* this file's sibling module, `configWriter.ts` — never duplicated. If
|
|
147
|
+
* absent, the section is appended once, with a separating blank line.
|
|
148
|
+
*/
|
|
149
|
+
export function injectBoundedSection(content, section) {
|
|
150
|
+
const startIdx = content.indexOf(SECTION_START);
|
|
151
|
+
const endIdx = content.indexOf(SECTION_END);
|
|
152
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
153
|
+
return content.slice(0, startIdx) + section + content.slice(endIdx + SECTION_END.length);
|
|
154
|
+
}
|
|
155
|
+
const sep = content.endsWith('\n\n') ? '' : content.endsWith('\n') ? '\n' : '\n\n';
|
|
156
|
+
return `${content}${sep}${section}\n`;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Fetch and write both files in one call, mirroring `refreshEgressPolicy`.
|
|
160
|
+
* Never throws — a failed poll must not take down the sync loop.
|
|
161
|
+
*
|
|
162
|
+
* Does NOT itself check `WorkspaceSettings.decisionsLogEnabled` — see this
|
|
163
|
+
* module's own doc comment for why that gate belongs to the caller.
|
|
164
|
+
*/
|
|
165
|
+
export async function refreshDecisionsDigest(opts) {
|
|
166
|
+
const digest = await fetchDecisionsDigest(opts);
|
|
167
|
+
if (digest === null)
|
|
168
|
+
return null;
|
|
169
|
+
try {
|
|
170
|
+
const fullPath = path.join(opts.workspaceRoot, DECISIONS_LOG_RELATIVE_PATH);
|
|
171
|
+
await atomicWrite(fullPath, renderDecisionsMarkdown(digest.entries));
|
|
172
|
+
if (opts.harnesses.includes(HarnessType.CLAUDE_CODE)) {
|
|
173
|
+
const claudeMdPath = path.join(opts.workspaceRoot, HARNESS_FILES[HarnessType.CLAUDE_CODE]);
|
|
174
|
+
let existing = null;
|
|
175
|
+
try {
|
|
176
|
+
existing = await fs.readFile(claudeMdPath, 'utf-8');
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
existing = null;
|
|
180
|
+
}
|
|
181
|
+
if (existing !== null) {
|
|
182
|
+
const section = renderBoundedSection(digest.entries, 10);
|
|
183
|
+
const updated = injectBoundedSection(existing, section);
|
|
184
|
+
if (updated !== existing) {
|
|
185
|
+
await atomicWrite(claudeMdPath, updated);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
log.debug({ action: 'decisions_digest_skip_inject' }, 'claude-code config file not written yet this run — skipping bounded-section injection this cycle');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return { entriesWritten: digest.entries.length };
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
log.warn({ action: 'decisions_digest_write_failed', err }, 'Could not write decisions-log files');
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=decisionsDigest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decisionsDigest.js","sourceRoot":"","sources":["../../src/lib/decisionsDigest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* egressPolicy.ts — ships the workspace's central egress policy *to* the
|
|
3
|
+
* machine, so an admin sets `enforce`/`monitor` + an allow list once in the
|
|
4
|
+
* control plane rather than in every developer's local proxy config
|
|
5
|
+
* (LLD #63 §4). Mirrors `policySnapshot.ts` / `approvedBypasses.ts`: fetch from
|
|
6
|
+
* the control plane, write a local artifact under `.intutic/hooks` with
|
|
7
|
+
* integrity headers, and let the consumer (here the Rust proxy) read it.
|
|
8
|
+
*
|
|
9
|
+
* # The contract with the proxy
|
|
10
|
+
*
|
|
11
|
+
* `.intutic/hooks/egress-policy.json` — same directory as the policy snapshot
|
|
12
|
+
* and for the same reason: `.intutic/hooks` is in `UNIVERSAL_PROTECTED_PATHS`,
|
|
13
|
+
* so a gate already refuses an agent's own write there. The file carries its
|
|
14
|
+
* own integrity: `digest` (sha256 of a canonical `mode\nallow…` string, first
|
|
15
|
+
* 32 hex) and `workspace`. The proxy recomputes the digest and refuses a file
|
|
16
|
+
* whose digest or workspace does not match, keeping its config-derived policy
|
|
17
|
+
* instead — a corrupt or foreign file degrades to "local config", never to a
|
|
18
|
+
* weaker enforcement than the operator configured.
|
|
19
|
+
*
|
|
20
|
+
* `mode: null` means "central management not configured": the proxy keeps its
|
|
21
|
+
* local `intutic_settings.egress` / `INTUTIC_EGRESS_MODE`. A `mode` of
|
|
22
|
+
* `off`/`monitor`/`enforce` is authoritative for the mode; the `allow` list is
|
|
23
|
+
* UNIONed with the proxy's local allow entries so a developer's local infra
|
|
24
|
+
* allowances are never dropped.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
export declare const DEFAULT_EGRESS_CACHE_DIR: string;
|
|
29
|
+
export declare const EGRESS_POLICY_FILE = "egress-policy.json";
|
|
30
|
+
export type EgressMode = 'off' | 'monitor' | 'enforce';
|
|
31
|
+
/** The workspace egress policy, as `GET /api/v1/workspace/egress-policy` returns it. */
|
|
32
|
+
export interface WorkspaceEgressPolicy {
|
|
33
|
+
/** null → central management not configured; the proxy keeps local config. */
|
|
34
|
+
mode: EgressMode | null;
|
|
35
|
+
allow: string[];
|
|
36
|
+
}
|
|
37
|
+
export interface EgressPolicyOptions {
|
|
38
|
+
controlPlaneUrl: string;
|
|
39
|
+
apiKey: string;
|
|
40
|
+
workspaceId: string;
|
|
41
|
+
/** Override the directory (tests). */
|
|
42
|
+
cacheDir?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The canonical string the digest is computed over — identical on the daemon
|
|
46
|
+
* (here) and the proxy (Rust `load_local_egress_file`), so both derive the same
|
|
47
|
+
* digest. Mode (or empty) then each allow entry, newline-joined. Allow entries
|
|
48
|
+
* are hosts/CIDRs and never contain newlines.
|
|
49
|
+
*/
|
|
50
|
+
export declare function egressDigestInput(mode: EgressMode | null, allow: string[]): string;
|
|
51
|
+
/** Fetch this workspace's central egress policy. Returns null on any failure —
|
|
52
|
+
* the caller keeps the previous cache file rather than replacing it with
|
|
53
|
+
* nothing, the same rule the policy snapshot follows. */
|
|
54
|
+
export declare function fetchWorkspaceEgressPolicy(opts: EgressPolicyOptions): Promise<WorkspaceEgressPolicy | null>;
|
|
55
|
+
/** Write the cache file atomically, digest + workspace header included — same
|
|
56
|
+
* read-only-by-rename posture as the policy snapshot. Exported so a test can
|
|
57
|
+
* assert the on-disk shape directly. */
|
|
58
|
+
export declare function writeEgressPolicy(policy: WorkspaceEgressPolicy, workspaceId: string, cacheDir?: string): Promise<{
|
|
59
|
+
digest: string;
|
|
60
|
+
mode: EgressMode | null;
|
|
61
|
+
}>;
|
|
62
|
+
/** Fetch and write in one call. Safe to run on every sync cycle. Never throws —
|
|
63
|
+
* a failed poll must not take down the sync loop, and leaves the previous
|
|
64
|
+
* cache in place. */
|
|
65
|
+
export declare function refreshEgressPolicy(opts: EgressPolicyOptions): Promise<{
|
|
66
|
+
digest: string;
|
|
67
|
+
mode: EgressMode | null;
|
|
68
|
+
} | null>;
|
|
69
|
+
//# sourceMappingURL=egressPolicy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"egressPolicy.d.ts","sourceRoot":"","sources":["../../src/lib/egressPolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAUH,eAAO,MAAM,wBAAwB,QAA+C,CAAA;AACpF,eAAO,MAAM,kBAAkB,uBAAuB,CAAA;AAEtD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,CAAA;AAEtD,wFAAwF;AACxF,MAAM,WAAW,qBAAqB;IACpC,8EAA8E;IAC9E,IAAI,EAAE,UAAU,GAAG,IAAI,CAAA;IACvB,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,eAAe,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAaD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAElF;AAED;;0DAE0D;AAC1D,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CA0BvC;AAED;;yCAEyC;AACzC,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,qBAAqB,EAC7B,WAAW,EAAE,MAAM,EACnB,QAAQ,GAAE,MAAiC,GAC1C,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAA;CAAE,CAAC,CAuBtD;AAED;;sBAEsB;AACtB,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAAC,CAS7D"}
|