failproofai 1.0.0-beta.8 → 1.0.0-beta.9
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +9 -9
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +9 -9
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +10 -10
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1tkyag1._.js → [root-of-the-server]__0abann8._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ommekk._.js → [root-of-the-server]__0f9901b._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tyf0tc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_04mekdy._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0r-dsre._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0se484m._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0uz7o9y._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1fahg9e._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0i4l3_6._.js → _1t3vvw3._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1w8sola._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1w_6p9g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +15 -15
- package/.next/standalone/.next/static/chunks/{1zrdsxe91qq6i.js → 0jibhdbjojuq5.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3yn1y6ckypqwp.js → 0qubxfmqjdk8-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2047vah_v4emu.js → 159mxiauw9a8_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2bw0stk-s-1dd.js → 1xxzquhtvg72x.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3m8onpqrjxzu5.js → 270jpx60b2kuj.js} +1 -1
- package/.next/standalone/.next/static/chunks/{02vvywmww22zz.js → 2f295j8d9ta-h.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1fwh-2un_l-eo.js → 2g7p545cy5458.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2r_j5wrkbu5wm.js → 2nsm1u-n43hfa.js} +1 -1
- package/.next/standalone/.next/static/chunks/{23hl78dzffun4.js → 3940dugfmx1ai.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0_-focvo4cuiv.js → 3dpc4c0l_hf32.js} +1 -1
- package/.next/standalone/package.json +5 -5
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +66 -30
- package/dist/worker.mjs +1 -1
- package/package.json +5 -5
- package/src/hooks/collector-config.ts +18 -2
- package/src/hooks/configure-wizard.ts +49 -28
- package/src/hooks/fp-home.ts +23 -3
- package/src/hooks/fp-reset.ts +112 -5
- /package/.next/standalone/.next/static/{gOR8i03AeQm1VnIayDHVV → biwmgAwO6vVswC0z-soDU}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{gOR8i03AeQm1VnIayDHVV → biwmgAwO6vVswC0z-soDU}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{gOR8i03AeQm1VnIayDHVV → biwmgAwO6vVswC0z-soDU}/_ssgManifest.js +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -19,7 +19,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
19
19
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
20
|
|
|
21
21
|
// package.json
|
|
22
|
-
var version2 = "1.0.0-beta.
|
|
22
|
+
var version2 = "1.0.0-beta.9";
|
|
23
23
|
var init_package = () => {};
|
|
24
24
|
|
|
25
25
|
// src/posthog-key.ts
|
|
@@ -1007,7 +1007,8 @@ function resettablePaths() {
|
|
|
1007
1007
|
legacy.launcherMarker(),
|
|
1008
1008
|
legacy.lastVersion(),
|
|
1009
1009
|
legacy.auditDashboard(),
|
|
1010
|
-
legacy.
|
|
1010
|
+
legacy.auditCacheDir(),
|
|
1011
|
+
legacy.codexSessionPaths(),
|
|
1011
1012
|
legacy.spoolDir(),
|
|
1012
1013
|
legacy.failedDir(),
|
|
1013
1014
|
legacy.collectorHealth(),
|
|
@@ -1015,12 +1016,10 @@ function resettablePaths() {
|
|
|
1015
1016
|
legacy.cloudManagedPolicies(),
|
|
1016
1017
|
localPoliciesDir(),
|
|
1017
1018
|
cloudPoliciesDir(),
|
|
1018
|
-
at("cursors"),
|
|
1019
1019
|
at("state"),
|
|
1020
1020
|
configFile(),
|
|
1021
1021
|
credentialsFile(),
|
|
1022
1022
|
auditDir(),
|
|
1023
|
-
hookActivityDir(),
|
|
1024
1023
|
customAgentsDir(),
|
|
1025
1024
|
logsDir()
|
|
1026
1025
|
];
|
|
@@ -11955,11 +11954,12 @@ var init_handler = __esm(() => {
|
|
|
11955
11954
|
var exports_fp_reset = {};
|
|
11956
11955
|
__export(exports_fp_reset, {
|
|
11957
11956
|
resetHome: () => resetHome,
|
|
11957
|
+
migrateHookActivity: () => migrateHookActivity,
|
|
11958
11958
|
migrateConventionPolicies: () => migrateConventionPolicies,
|
|
11959
11959
|
layoutWarningForHook: () => layoutWarningForHook2,
|
|
11960
11960
|
checkLayoutForCli: () => checkLayoutForCli
|
|
11961
11961
|
});
|
|
11962
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync10, readdirSync as readdirSync13, renameSync as renameSync5, rmSync as rmSync6 } from "node:fs";
|
|
11962
|
+
import { copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync10, readdirSync as readdirSync13, renameSync as renameSync5, rmSync as rmSync6 } from "node:fs";
|
|
11963
11963
|
import { resolve as resolve15 } from "node:path";
|
|
11964
11964
|
function migrateConventionPolicies() {
|
|
11965
11965
|
const from = policiesDir();
|
|
@@ -11984,8 +11984,45 @@ function migrateConventionPolicies() {
|
|
|
11984
11984
|
} catch {}
|
|
11985
11985
|
return moved.sort((a, b) => a.localeCompare(b));
|
|
11986
11986
|
}
|
|
11987
|
+
function migrateHookActivity() {
|
|
11988
|
+
const from = legacy.hookActivityDir();
|
|
11989
|
+
if (!existsSync18(from))
|
|
11990
|
+
return [];
|
|
11991
|
+
const moved = [];
|
|
11992
|
+
try {
|
|
11993
|
+
const entries = readdirSync13(from, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl"));
|
|
11994
|
+
if (entries.length === 0)
|
|
11995
|
+
return [];
|
|
11996
|
+
const to = hookActivityDir();
|
|
11997
|
+
mkdirSync10(to, { recursive: true });
|
|
11998
|
+
const stamp2 = Date.now();
|
|
11999
|
+
let seq = 0;
|
|
12000
|
+
for (const entry of entries) {
|
|
12001
|
+
const source = resolve15(from, entry.name);
|
|
12002
|
+
let name = entry.name === "current.jsonl" ? `page-${stamp2}-${seq++}.jsonl` : entry.name;
|
|
12003
|
+
let target = resolve15(to, name);
|
|
12004
|
+
while (existsSync18(target)) {
|
|
12005
|
+
name = `page-${stamp2}-${seq++}.jsonl`;
|
|
12006
|
+
target = resolve15(to, name);
|
|
12007
|
+
}
|
|
12008
|
+
try {
|
|
12009
|
+
renameSync5(source, target);
|
|
12010
|
+
moved.push(name);
|
|
12011
|
+
} catch (err) {
|
|
12012
|
+
if (err?.code === "EXDEV") {
|
|
12013
|
+
try {
|
|
12014
|
+
copyFileSync(source, target);
|
|
12015
|
+
moved.push(name);
|
|
12016
|
+
} catch {}
|
|
12017
|
+
}
|
|
12018
|
+
}
|
|
12019
|
+
}
|
|
12020
|
+
} catch {}
|
|
12021
|
+
return moved.sort((a, b) => a.localeCompare(b));
|
|
12022
|
+
}
|
|
11987
12023
|
function resetHome(from) {
|
|
11988
12024
|
const migrated = migrateConventionPolicies();
|
|
12025
|
+
const activity = migrateHookActivity();
|
|
11989
12026
|
const removed = [];
|
|
11990
12027
|
for (const path3 of resettablePaths()) {
|
|
11991
12028
|
if (!existsSync18(path3))
|
|
@@ -11996,7 +12033,7 @@ function resetHome(from) {
|
|
|
11996
12033
|
} catch {}
|
|
11997
12034
|
}
|
|
11998
12035
|
writeVersionFile();
|
|
11999
|
-
return { removed, migrated, from };
|
|
12036
|
+
return { removed, migrated, activity, from };
|
|
12000
12037
|
}
|
|
12001
12038
|
async function checkLayoutForCli() {
|
|
12002
12039
|
const state = detectLayout();
|
|
@@ -12015,21 +12052,25 @@ async function checkLayoutForCli() {
|
|
|
12015
12052
|
};
|
|
12016
12053
|
}
|
|
12017
12054
|
if (state.kind === "stale") {
|
|
12018
|
-
const { removed, migrated } = resetHome(state.found);
|
|
12055
|
+
const { removed, migrated, activity } = resetHome(state.found);
|
|
12019
12056
|
return {
|
|
12020
12057
|
state,
|
|
12021
12058
|
fatal: false,
|
|
12022
12059
|
didReset: true,
|
|
12023
12060
|
lines: [
|
|
12024
12061
|
`failproofai reorganised ${failproofaiHome()} in this version.`,
|
|
12025
|
-
`Removed ${removed.length} item(s) from the old layout — policy config
|
|
12026
|
-
`
|
|
12062
|
+
`Removed ${removed.length} item(s) from the old layout — policy config and`,
|
|
12063
|
+
`audit cache. Your daemon binary and decision history were kept.`,
|
|
12027
12064
|
...migrated.length > 0 ? [
|
|
12028
12065
|
``,
|
|
12029
12066
|
`Kept your own policy file(s) and moved them to where this version`,
|
|
12030
12067
|
`loads them (${customPoliciesDir()}):`,
|
|
12031
12068
|
...migrated.map((name) => ` ${name}`)
|
|
12032
12069
|
] : [],
|
|
12070
|
+
...activity.length > 0 ? [
|
|
12071
|
+
``,
|
|
12072
|
+
`Carried ${activity.length} page(s) of decision history into ${hookActivityDir()}.`
|
|
12073
|
+
] : [],
|
|
12033
12074
|
``,
|
|
12034
12075
|
`Run \`failproofai config\` to set up again.`
|
|
12035
12076
|
]
|
|
@@ -12957,7 +12998,7 @@ async function validateIngestKey(cred, fetchImpl = fetch, timeoutMs = 1e4) {
|
|
|
12957
12998
|
clearTimeout(timer);
|
|
12958
12999
|
}
|
|
12959
13000
|
}
|
|
12960
|
-
var DEFAULT_INGEST_URL = "https://
|
|
13001
|
+
var DEFAULT_INGEST_URL = "https://app.befailproof.ai/v1/events";
|
|
12961
13002
|
var init_collector_config = __esm(() => {
|
|
12962
13003
|
init_fp_home();
|
|
12963
13004
|
init_fp_config();
|
|
@@ -26036,31 +26077,26 @@ refreshed. Re-run \`failproofai config\` once you can use sudo.
|
|
|
26036
26077
|
}
|
|
26037
26078
|
}
|
|
26038
26079
|
if (url === null) {
|
|
26039
|
-
const
|
|
26040
|
-
|
|
26041
|
-
|
|
26042
|
-
|
|
26043
|
-
|
|
26044
|
-
|
|
26045
|
-
|
|
26046
|
-
|
|
26047
|
-
|
|
26048
|
-
|
|
26049
|
-
});
|
|
26050
|
-
if (entered === null)
|
|
26051
|
-
return cancel();
|
|
26052
|
-
const validated = validateCloudUrl(cloudBaseFor(entered));
|
|
26053
|
-
if (!validated.ok) {
|
|
26080
|
+
const override = process.env.FAILPROOFAI_CLOUD_URL?.trim();
|
|
26081
|
+
if (override) {
|
|
26082
|
+
const validated = validateCloudUrl(cloudBaseFor(override));
|
|
26083
|
+
if (!validated.ok) {
|
|
26084
|
+
stdout.write(`
|
|
26085
|
+
FAILPROOFAI_CLOUD_URL is set to "${override}", which cannot be used: ${validated.reason}
|
|
26086
|
+
`);
|
|
26087
|
+
return cancel();
|
|
26088
|
+
}
|
|
26089
|
+
url = validated.url;
|
|
26054
26090
|
stdout.write(`
|
|
26055
|
-
${
|
|
26091
|
+
Using ${url} (from FAILPROOFAI_CLOUD_URL).
|
|
26056
26092
|
`);
|
|
26057
|
-
|
|
26093
|
+
} else {
|
|
26094
|
+
url = cloudBaseFor(DEFAULT_INGEST_URL);
|
|
26058
26095
|
}
|
|
26059
|
-
url = validated.url;
|
|
26060
26096
|
}
|
|
26061
26097
|
if (token === null) {
|
|
26062
26098
|
token = await promptText({
|
|
26063
|
-
message:
|
|
26099
|
+
message: `API key for ${new URL(url).host}`,
|
|
26064
26100
|
hint: "needs events:add · policies:pull enables managed policy too",
|
|
26065
26101
|
mask: true,
|
|
26066
26102
|
validate: (v) => v.length >= 8 ? null : "that looks too short to be a key",
|
|
@@ -28751,7 +28787,7 @@ import { realpathSync as realpathSync6 } from "fs";
|
|
|
28751
28787
|
import { dirname as dirname17, resolve as resolve26 } from "path";
|
|
28752
28788
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
28753
28789
|
// package.json
|
|
28754
|
-
var version = "1.0.0-beta.
|
|
28790
|
+
var version = "1.0.0-beta.9";
|
|
28755
28791
|
|
|
28756
28792
|
// bin/failproofai.mjs
|
|
28757
28793
|
if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
|
package/dist/worker.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.9",
|
|
4
4
|
"description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs",
|
|
@@ -114,9 +114,9 @@
|
|
|
114
114
|
"sharp": "0.35.0"
|
|
115
115
|
},
|
|
116
116
|
"optionalDependencies": {
|
|
117
|
-
"@failproofai/failproofaid-linux-x64": "1.0.0-beta.
|
|
118
|
-
"@failproofai/failproofaid-linux-arm64": "1.0.0-beta.
|
|
119
|
-
"@failproofai/failproofaid-darwin-x64": "1.0.0-beta.
|
|
120
|
-
"@failproofai/failproofaid-darwin-arm64": "1.0.0-beta.
|
|
117
|
+
"@failproofai/failproofaid-linux-x64": "1.0.0-beta.9",
|
|
118
|
+
"@failproofai/failproofaid-linux-arm64": "1.0.0-beta.9",
|
|
119
|
+
"@failproofai/failproofaid-darwin-x64": "1.0.0-beta.9",
|
|
120
|
+
"@failproofai/failproofaid-darwin-arm64": "1.0.0-beta.9"
|
|
121
121
|
}
|
|
122
122
|
}
|
|
@@ -31,8 +31,24 @@ import { readHooksConfig, writeHooksConfig } from "./hooks-config";
|
|
|
31
31
|
import { credentialsFile, failproofaiHome as layoutHome } from "./fp-home";
|
|
32
32
|
import { readCredentials, writeCredentials, updateConfig } from "./fp-config";
|
|
33
33
|
|
|
34
|
-
/**
|
|
35
|
-
|
|
34
|
+
/**
|
|
35
|
+
* The hosted ingest endpoint — a COMPLETE endpoint, not a base to join onto.
|
|
36
|
+
*
|
|
37
|
+
* The DASHBOARD hostname, not the API server's. On the hosted deployment a
|
|
38
|
+
* reverse proxy routes `/v1/*` and `/enforcement/v1/*` to the server and
|
|
39
|
+
* everything else to the Next.js app, so this path already reaches ingest —
|
|
40
|
+
* the server hostname is not needed to get there and does not need to be a
|
|
41
|
+
* public name for the product to work.
|
|
42
|
+
*
|
|
43
|
+
* One hostname is also what makes `--connect <origin>` mean one thing. Ingest,
|
|
44
|
+
* key introspection (`/v1/auth/introspect`) and cloud-managed policy
|
|
45
|
+
* (`/enforcement/v1/*`) all resolve against the same origin someone pastes,
|
|
46
|
+
* which is the origin they already have in their browser.
|
|
47
|
+
*
|
|
48
|
+
* MUST stay byte-identical to `DEFAULT_INGEST_URL` in
|
|
49
|
+
* `crates/fpai-collect/src/config.rs` — see that copy for why.
|
|
50
|
+
*/
|
|
51
|
+
export const DEFAULT_INGEST_URL = "https://app.befailproof.ai/v1/events";
|
|
36
52
|
|
|
37
53
|
export interface IngestCredential {
|
|
38
54
|
url: string;
|
|
@@ -1021,39 +1021,60 @@ export async function runConfigureWizard(io: WizardIO = {}): Promise<WizardResul
|
|
|
1021
1021
|
}
|
|
1022
1022
|
|
|
1023
1023
|
if (url === null) {
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
//
|
|
1043
|
-
//
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1024
|
+
// NOT asked for. There is exactly one right answer for everybody using
|
|
1025
|
+
// the hosted product, and asking made it look like a decision — which
|
|
1026
|
+
// is how a key ends up pasted into the URL field, and how someone
|
|
1027
|
+
// reasonably types the dashboard's own address and gets a 404 from a
|
|
1028
|
+
// web app that is not the ingest endpoint. Both are real, both happened
|
|
1029
|
+
// within ten minutes of each other, and neither is a mistake the person
|
|
1030
|
+
// making it can be expected to avoid: "Failproof Cloud URL" has no
|
|
1031
|
+
// knowable answer other than the default it was already showing.
|
|
1032
|
+
//
|
|
1033
|
+
// The two audiences that genuinely need a different endpoint keep an
|
|
1034
|
+
// explicit way to say so, and neither is an interactive prompt:
|
|
1035
|
+
//
|
|
1036
|
+
// • local development / self-hosting → FAILPROOFAI_CLOUD_URL, the
|
|
1037
|
+
// same variable the DAEMON already reads for cloud-managed policy
|
|
1038
|
+
// (crates/failproofaid/src/cloud_client.rs), so one export points
|
|
1039
|
+
// the whole machine at one place instead of the wizard and the
|
|
1040
|
+
// daemon disagreeing.
|
|
1041
|
+
// • scripted installs → `failproofai config --connect <url> --token`,
|
|
1042
|
+
// unchanged.
|
|
1043
|
+
//
|
|
1044
|
+
// The env value goes through the SAME `validateCloudUrl` a typed one
|
|
1045
|
+
// did — it is not a trusted back door. http stays loopback-only, so a
|
|
1046
|
+
// bearer token still cannot be exported onto the wire in clear by
|
|
1047
|
+
// setting a variable.
|
|
1048
|
+
const override = process.env.FAILPROOFAI_CLOUD_URL?.trim();
|
|
1049
|
+
if (override) {
|
|
1050
|
+
const validated = validateCloudUrl(cloudBaseFor(override));
|
|
1051
|
+
if (!validated.ok) {
|
|
1052
|
+
// Loud, not silent-fallback-to-hosted: someone who exported this
|
|
1053
|
+
// wants THAT endpoint, and quietly reporting a machine to the
|
|
1054
|
+
// hosted service instead is the one outcome they did not ask for.
|
|
1055
|
+
stdout.write(
|
|
1056
|
+
`\nFAILPROOFAI_CLOUD_URL is set to "${override}", which cannot be used: ` +
|
|
1057
|
+
`${validated.reason}\n`,
|
|
1058
|
+
);
|
|
1059
|
+
return cancel();
|
|
1060
|
+
}
|
|
1061
|
+
url = validated.url;
|
|
1062
|
+
// Named on screen, because an env var is invisible at the moment it
|
|
1063
|
+
// matters and a machine reporting somewhere unexpected is exactly the
|
|
1064
|
+
// thing nobody notices until they go looking for data that is not
|
|
1065
|
+
// there.
|
|
1066
|
+
stdout.write(`\nUsing ${url} (from FAILPROOFAI_CLOUD_URL).\n`);
|
|
1067
|
+
} else {
|
|
1068
|
+
url = cloudBaseFor(DEFAULT_INGEST_URL);
|
|
1050
1069
|
}
|
|
1051
|
-
url = validated.url;
|
|
1052
1070
|
}
|
|
1053
1071
|
|
|
1054
1072
|
if (token === null) {
|
|
1055
1073
|
token = await promptText({
|
|
1056
|
-
|
|
1074
|
+
// The destination is in the question now that it is no longer a
|
|
1075
|
+
// question of its own. It is the only remaining place a person can
|
|
1076
|
+
// notice they are about to send a key somewhere they did not mean.
|
|
1077
|
+
message: `API key for ${new URL(url).host}`,
|
|
1057
1078
|
hint: "needs events:add · policies:pull enables managed policy too",
|
|
1058
1079
|
// Masked: setup is routinely run while screen-sharing, and a pasted
|
|
1059
1080
|
// key would otherwise sit in the scrollback of every recording.
|
package/src/hooks/fp-home.ts
CHANGED
|
@@ -312,7 +312,15 @@ export function resettablePaths(): string[] {
|
|
|
312
312
|
legacy.launcherMarker(),
|
|
313
313
|
legacy.lastVersion(),
|
|
314
314
|
legacy.auditDashboard(),
|
|
315
|
-
legacy.cacheDir(),
|
|
315
|
+
// `legacy.cacheDir()` — the whole of `cache/` — stood here, and it CONTAINS
|
|
316
|
+
// `cache/hook-activity`: layout 1's decision log. Deleting the parent threw
|
|
317
|
+
// away every decision the machine had ever recorded, which is the data the
|
|
318
|
+
// dashboard's activity tab exists to show. Its children are now named
|
|
319
|
+
// individually so the log can be carried across by
|
|
320
|
+
// `migrateHookActivity()`, and everything else in `cache/` still goes —
|
|
321
|
+
// both remaining entries are re-derived on demand.
|
|
322
|
+
legacy.auditCacheDir(),
|
|
323
|
+
legacy.codexSessionPaths(),
|
|
316
324
|
legacy.spoolDir(),
|
|
317
325
|
legacy.failedDir(),
|
|
318
326
|
legacy.collectorHealth(),
|
|
@@ -335,7 +343,14 @@ export function resettablePaths(): string[] {
|
|
|
335
343
|
// setup, `cloud-policies/` by the next daemon poll.
|
|
336
344
|
localPoliciesDir(),
|
|
337
345
|
cloudPoliciesDir(),
|
|
338
|
-
at("cursors"),
|
|
346
|
+
// `at("cursors")` is deliberately NOT here, and it is load-bearing for the
|
|
347
|
+
// activity migration above rather than a separate opinion. Cursors are keyed
|
|
348
|
+
// on `(device, inode)`; `migrateHookActivity()` MOVES pages, which preserves
|
|
349
|
+
// the inode, so a surviving cursor still points at the file it belongs to
|
|
350
|
+
// and resumes at the right offset. Delete the cursors and every carried page
|
|
351
|
+
// reads as new and re-ships in full — which is the outcome the move exists
|
|
352
|
+
// to avoid. A cursor whose file is gone is inert (`retain_existing` drops
|
|
353
|
+
// it), so keeping them costs nothing when there is nothing to resume.
|
|
339
354
|
at("state"),
|
|
340
355
|
// Layout 2.
|
|
341
356
|
// NOT versionFile() — it is the layout marker, not user data, and the
|
|
@@ -344,7 +359,12 @@ export function resettablePaths(): string[] {
|
|
|
344
359
|
configFile(),
|
|
345
360
|
credentialsFile(),
|
|
346
361
|
auditDir(),
|
|
347
|
-
hookActivityDir()
|
|
362
|
+
// NOT hookActivityDir(). It is the DESTINATION `migrateHookActivity()` has
|
|
363
|
+
// just moved layout 1's log into, so listing it here deleted the migration
|
|
364
|
+
// moments after it happened — the reset runs every path in this list after
|
|
365
|
+
// the migrations. It is also the one directory here that is neither derived
|
|
366
|
+
// nor re-fetchable: a decision log is the record of what this machine did,
|
|
367
|
+
// and nothing regenerates it.
|
|
348
368
|
customAgentsDir(),
|
|
349
369
|
logsDir(),
|
|
350
370
|
// NOT bin/ — a downloaded daemon binary is large, version-pinned and
|
package/src/hooks/fp-reset.ts
CHANGED
|
@@ -29,12 +29,14 @@
|
|
|
29
29
|
* user out of their agent entirely, with no way back except hand-editing JSON.
|
|
30
30
|
* A loud warning that survives until setup runs is the proportionate answer.
|
|
31
31
|
*/
|
|
32
|
-
import { existsSync, mkdirSync, readdirSync, renameSync, rmSync } from "node:fs";
|
|
32
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, renameSync, rmSync } from "node:fs";
|
|
33
33
|
import { resolve } from "node:path";
|
|
34
34
|
import {
|
|
35
35
|
LAYOUT_VERSION,
|
|
36
36
|
customPoliciesDir,
|
|
37
37
|
failproofaiHome,
|
|
38
|
+
hookActivityDir,
|
|
39
|
+
legacy,
|
|
38
40
|
policiesDir,
|
|
39
41
|
resettablePaths,
|
|
40
42
|
} from "./fp-home";
|
|
@@ -52,6 +54,8 @@ export interface ResetOutcome {
|
|
|
52
54
|
removed: string[];
|
|
53
55
|
/** Basenames of user-authored policy files moved into `custom-policies/`. */
|
|
54
56
|
migrated: string[];
|
|
57
|
+
/** Basenames of decision-log pages carried into layout 2's `hook-activity/`. */
|
|
58
|
+
activity: string[];
|
|
55
59
|
/** The layout that was found before the reset. */
|
|
56
60
|
from: number;
|
|
57
61
|
}
|
|
@@ -110,6 +114,94 @@ export function migrateConventionPolicies(): string[] {
|
|
|
110
114
|
return moved.sort((a, b) => a.localeCompare(b));
|
|
111
115
|
}
|
|
112
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Carry layout 1's decision log into layout 2, WITHOUT re-shipping it.
|
|
119
|
+
*
|
|
120
|
+
* `cache/hook-activity` was deleted along with the rest of `cache/`, so an
|
|
121
|
+
* upgrade silently discarded every decision the machine had ever recorded —
|
|
122
|
+
* the data the dashboard's activity tab exists to show, and on a connected
|
|
123
|
+
* machine the data an operator had already been billed the collection of.
|
|
124
|
+
*
|
|
125
|
+
* # Why this MOVES rather than copies
|
|
126
|
+
*
|
|
127
|
+
* The collector keys its cursors on `(device, inode)` — deliberately, because
|
|
128
|
+
* the store rotates by RENAMING `current.jsonl`, and a path-keyed cursor would
|
|
129
|
+
* both re-ship the rotated file and carry its offset onto the fresh one.
|
|
130
|
+
* `rename()` preserves the inode, so a moved page is still the file its cursor
|
|
131
|
+
* belongs to and resumes at the right offset. A copy gives every page a NEW
|
|
132
|
+
* inode, which reads as "never seen before" and re-ships all of it.
|
|
133
|
+
*
|
|
134
|
+
* `head_fingerprint` is what makes that safe rather than lucky: the cursor
|
|
135
|
+
* verifies the file's first bytes whenever a resumed cursor's path has changed,
|
|
136
|
+
* which is exactly this situation. It was added for inode REUSE; a migration is
|
|
137
|
+
* the same question asked the other way round.
|
|
138
|
+
*
|
|
139
|
+
* The fallback for `EXDEV` (a `cache/` on a different filesystem — rare, since
|
|
140
|
+
* both live under one home, but possible with bind mounts) is a copy, accepting
|
|
141
|
+
* that those pages re-ship. Ingest dedups on a content hash and collapses them
|
|
142
|
+
* at merge, so the cost is bandwidth, not duplicate rows.
|
|
143
|
+
*
|
|
144
|
+
* # What is deliberately NOT carried
|
|
145
|
+
*
|
|
146
|
+
* `current.count` and `stats.json` are derived state, and two of each cannot be
|
|
147
|
+
* merged without inventing a number. They are dropped and the store rebuilds
|
|
148
|
+
* them. The legacy `current.jsonl` is moved under a PAGE name rather than onto
|
|
149
|
+
* the destination's own `current.jsonl`, which may already exist and may be
|
|
150
|
+
* mid-write — a rotated page is exactly what the store would have made of it.
|
|
151
|
+
*/
|
|
152
|
+
export function migrateHookActivity(): string[] {
|
|
153
|
+
const from = legacy.hookActivityDir();
|
|
154
|
+
if (!existsSync(from)) return [];
|
|
155
|
+
const moved: string[] = [];
|
|
156
|
+
try {
|
|
157
|
+
const entries = readdirSync(from, { withFileTypes: true }).filter(
|
|
158
|
+
(e) => e.isFile() && e.name.endsWith(".jsonl"),
|
|
159
|
+
);
|
|
160
|
+
if (entries.length === 0) return [];
|
|
161
|
+
|
|
162
|
+
const to = hookActivityDir();
|
|
163
|
+
mkdirSync(to, { recursive: true });
|
|
164
|
+
|
|
165
|
+
// One timestamp for the whole migration, with a per-file counter, so the
|
|
166
|
+
// names are stable, ordered, and cannot collide with each other.
|
|
167
|
+
const stamp = Date.now();
|
|
168
|
+
let seq = 0;
|
|
169
|
+
|
|
170
|
+
for (const entry of entries) {
|
|
171
|
+
const source = resolve(from, entry.name);
|
|
172
|
+
// `current.jsonl` becomes a page: the destination has its own, and the
|
|
173
|
+
// store's reader treats pages and current identically.
|
|
174
|
+
let name = entry.name === "current.jsonl" ? `page-${stamp}-${seq++}.jsonl` : entry.name;
|
|
175
|
+
let target = resolve(to, name);
|
|
176
|
+
// Never overwrite. A same-named page in the destination is a different
|
|
177
|
+
// file with different records, and losing either is worse than a rename.
|
|
178
|
+
while (existsSync(target)) {
|
|
179
|
+
name = `page-${stamp}-${seq++}.jsonl`;
|
|
180
|
+
target = resolve(to, name);
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
renameSync(source, target);
|
|
184
|
+
moved.push(name);
|
|
185
|
+
} catch (err) {
|
|
186
|
+
// EXDEV: a rename across filesystems. Copy instead and accept the
|
|
187
|
+
// re-ship; a page left behind would be data lost.
|
|
188
|
+
if ((err as NodeJS.ErrnoException)?.code === "EXDEV") {
|
|
189
|
+
try {
|
|
190
|
+
copyFileSync(source, target);
|
|
191
|
+
moved.push(name);
|
|
192
|
+
} catch {
|
|
193
|
+
// Unreadable or undeletable — leave it where it is. `cache/` is no
|
|
194
|
+
// longer deleted wholesale, so "left behind" means "still there".
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
// An activity directory we cannot read is not worth aborting a reset over.
|
|
201
|
+
}
|
|
202
|
+
return moved.sort((a, b) => a.localeCompare(b));
|
|
203
|
+
}
|
|
204
|
+
|
|
113
205
|
/**
|
|
114
206
|
* Delete every path layout 1 or layout 2 could have written, then stamp
|
|
115
207
|
* VERSION so the next run reads as current.
|
|
@@ -124,6 +216,7 @@ export function resetHome(from: number): ResetOutcome {
|
|
|
124
216
|
// BEFORE the deletions, so a file that is mid-move is never one the reset
|
|
125
217
|
// then walks over.
|
|
126
218
|
const migrated = migrateConventionPolicies();
|
|
219
|
+
const activity = migrateHookActivity();
|
|
127
220
|
const removed: string[] = [];
|
|
128
221
|
for (const path of resettablePaths()) {
|
|
129
222
|
if (!existsSync(path)) continue;
|
|
@@ -137,7 +230,7 @@ export function resetHome(from: number): ResetOutcome {
|
|
|
137
230
|
}
|
|
138
231
|
}
|
|
139
232
|
writeVersionFile();
|
|
140
|
-
return { removed, migrated, from };
|
|
233
|
+
return { removed, migrated, activity, from };
|
|
141
234
|
}
|
|
142
235
|
|
|
143
236
|
export interface LayoutCheck {
|
|
@@ -187,15 +280,19 @@ export async function checkLayoutForCli(): Promise<LayoutCheck> {
|
|
|
187
280
|
}
|
|
188
281
|
|
|
189
282
|
if (state.kind === "stale") {
|
|
190
|
-
const { removed, migrated } = resetHome(state.found);
|
|
283
|
+
const { removed, migrated, activity } = resetHome(state.found);
|
|
191
284
|
return {
|
|
192
285
|
state,
|
|
193
286
|
fatal: false,
|
|
194
287
|
didReset: true,
|
|
195
288
|
lines: [
|
|
196
289
|
`failproofai reorganised ${failproofaiHome()} in this version.`,
|
|
197
|
-
|
|
198
|
-
|
|
290
|
+
// "activity history" was in this sentence while the reset was deleting
|
|
291
|
+
// it. It is carried over now, so the message says what was KEPT as well
|
|
292
|
+
// as what went — a user who reads "removed" and nothing else has no way
|
|
293
|
+
// to know their decision log survived.
|
|
294
|
+
`Removed ${removed.length} item(s) from the old layout — policy config and`,
|
|
295
|
+
`audit cache. Your daemon binary and decision history were kept.`,
|
|
199
296
|
// Named individually rather than counted. These are files a person
|
|
200
297
|
// wrote; "moved 3 items" is not something you can check at a glance,
|
|
201
298
|
// and the whole point of saying it is that they can.
|
|
@@ -207,6 +304,16 @@ export async function checkLayoutForCli(): Promise<LayoutCheck> {
|
|
|
207
304
|
...migrated.map((name) => ` ${name}`),
|
|
208
305
|
]
|
|
209
306
|
: []),
|
|
307
|
+
// Counted, not named. Unlike policy files these are machine-written
|
|
308
|
+
// pages with generated names — a list of them tells the reader nothing
|
|
309
|
+
// they could act on, where the COUNT answers the only question they
|
|
310
|
+
// have: did my history survive.
|
|
311
|
+
...(activity.length > 0
|
|
312
|
+
? [
|
|
313
|
+
``,
|
|
314
|
+
`Carried ${activity.length} page(s) of decision history into ${hookActivityDir()}.`,
|
|
315
|
+
]
|
|
316
|
+
: []),
|
|
210
317
|
``,
|
|
211
318
|
`Run \`failproofai config\` to set up again.`,
|
|
212
319
|
],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|