failproofai 1.0.7-beta.2 → 1.0.8-beta.0
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 +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- 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 +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +5 -5
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +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 +4 -4
- 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 +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- 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 +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +6 -7
- 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 +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 +18 -7
- package/.next/standalone/.next/server/app/settings/page.js +9 -7
- 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/{[externals]__1j-zsg5._.js → [externals]__20kzpkf._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +22 -0
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +20 -20
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +9 -9
- package/.next/standalone/.next/server/chunks/_1q5i8mb._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_16xa545._.js +3 -0
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +5 -0
- package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[externals]__0ohnuzs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qxnccm._.js → [root-of-the-server]__06pflha._.js} +2 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0soxz2z._.js → [root-of-the-server]__12e7nhs._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0l44ual._.js → [root-of-the-server]__14o3ek1._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19evfi8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1v-jvrv._.js → _0o4xkpl._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1mel6y1._.js → _1-7sqrb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +13 -13
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.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/chunks/ssr/app_settings_02tf1h4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0aiy-os._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0-q0umm._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9d._.js +12 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0g194sy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +12 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1kx9e0d._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +3 -0
- 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 +33 -22
- package/.next/standalone/.next/static/chunks/078gnymqoh3r4.js +1 -0
- package/.next/standalone/.next/static/chunks/{3brze37td_wnc.js → 0ahfwmbkpgfw4.js} +1 -1
- package/.next/standalone/.next/static/chunks/{043j99m8ykg__.css → 0bhidk90e-07f.css} +1 -1
- package/.next/standalone/.next/static/chunks/1bu2-nv59ed6i.js +1 -0
- package/.next/standalone/.next/static/chunks/{3ugmd_7dyn0id.js → 1v_tp3hm8wyhe.js} +1 -1
- package/.next/standalone/.next/static/chunks/2a405_e1o26ol.js +1 -0
- package/.next/standalone/.next/static/chunks/{3yxro_r2_o9ad.js → 2vo7qbdbvnc0o.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0fqd7m_u81mi5.js → 2z7i-yg59w4pn.js} +1 -1
- package/.next/standalone/.next/static/chunks/36uhh9el_oz8e.js +1 -0
- package/.next/standalone/.next/static/chunks/{129ag2bw93bdh.js → 3bgot5v6f6s3c.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3otmypm6j_xfo.js → 3o3f1ibfci0p7.js} +2 -2
- package/.next/standalone/SECURITY.md +53 -0
- package/.next/standalone/app/actions/get-jev-config.ts +194 -14
- package/.next/standalone/app/actions/update-jev-config.ts +160 -11
- package/.next/standalone/app/policies/hooks-client.tsx +5 -3
- package/.next/standalone/app/settings/jev-panel.tsx +169 -12
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +22 -4
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +14 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +17 -3
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +3 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +22 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +169 -0
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +65 -0
- package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +61 -0
- package/.next/standalone/package.json +9 -9
- package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
- package/.next/standalone/sdk/typescript/CHANGELOG.md +15 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +10 -30
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +3 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -16
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +3 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +13 -132
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -142
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +1396 -1016
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +9 -0
- package/.next/standalone/sdk/typescript/scripts/release.mjs +30 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +1 -0
- package/bin/failproofai.mjs +37 -6
- package/dist/cli.mjs +2861 -1145
- package/dist/worker.mjs +815 -197
- package/package.json +9 -9
- package/src/hooks/builtin-policies.ts +24 -6
- package/src/hooks/cloud-connection.ts +190 -1
- package/src/hooks/cloud-enrollment-cli.ts +56 -7
- package/src/hooks/cloud-introspect.ts +6 -0
- package/src/hooks/configure-wizard.ts +26 -6
- package/src/hooks/custom-hooks-loader.ts +58 -8
- package/src/hooks/effective-reviewers.ts +172 -8
- package/src/hooks/flush-cli.ts +35 -8
- package/src/hooks/fp-config.ts +266 -1
- package/src/hooks/fp-reset.ts +22 -4
- package/src/hooks/handler.ts +44 -7
- package/src/hooks/hook-activity-store.ts +13 -5
- package/src/hooks/jev-cli.ts +1009 -86
- package/src/hooks/jev-cloud-connection.ts +392 -0
- package/src/hooks/manager.ts +44 -6
- package/src/hooks/pack-cli.ts +175 -37
- package/src/hooks/pack-failclosed.ts +3 -0
- package/src/hooks/pack-manifest.ts +11 -1
- package/src/hooks/pack-store.ts +47 -10
- package/src/hooks/policy-authority.ts +73 -20
- package/src/hooks/policy-catalog.ts +10 -8
- package/src/hooks/policy-evaluator.ts +31 -8
- package/src/hooks/policy-registry.ts +3 -2
- package/src/hooks/policy-reviewability.ts +41 -12
- package/src/hooks/policy-types.ts +5 -3
- package/src/hooks/semantic/combine.ts +124 -84
- package/src/hooks/semantic/decide.ts +16 -0
- package/src/hooks/semantic/envelope.ts +18 -59
- package/src/hooks/semantic/evaluator.ts +11 -2
- package/src/hooks/semantic/facts.ts +7 -1
- package/src/hooks/semantic/intent.ts +8 -8
- package/src/hooks/semantic/jev-client.ts +540 -28
- package/src/hooks/semantic/jev-config.ts +586 -38
- package/src/hooks/semantic/jev-review.ts +26 -11
- package/src/hooks/semantic/jev-throttle.ts +8 -2
- package/src/hooks/semantic/pack-policies.ts +58 -15
- package/src/hooks/semantic/policies.ts +4 -3
- package/src/hooks/semantic/redact.ts +28 -1
- package/src/hooks/semantic/session-root.ts +116 -0
- package/src/hooks/semantic/types.ts +11 -0
- package/src/hooks/tui.ts +4 -0
- package/src/hooks/worker-server.ts +2 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bf34x4._.js +0 -22
- package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +0 -5
- package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/_1w_5l7t._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +0 -12
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +0 -3
- package/.next/standalone/.next/static/chunks/1qd741hzlmjbo.js +0 -1
- package/.next/standalone/.next/static/chunks/2_pltstd8-xgs.js +0 -1
- package/.next/standalone/.next/static/chunks/2c8j9l6j_b1ci.js +0 -1
- package/.next/standalone/.next/static/chunks/3-k569wzcli8q.js +0 -1
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_ssgManifest.js +0 -0
package/src/hooks/pack-cli.ts
CHANGED
|
@@ -21,12 +21,12 @@ import {
|
|
|
21
21
|
readInstalledPacks,
|
|
22
22
|
type SemanticManifestEntry,
|
|
23
23
|
} from "./pack-manifest";
|
|
24
|
-
import { parseSemver } from "./semver-precedence";
|
|
24
|
+
import { compareVersions, parseSemver } from "./semver-precedence";
|
|
25
25
|
// The build step measures a pack's question set against the envelope's own
|
|
26
26
|
// budget, which means reaching the semantic side. This is a CLI module — loaded
|
|
27
27
|
// by `failproofai publish`, never by a hook — so the rule that keeps those
|
|
28
28
|
// modules off an unconfigured machine's hook path does not apply here.
|
|
29
|
-
import { MAX_PACK_QUESTION_CHARS, questionChars } from "./semantic/pack-policies";
|
|
29
|
+
import { BUILTIN_QUESTION_CHARS, MAX_PACK_QUESTION_CHARS, questionChars, semanticPoliciesFromPacks } from "./semantic/pack-policies";
|
|
30
30
|
import {
|
|
31
31
|
AmbiguousPackId,
|
|
32
32
|
PACK_CHECKSUMS_ASSET,
|
|
@@ -40,10 +40,12 @@ import {
|
|
|
40
40
|
removePack,
|
|
41
41
|
setPackPolicyEnabled,
|
|
42
42
|
slugifyCategory,
|
|
43
|
+
type PackPreview,
|
|
43
44
|
} from "./pack-store";
|
|
44
45
|
import type { PolicyEffect } from "./cloud-managed-policies";
|
|
45
46
|
import { loadCustomHooks } from "./custom-hooks-loader";
|
|
46
47
|
import { getSemanticRegistrations } from "./custom-hooks-registry";
|
|
48
|
+
import { isFirstPartyPack, isReservedClaim, jevPacks } from "./effective-reviewers";
|
|
47
49
|
import { authorityFieldsOf, authorityProblem, resolvePolicyAuthority } from "./policy-authority";
|
|
48
50
|
import type { PolicyCatalogEntry, SemanticPolicyDeclaration } from "./policy-types";
|
|
49
51
|
import type { MultiChoice, RenderOpts, TTYIn, TTYOut } from "./tui";
|
|
@@ -80,8 +82,20 @@ export interface PackBuildMeta {
|
|
|
80
82
|
policies: number;
|
|
81
83
|
defaultOn: number;
|
|
82
84
|
commit?: string;
|
|
85
|
+
/** Jev checks and no regex half: `publish` repeats {@link JEV_ONLY_ROLLBACK_REMINDER}. */
|
|
86
|
+
semanticOnly?: boolean;
|
|
83
87
|
}
|
|
84
88
|
|
|
89
|
+
/**
|
|
90
|
+
* A build from before `semanticOnly` refuses such a pack as blanket-deny, so its
|
|
91
|
+
* author has to tell users. Shared by build's lines and publish's success
|
|
92
|
+
* message, which is the only one an author actually reads.
|
|
93
|
+
*/
|
|
94
|
+
const JEV_ONLY_ROLLBACK_REMINDER = [
|
|
95
|
+
" Jev checks alone: tell users to remove it before rolling a machine back to an older failproofai,",
|
|
96
|
+
" which can deny every tool call over a pack it will not load (see the publish-a-pack docs).",
|
|
97
|
+
];
|
|
98
|
+
|
|
85
99
|
const ok = (lines: string[], meta?: PackBuildMeta): PackCliResult => ({
|
|
86
100
|
lines,
|
|
87
101
|
exitCode: 0,
|
|
@@ -89,12 +103,30 @@ const ok = (lines: string[], meta?: PackBuildMeta): PackCliResult => ({
|
|
|
89
103
|
});
|
|
90
104
|
const fail = (lines: string[]): PackCliResult => ({ lines, exitCode: 1 });
|
|
91
105
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
106
|
+
/**
|
|
107
|
+
* The first failproofai that runs a pack's Jev checks as published: 1.0.7
|
|
108
|
+
* ignores `semantic` (and `minCliVersion`), and 1.0.7-beta.x replaces the
|
|
109
|
+
* built-in checks with a pack's. A pack with checks is held to at least this.
|
|
110
|
+
*/
|
|
111
|
+
const JEV_PACK_MIN_CLI = "1.0.8-beta.0";
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Every occurrence of any of `flags`, comma-split and de-duplicated: a repeated
|
|
115
|
+
* flag adds to the list rather than being silently dropped. `[]` when any
|
|
116
|
+
* occurrence has no value (callers refuse that), undefined when none appears.
|
|
117
|
+
*/
|
|
118
|
+
function parseList(rest: string[], ...flags: string[]): string[] | undefined {
|
|
119
|
+
let found = false;
|
|
120
|
+
const out: string[] = [];
|
|
121
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
122
|
+
const flag = flags.find((f) => rest[i] === f || rest[i].startsWith(`${f}=`));
|
|
123
|
+
if (!flag) continue;
|
|
124
|
+
found = true;
|
|
125
|
+
const raw = rest[i] === flag ? rest[i + 1] : rest[i].slice(flag.length + 1);
|
|
126
|
+
if (!raw || raw.startsWith("--")) return [];
|
|
127
|
+
out.push(...raw.split(",").map((s) => s.trim()).filter(Boolean));
|
|
128
|
+
}
|
|
129
|
+
return found ? [...new Set(out)] : undefined;
|
|
98
130
|
}
|
|
99
131
|
|
|
100
132
|
/**
|
|
@@ -184,17 +216,25 @@ export function packAddSource(rest: string[]): string | undefined {
|
|
|
184
216
|
* a third would be the one that drifts.
|
|
185
217
|
*/
|
|
186
218
|
function parseCliList(rest: string[]): { clis?: string[] } | { error: string[] } {
|
|
187
|
-
|
|
188
|
-
|
|
219
|
+
// Every `--cli`, not just the first: a repeat adds agents. One that names
|
|
220
|
+
// none still refuses the whole selection, below.
|
|
189
221
|
const values: string[] = [];
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
222
|
+
let found = false;
|
|
223
|
+
let emptyOccurrence = false;
|
|
224
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
225
|
+
const before = values.length;
|
|
226
|
+
if (rest[i].startsWith("--cli=")) {
|
|
227
|
+
values.push(...rest[i].slice("--cli=".length).split(",").filter((v) => v.trim()));
|
|
228
|
+
} else if (rest[i] === "--cli") {
|
|
229
|
+
for (let j = i + 1; j < rest.length && looksLikeCliName(rest[j]); j += 1) {
|
|
230
|
+
values.push(...rest[j].split(",").filter((v) => v.trim()));
|
|
231
|
+
}
|
|
232
|
+
} else continue;
|
|
233
|
+
found = true;
|
|
234
|
+
if (values.length === before) emptyOccurrence = true;
|
|
196
235
|
}
|
|
197
|
-
|
|
236
|
+
if (!found) return {};
|
|
237
|
+
const names = emptyOccurrence ? [] : [...new Set(values.map((v) => v.trim()))];
|
|
198
238
|
// `--cli` with nothing after it is a typo, not a scope.
|
|
199
239
|
//
|
|
200
240
|
// It used to become `clis: []`, which `installed.json` stores verbatim and
|
|
@@ -254,7 +294,7 @@ function selectionFrom(rest: string[]): {
|
|
|
254
294
|
} {
|
|
255
295
|
// `--policy` reads right for one ("give me this policy"), `--only` for a set.
|
|
256
296
|
// They are the same switch; taking both means neither is the wrong guess.
|
|
257
|
-
const only = parseList(rest, "--policy"
|
|
297
|
+
const only = parseList(rest, "--policy", "--only");
|
|
258
298
|
const categories = parseList(rest, "--category");
|
|
259
299
|
const parsedClis = parseCliList(rest);
|
|
260
300
|
if ("error" in parsedClis) return { error: parsedClis.error };
|
|
@@ -312,6 +352,10 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
312
352
|
const effect = flag("effect") ?? "enforce";
|
|
313
353
|
const minCliVersion = flag("min-cli-version");
|
|
314
354
|
const outDir = resolve(flag("out") ?? "dist-pack");
|
|
355
|
+
// Where it will be installed from, which is what a machine judges a pack by
|
|
356
|
+
// (`isFirstPartyPack` reads `source`, never the id). `publish` passes its
|
|
357
|
+
// repository; `pack build` has only the id to go on.
|
|
358
|
+
const repo = flag("repo");
|
|
315
359
|
|
|
316
360
|
if (!entry || !id || !version) {
|
|
317
361
|
return fail([
|
|
@@ -400,6 +444,7 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
400
444
|
// same deal `parsePackPolicy` gets above, so a semantic policy that could
|
|
401
445
|
// never load fails here, where the author can fix it.
|
|
402
446
|
const semantic: SemanticManifestEntry[] = [];
|
|
447
|
+
const packSource = { source: `github:${repo ?? identity.id}` };
|
|
403
448
|
for (const [index, declaration] of semanticDeclarations.entries()) {
|
|
404
449
|
try {
|
|
405
450
|
const parsed = parsePackSemanticPolicy(identity.id, declaration, index);
|
|
@@ -409,21 +454,49 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
409
454
|
if (semantic.some((s) => s.name === parsed.name)) {
|
|
410
455
|
throw new Error(`two semantic policies are called ${JSON.stringify(parsed.name)}`);
|
|
411
456
|
}
|
|
457
|
+
// The loader's own rule: a built-in check name from anyone but FailproofAI
|
|
458
|
+
// is void on every machine, never asked and never a reviewer.
|
|
459
|
+
if (isReservedClaim(packSource, parsed.name)) {
|
|
460
|
+
throw new Error(
|
|
461
|
+
`${JSON.stringify(parsed.name)} is a name reserved for FailproofAI's own Jev checks, so a pack from ` +
|
|
462
|
+
`${repo ?? identity.id} would never have it asked. Pick a name of your own.`,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
412
465
|
semantic.push(parsed);
|
|
413
466
|
} catch (err) {
|
|
414
467
|
return fail([err instanceof Error ? err.message : String(err)]);
|
|
415
468
|
}
|
|
416
469
|
}
|
|
417
470
|
|
|
471
|
+
// A pack with Jev checks needs a build that runs them, so its minimum is at
|
|
472
|
+
// least the first one — written when the author gave none, refused when lower.
|
|
473
|
+
if (semantic.length > 0 && minCliVersion !== undefined && (compareVersions(minCliVersion, JEV_PACK_MIN_CLI) ?? 0) < 0) {
|
|
474
|
+
return fail([
|
|
475
|
+
`--min-cli-version ${minCliVersion} is older than the first failproofai that runs a pack's Jev checks (${JEV_PACK_MIN_CLI}):`,
|
|
476
|
+
"1.0.7 ignores them, and 1.0.7-beta.x replaces the built-in checks with them.",
|
|
477
|
+
`Pass --min-cli-version ${JEV_PACK_MIN_CLI} or newer, or leave it out and ${JEV_PACK_MIN_CLI} is written.`,
|
|
478
|
+
]);
|
|
479
|
+
}
|
|
480
|
+
const requiredCli = semantic.length > 0 ? (minCliVersion ?? JEV_PACK_MIN_CLI) : minCliVersion;
|
|
481
|
+
|
|
418
482
|
// The question budget, checked against what one Jev request can carry. A pack
|
|
419
483
|
// over it installs and then has its overflow policies dropped one by one on
|
|
420
484
|
// the user's machine, in manifest order — a pack that enforces less than it
|
|
421
485
|
// says, which is the failure this whole lane is built to avoid.
|
|
486
|
+
//
|
|
487
|
+
// A pack from outside FailproofAI is ADDED to the built-in checks, which every
|
|
488
|
+
// machine spends the budget on first (`semanticPoliciesFromPacks`), so it gets
|
|
489
|
+
// only what they leave.
|
|
422
490
|
const questionCost = semantic.reduce((total, entry) => total + questionChars(entry), 0);
|
|
423
|
-
|
|
491
|
+
const firstParty = isFirstPartyPack(packSource);
|
|
492
|
+
const questionBudget = MAX_PACK_QUESTION_CHARS - (firstParty ? 0 : BUILTIN_QUESTION_CHARS);
|
|
493
|
+
if (questionCost > questionBudget) {
|
|
424
494
|
return fail([
|
|
425
495
|
`This pack's ${semantic.length} semantic policies compile to ${questionCost} characters of questions, ` +
|
|
426
|
-
|
|
496
|
+
(firstParty
|
|
497
|
+
? `over the ${MAX_PACK_QUESTION_CHARS} one Jev request has room for.`
|
|
498
|
+
: `over the ${questionBudget} a machine leaves a pack from outside FailproofAI: one Jev request has room ` +
|
|
499
|
+
`for ${MAX_PACK_QUESTION_CHARS}, and the 16 built-in checks every machine asks take ${BUILTIN_QUESTION_CHARS} of it first.`),
|
|
427
500
|
"Shorten the probe instructions and criteria, or ship fewer policies per pack.",
|
|
428
501
|
]);
|
|
429
502
|
}
|
|
@@ -452,6 +525,9 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
452
525
|
? "One policy declares an authority this build cannot publish:"
|
|
453
526
|
: `${authorityProblems.length} policies declare an authority this build cannot publish:`,
|
|
454
527
|
...authorityProblems,
|
|
528
|
+
...(reviewers
|
|
529
|
+
? ["This pack declares Jev checks of its own, so reviewedBy may name only those."]
|
|
530
|
+
: []),
|
|
455
531
|
'Fix the declaration, or leave authority out and the policy is "hard". See https://docs.befailproof.ai/policies/authority',
|
|
456
532
|
]);
|
|
457
533
|
}
|
|
@@ -464,7 +540,9 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
464
540
|
// FALSE: a pack's declared defaults are what `pack add` switches on with no
|
|
465
541
|
// flags, and switching on a stranger's every policy unattended is the
|
|
466
542
|
// installer opinion this lane already refused once.
|
|
467
|
-
const extra = hook as unknown as {
|
|
543
|
+
const extra = hook as unknown as {
|
|
544
|
+
category?: unknown; defaultEnabled?: unknown; params?: unknown; alwaysOn?: unknown;
|
|
545
|
+
};
|
|
468
546
|
// `params` is read off the registration for the same reason, and leaving it
|
|
469
547
|
// out was not cosmetic: `registerPolicy` takes a pack policy's schema from the
|
|
470
548
|
// MANIFEST by name, so a pack published without it evaluates every one of its
|
|
@@ -489,6 +567,9 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
489
567
|
defaultEnabled: extra.defaultEnabled === true,
|
|
490
568
|
match: hook.match ?? {},
|
|
491
569
|
...(extra.params !== undefined ? { params: extra.params } : {}),
|
|
570
|
+
// Carried so parsePackPolicy refuses it as the loader would; left off, an
|
|
571
|
+
// alwaysOn registration published as an ordinary (even reviewable) policy.
|
|
572
|
+
...("alwaysOn" in extra ? { alwaysOn: extra.alwaysOn } : {}),
|
|
492
573
|
// Whether Jev may clear this policy's verdict. A pack's MANIFEST is what
|
|
493
574
|
// a machine reads it from, so a declaration left on the registration
|
|
494
575
|
// alone would be published as nothing — silently hard. Checked above, so
|
|
@@ -516,7 +597,7 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
516
597
|
{
|
|
517
598
|
id: identity.id,
|
|
518
599
|
version: identity.version,
|
|
519
|
-
...(
|
|
600
|
+
...(requiredCli ? { minCliVersion: requiredCli } : {}),
|
|
520
601
|
effect: identity.effect,
|
|
521
602
|
...(identity.commit ? { commit: identity.commit } : {}),
|
|
522
603
|
policies,
|
|
@@ -538,25 +619,35 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
538
619
|
);
|
|
539
620
|
|
|
540
621
|
const on = policies.filter((p) => (p as { defaultEnabled?: boolean }).defaultEnabled).length;
|
|
622
|
+
const semanticOnly = policies.length === 0 && semantic.length > 0;
|
|
541
623
|
return ok([
|
|
542
624
|
`Built ${identity.id}@${identity.version} — ${policies.length} policies, ${on} on by default.`,
|
|
543
|
-
// Said separately, and said at all, because this half
|
|
544
|
-
//
|
|
545
|
-
//
|
|
625
|
+
// Said separately, and said at all, because this half changes what Jev asks
|
|
626
|
+
// on every machine that installs the pack. An author who did not mean to
|
|
627
|
+
// ship it should find that out here.
|
|
546
628
|
...(semantic.length > 0
|
|
547
629
|
? [
|
|
548
630
|
` ${semantic.length} semantic ${semantic.length === 1 ? "policy" : "policies"} for Jev ` +
|
|
549
|
-
`(${questionCost} characters of questions),
|
|
631
|
+
`(${questionCost} characters of questions), ` +
|
|
632
|
+
(identity.effect === "observe"
|
|
633
|
+
? "not asked where it installs: Jev asks only the checks of packs that enforce."
|
|
634
|
+
: `${firstParty ? "replacing" : "added to"} the built-in checks where it installs.`),
|
|
550
635
|
]
|
|
551
636
|
: []),
|
|
552
|
-
...(
|
|
637
|
+
...(requiredCli ? [` Requires failproofai ${requiredCli} or newer.`] : []),
|
|
638
|
+
...(semanticOnly ? JEV_ONLY_ROLLBACK_REMINDER : []),
|
|
553
639
|
` ${outDir}/${PACK_MANIFEST_ASSET}`,
|
|
554
640
|
` ${outDir}/${PACK_ENTRY_ASSET}`,
|
|
555
641
|
` ${outDir}/${PACK_CHECKSUMS_ASSET}`,
|
|
556
642
|
"",
|
|
557
643
|
`Publish: attach all three to a GitHub release tagged ${identity.version}, then anyone runs:`,
|
|
558
644
|
` failproofai policies add <owner>/<repo>`,
|
|
559
|
-
], {
|
|
645
|
+
], {
|
|
646
|
+
policies: policies.length,
|
|
647
|
+
defaultOn: on,
|
|
648
|
+
...(identity.commit ? { commit: identity.commit } : {}),
|
|
649
|
+
...(semanticOnly ? { semanticOnly } : {}),
|
|
650
|
+
});
|
|
560
651
|
}
|
|
561
652
|
|
|
562
653
|
/**
|
|
@@ -2304,6 +2395,7 @@ async function publish(rest: string[]): Promise<PackCliResult> {
|
|
|
2304
2395
|
const built = await build([
|
|
2305
2396
|
entryToBuild,
|
|
2306
2397
|
"--id", id,
|
|
2398
|
+
...(repo ? ["--repo", repo] : []),
|
|
2307
2399
|
"--version", version,
|
|
2308
2400
|
// NOT when the tree is dirty, even though `--version` let the publish
|
|
2309
2401
|
// through. `commit` claims these bytes came from that commit, and on a
|
|
@@ -2328,7 +2420,9 @@ async function publish(rest: string[]): Promise<PackCliResult> {
|
|
|
2328
2420
|
...gitLines,
|
|
2329
2421
|
...(gitLines.length ? [""] : []),
|
|
2330
2422
|
...bundleNote,
|
|
2331
|
-
|
|
2423
|
+
// Everything above build's blank line: a fixed count cut the rollback
|
|
2424
|
+
// reminder mid-sentence and dropped the asset paths.
|
|
2425
|
+
...built.lines.slice(0, built.lines.indexOf("")),
|
|
2332
2426
|
"",
|
|
2333
2427
|
...(repo
|
|
2334
2428
|
? ["Dry run — nothing was published.", `Drop --dry-run to release it on ${repo}.`]
|
|
@@ -2444,6 +2538,7 @@ async function publish(rest: string[]): Promise<PackCliResult> {
|
|
|
2444
2538
|
`Published ${id}@${version} to ${repo} at tag ${tag}.` +
|
|
2445
2539
|
(versionFromSha ? " That names the commit it was built from." : ""),
|
|
2446
2540
|
` ${assets.length} assets attached`,
|
|
2541
|
+
...(built.meta?.semanticOnly ? JEV_ONLY_ROLLBACK_REMINDER : []),
|
|
2447
2542
|
"",
|
|
2448
2543
|
// The install lines are the whole point of the success message, and on a
|
|
2449
2544
|
// private repository they are a lie — `policies add` 404s there. Print the
|
|
@@ -2555,6 +2650,36 @@ function semanticPhrase(count: number): string {
|
|
|
2555
2650
|
return `${count} Jev ${count === 1 ? "check" : "checks"}`;
|
|
2556
2651
|
}
|
|
2557
2652
|
|
|
2653
|
+
/**
|
|
2654
|
+
* How a pack's Jev checks sit beside this build's, for add, show and the
|
|
2655
|
+
* picker alike: a FailproofAI pack's replace them, anyone else's are added
|
|
2656
|
+
* (`replacesBuiltinChecks`), and only where `jevPacks` lets the pack take part —
|
|
2657
|
+
* never for an observe pack, only for its agents when scoped. One phrase, so
|
|
2658
|
+
* the three cannot drift apart again.
|
|
2659
|
+
*/
|
|
2660
|
+
function besideBuiltinChecks(pack: { source?: string; effect?: PolicyEffect; clis?: string[] | null }): string {
|
|
2661
|
+
if (jevPacks([{ effect: pack.effect ?? "enforce", clis: null }]).length === 0) {
|
|
2662
|
+
return "not asked: this pack only observes, and Jev asks only the checks of packs that enforce";
|
|
2663
|
+
}
|
|
2664
|
+
const how = isFirstPartyPack(pack) ? "replacing this build's own set" : "added to this build's own checks";
|
|
2665
|
+
return pack.clis && pack.clis.length > 0 ? `${how}, for ${pack.clis.join(", ")} only` : how;
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
/**
|
|
2669
|
+
* Which of a pack's checks the resolver would leave out beside what is
|
|
2670
|
+
* installed (the shared budget, a reserved or contested name). `preview` is a
|
|
2671
|
+
* pack not installed yet, judged as if it were. A diagnostic: never throws.
|
|
2672
|
+
*/
|
|
2673
|
+
function jevCheckWarnings(id: string, preview?: PackPreview): string[] {
|
|
2674
|
+
try {
|
|
2675
|
+
const installed = readInstalledPacks().packs.filter((p) => p.id !== preview?.id);
|
|
2676
|
+
const packs = preview ? [...installed, { ...preview, clis: null }] : installed;
|
|
2677
|
+
return semanticPoliciesFromPacks(jevPacks(packs)).errors.filter((e) => e.includes(id));
|
|
2678
|
+
} catch {
|
|
2679
|
+
return [];
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2558
2683
|
async function pickFromSource(
|
|
2559
2684
|
source: string,
|
|
2560
2685
|
io: { stdin: TTYIn; stdout: TTYOut },
|
|
@@ -2586,12 +2711,12 @@ async function pickFromSource(
|
|
|
2586
2711
|
}
|
|
2587
2712
|
const on = preview.policies.filter((p) => p.defaultEnabled).length;
|
|
2588
2713
|
// Said BEFORE the list, because the list cannot say it: a pack's Jev checks are
|
|
2589
|
-
// not rows here, they arrive whole
|
|
2590
|
-
//
|
|
2714
|
+
// not rows here, and they arrive whole. Somebody ticking boxes should know
|
|
2715
|
+
// that is part of what they are agreeing to.
|
|
2591
2716
|
if (preview.semantic.length > 0) {
|
|
2592
2717
|
io.stdout.write(
|
|
2593
|
-
`\n This pack also brings ${semanticPhrase(preview.semantic.length)}
|
|
2594
|
-
|
|
2718
|
+
`\n This pack also brings ${semanticPhrase(preview.semantic.length)} (not selectable), ` +
|
|
2719
|
+
`${besideBuiltinChecks(preview)}.\n See: failproofai policies show ${source}\n\n`,
|
|
2595
2720
|
);
|
|
2596
2721
|
}
|
|
2597
2722
|
const picked = await multiSelect<string>({
|
|
@@ -2719,16 +2844,25 @@ async function add(rest: string[]): Promise<PackCliResult> {
|
|
|
2719
2844
|
: ` enabled (${result.enabled.length}/${result.available.length}, ${why}): ${summarise(result.enabled)}`,
|
|
2720
2845
|
);
|
|
2721
2846
|
// Counted separately, and without an on/off ratio, because there is none:
|
|
2722
|
-
// a pack's Jev checks arrive whole and REPLACE the ones
|
|
2847
|
+
// a pack's Jev checks arrive whole, and a FailproofAI pack's REPLACE the ones
|
|
2848
|
+
// this build ships (anyone else's are added to them).
|
|
2723
2849
|
// Silence here was the shape of the original bug — the manifest's semantic
|
|
2724
2850
|
// half was fetched, verified and then never written to `installed.json`, so
|
|
2725
2851
|
// the half somebody installed did nothing and nothing said so.
|
|
2726
2852
|
if (result.semantic > 0) {
|
|
2727
2853
|
lines.push(
|
|
2728
|
-
` ${semanticPhrase(result.semantic)}
|
|
2854
|
+
` ${semanticPhrase(result.semantic)}, ${besideBuiltinChecks(result)}. ` +
|
|
2729
2855
|
"They apply only where you configured Jev (`failproofai jev status`).",
|
|
2730
2856
|
);
|
|
2731
2857
|
}
|
|
2858
|
+
// Nothing after this screen says it: the record keeps no unreadable minimum.
|
|
2859
|
+
if (result.minCliVersionNote) lines.push(` ${result.minCliVersionNote}`);
|
|
2860
|
+
// Which of its checks the resolver will leave out beside what is already
|
|
2861
|
+
// installed (the shared question budget, a reserved or contested name).
|
|
2862
|
+
// Otherwise said only in the hook log, on the first call that asks Jev.
|
|
2863
|
+
if (result.semantic > 0) {
|
|
2864
|
+
for (const e of jevCheckWarnings(result.id)) lines.push(` ▲ ${e}`);
|
|
2865
|
+
}
|
|
2732
2866
|
|
|
2733
2867
|
if (skipped.length > 0) {
|
|
2734
2868
|
lines.push(` not enabled (${skipped.length}): ${summarise(skipped)}`);
|
|
@@ -3077,6 +3211,8 @@ export function jevChecksSection(
|
|
|
3077
3211
|
pack: {
|
|
3078
3212
|
policies: ReadonlyArray<PolicyCatalogEntry>;
|
|
3079
3213
|
semantic: ReadonlyArray<SemanticManifestEntry>;
|
|
3214
|
+
/** Where it is (or would be) installed from, which decides replace or add. */
|
|
3215
|
+
source?: string;
|
|
3080
3216
|
},
|
|
3081
3217
|
opts?: RenderOpts,
|
|
3082
3218
|
): string[] | null {
|
|
@@ -3110,8 +3246,8 @@ export function jevChecksSection(
|
|
|
3110
3246
|
...table({ head: ["", "", ""], rows, protect: [0, 1] }, opts),
|
|
3111
3247
|
"",
|
|
3112
3248
|
...note(
|
|
3113
|
-
"Nothing toggles them: `--policy` cannot name one
|
|
3114
|
-
|
|
3249
|
+
"Nothing toggles them: `--policy` cannot name one and `failproofai policies` never lists them. " +
|
|
3250
|
+
`They arrive whole, ${besideBuiltinChecks(pack)}.`,
|
|
3115
3251
|
opts,
|
|
3116
3252
|
),
|
|
3117
3253
|
...note(
|
|
@@ -3171,12 +3307,14 @@ async function listRemote(source: string): Promise<PackCliResult> {
|
|
|
3171
3307
|
opts,
|
|
3172
3308
|
),
|
|
3173
3309
|
preview.minCliVersion ? note(`Requires failproofai ${preview.minCliVersion} or newer.`, opts) : null,
|
|
3310
|
+
preview.minCliVersionNote ? note(preview.minCliVersionNote, opts) : null,
|
|
3174
3311
|
preview.resolvedFromLatest ? note(`Newest release: ${preview.source}`, opts) : null,
|
|
3175
3312
|
table({ head: ["", "", ""], rows }, opts),
|
|
3176
3313
|
// The half the table above cannot hold: its own section, under the
|
|
3177
3314
|
// policies rather than above them, because a check is read against the
|
|
3178
3315
|
// policies it can clear and those are the rows just passed.
|
|
3179
3316
|
jevChecksSection(preview, opts),
|
|
3317
|
+
preview.semantic.length > 0 ? jevCheckWarnings(preview.id, preview).flatMap((e) => warning([e], opts)) : null,
|
|
3180
3318
|
nextStep(`failproofai policies add ${source}`, "Install the defaults with:", opts),
|
|
3181
3319
|
note("Or take part of it: --policy <a,b>, --category <x,y>, --all", opts),
|
|
3182
3320
|
),
|
|
@@ -151,6 +151,9 @@ export function missingGuards(input: {
|
|
|
151
151
|
// loaded, would have allowed.
|
|
152
152
|
if (err.effect === "observe") continue;
|
|
153
153
|
if (outOfScope(err.clis, input.cli)) continue;
|
|
154
|
+
// Jev checks only: nothing in the regex tier is missing. The refusal is
|
|
155
|
+
// still reported (hook log, `failproofai policies`); it just denies nothing.
|
|
156
|
+
if (err.semanticOnly) continue;
|
|
154
157
|
const declared = err.declared ?? [];
|
|
155
158
|
out.push({
|
|
156
159
|
packId: err.id ?? "(unnamed pack)",
|
|
@@ -253,6 +253,13 @@ export interface PackError {
|
|
|
253
253
|
* deny on their behalf locks an agent out over enforcement it never had.
|
|
254
254
|
*/
|
|
255
255
|
clis?: string[] | null;
|
|
256
|
+
/**
|
|
257
|
+
* The record's regex `policies` list was readable and empty: a Jev-checks-only
|
|
258
|
+
* pack. It guards nothing in the regex tier, so its refusal has nothing to
|
|
259
|
+
* fail closed on. Read from the RAW list, never from `declared`: a list whose
|
|
260
|
+
* entries are all malformed also filters to nothing, and that must widen.
|
|
261
|
+
*/
|
|
262
|
+
semanticOnly?: true;
|
|
256
263
|
}
|
|
257
264
|
|
|
258
265
|
export interface PackReadResult {
|
|
@@ -532,7 +539,9 @@ export function checkPackMinCliVersion(
|
|
|
532
539
|
kind: "too-old",
|
|
533
540
|
reason:
|
|
534
541
|
`pack ${packId} needs failproofai ${String(declared)} or newer and this is ${current} — ` +
|
|
535
|
-
|
|
542
|
+
// As a range: the bare name installs `latest`, which can sit below a
|
|
543
|
+
// prerelease minimum. `declared` compared above, so it is plain semver.
|
|
544
|
+
`run \`npm i -g "failproofai@>=${String(declared)}" && failproofai update\``,
|
|
536
545
|
};
|
|
537
546
|
}
|
|
538
547
|
return { kind: "satisfied", declared: declared as string };
|
|
@@ -883,6 +892,7 @@ export function readInstalledPacks(): PackReadResult {
|
|
|
883
892
|
// Best effort: an entry too malformed to list policies yields nothing
|
|
884
893
|
// here, and a deny built from it is unavoidably blanket.
|
|
885
894
|
...(Array.isArray(rec?.policies) ? { declared: safeDeclared(rec.policies) } : {}),
|
|
895
|
+
...(Array.isArray(rec?.policies) && rec.policies.length === 0 ? { semanticOnly: true as const } : {}),
|
|
886
896
|
// A list of names, or nothing. Anything else is unreadable scope, and
|
|
887
897
|
// unreadable scope has to mean "every agent" — the same reasoning that
|
|
888
898
|
// widens an unreadable `match`: a narrowing nobody can parse says
|
package/src/hooks/pack-store.ts
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
parsePackSemanticPolicy,
|
|
46
46
|
readInstalledPacks,
|
|
47
47
|
} from "./pack-manifest";
|
|
48
|
+
import { isFirstPartyPack } from "./effective-reviewers";
|
|
48
49
|
import type { ResolvedPack, SemanticManifestEntry } from "./pack-manifest";
|
|
49
50
|
import type { InstalledPackRecord } from "./pack-manifest";
|
|
50
51
|
import type { PolicyCatalogEntry } from "./policy-types";
|
|
@@ -102,6 +103,11 @@ export interface AddPackResult {
|
|
|
102
103
|
* reporting "the pack is installed and enforcing nothing".
|
|
103
104
|
*/
|
|
104
105
|
semantic: number;
|
|
106
|
+
/** Why the pack's minCliVersion was ignored, when this build could not compare it. */
|
|
107
|
+
minCliVersionNote?: string;
|
|
108
|
+
/** As recorded, so the CLI can say where its Jev checks are asked (`jevPacks`). */
|
|
109
|
+
effect?: PolicyEffect;
|
|
110
|
+
clis?: string[];
|
|
105
111
|
artifact: string;
|
|
106
112
|
}
|
|
107
113
|
|
|
@@ -474,6 +480,8 @@ interface FetchedPack {
|
|
|
474
480
|
semantic: SemanticManifestEntry[];
|
|
475
481
|
/** The minimum CLI it claims, once this one has been checked against it. */
|
|
476
482
|
minCliVersion?: string;
|
|
483
|
+
/** Why a minimum this build cannot compare was ignored, to be said out loud. */
|
|
484
|
+
minCliVersionNote?: string;
|
|
477
485
|
effect?: PolicyEffect;
|
|
478
486
|
/** The git commit the publisher built this from, when they had one. */
|
|
479
487
|
commit?: string;
|
|
@@ -530,15 +538,22 @@ function parseManifestSemantic(packId: string, value: unknown): SemanticManifest
|
|
|
530
538
|
* until somebody works out why. Catching it at install turns the same fact into
|
|
531
539
|
* one line at the moment of choosing.
|
|
532
540
|
*
|
|
533
|
-
* A minimum this build cannot compare is
|
|
541
|
+
* A minimum this build cannot compare is ignored, exactly as the loader ignores
|
|
534
542
|
* it: a publisher's typo in a version string must not be able to stop anyone
|
|
535
|
-
* installing a pack.
|
|
536
|
-
*
|
|
543
|
+
* installing a pack. But the reason comes back as a note for the preview and
|
|
544
|
+
* the install to print, since the record keeps no unreadable value and the
|
|
545
|
+
* loader's own warning never reaches a CLI screen. `failproofai publish`
|
|
546
|
+
* refuses to write one, so this is only reachable from a pack built by some
|
|
547
|
+
* other tool.
|
|
537
548
|
*/
|
|
538
|
-
function assertMinCliVersion(
|
|
549
|
+
function assertMinCliVersion(
|
|
550
|
+
packId: string,
|
|
551
|
+
declared: unknown,
|
|
552
|
+
): { minCliVersion?: string; minCliVersionNote?: string } {
|
|
539
553
|
const verdict = checkPackMinCliVersion(packId, declared);
|
|
540
554
|
if (verdict.kind === "too-old") throw new Error(verdict.reason);
|
|
541
|
-
|
|
555
|
+
if (verdict.kind === "unreadable") return { minCliVersionNote: verdict.reason };
|
|
556
|
+
return verdict.declared ? { minCliVersion: verdict.declared } : {};
|
|
542
557
|
}
|
|
543
558
|
|
|
544
559
|
/** Fetch and fully validate a pack, without writing anything. */
|
|
@@ -636,6 +651,8 @@ export interface PackPreview {
|
|
|
636
651
|
semantic: SemanticManifestEntry[];
|
|
637
652
|
/** The minimum CLI this pack declares, when it declares one this build can read. */
|
|
638
653
|
minCliVersion?: string;
|
|
654
|
+
/** Why a declared minimum this build cannot read was ignored. */
|
|
655
|
+
minCliVersionNote?: string;
|
|
639
656
|
/** The exact source the preview was read from, tag resolved and pinned. */
|
|
640
657
|
source: string;
|
|
641
658
|
/** True when the tag was resolved rather than typed. */
|
|
@@ -685,7 +702,7 @@ export async function fetchPackPreview(source: string): Promise<PackPreview> {
|
|
|
685
702
|
semantic?: unknown; minCliVersion?: unknown;
|
|
686
703
|
};
|
|
687
704
|
const identity = parsePackIdentity(value);
|
|
688
|
-
const
|
|
705
|
+
const minCli = assertMinCliVersion(identity.id, value.minCliVersion);
|
|
689
706
|
const semantic = parseManifestSemantic(identity.id, value.semantic);
|
|
690
707
|
const policies = parseManifestPolicies(identity.id, value.policies);
|
|
691
708
|
assertDeclaresSomething(policies, semantic);
|
|
@@ -696,7 +713,7 @@ export async function fetchPackPreview(source: string): Promise<PackPreview> {
|
|
|
696
713
|
...(identity.commit ? { commit: identity.commit } : {}),
|
|
697
714
|
policies,
|
|
698
715
|
semantic,
|
|
699
|
-
...
|
|
716
|
+
...minCli,
|
|
700
717
|
source: formatPackSpec(spec),
|
|
701
718
|
resolvedFromLatest,
|
|
702
719
|
};
|
|
@@ -710,7 +727,14 @@ export async function fetchPackPreview(source: string): Promise<PackPreview> {
|
|
|
710
727
|
function parseManifestPolicies(packId: string, value: unknown): PolicyCatalogEntry[] {
|
|
711
728
|
if (value === undefined) return [];
|
|
712
729
|
if (!Array.isArray(value)) throw new Error(`pack ${packId} policies is not an array`);
|
|
713
|
-
|
|
730
|
+
const policies = value.map((policy, i) => parsePackPolicy(packId, policy, i));
|
|
731
|
+
// The loader's rule: it refuses the whole record over a repeated name.
|
|
732
|
+
const names = new Set<string>();
|
|
733
|
+
for (const p of policies) {
|
|
734
|
+
if (names.has(p.name)) throw new Error(`pack ${packId} declares ${p.name} twice`);
|
|
735
|
+
names.add(p.name);
|
|
736
|
+
}
|
|
737
|
+
return policies;
|
|
714
738
|
}
|
|
715
739
|
|
|
716
740
|
/**
|
|
@@ -776,7 +800,7 @@ async function fetchPack(spec: PinnedPackSpec): Promise<FetchedPack> {
|
|
|
776
800
|
// Before the shape checks below, because being too old for the pack is not a
|
|
777
801
|
// complaint about the pack: the message to give is "update the CLI", and a
|
|
778
802
|
// manifest field this build cannot read yet must not be reported as malformed.
|
|
779
|
-
const
|
|
803
|
+
const minCli = assertMinCliVersion(identity.id, raw.minCliVersion);
|
|
780
804
|
// Validated with the SAME rules the loader applies, so a pack that could never
|
|
781
805
|
// load is refused here — while nothing has been written — rather than
|
|
782
806
|
// installing cleanly and failing silently on the next tool call.
|
|
@@ -789,7 +813,7 @@ async function fetchPack(spec: PinnedPackSpec): Promise<FetchedPack> {
|
|
|
789
813
|
version: identity.version,
|
|
790
814
|
policies,
|
|
791
815
|
semantic,
|
|
792
|
-
...
|
|
816
|
+
...minCli,
|
|
793
817
|
...(raw.effect !== undefined ? { effect: identity.effect } : {}),
|
|
794
818
|
...(identity.commit ? { commit: identity.commit } : {}),
|
|
795
819
|
artifact,
|
|
@@ -995,6 +1019,16 @@ export async function addPack(
|
|
|
995
1019
|
: `Name the tag whose manifest says ${fetched.version}, or fix the pack: ${fix}.`),
|
|
996
1020
|
);
|
|
997
1021
|
}
|
|
1022
|
+
// The id is self-declared, so without this any release could install as a
|
|
1023
|
+
// FailproofAI pack: labelled FailproofAI's own wherever verdicts are shown,
|
|
1024
|
+
// and squatting the id the real pack then cannot take. Owner only — an id may
|
|
1025
|
+
// otherwise differ from its repository (`publish --id`).
|
|
1026
|
+
if (/^failproofai\//i.test(fetched.id) && !isFirstPartyPack({ source: formatPackSpec(spec) })) {
|
|
1027
|
+
throw new Error(
|
|
1028
|
+
`${fetched.id} was not installed: the FailproofAI/ namespace is reserved for releases from ` +
|
|
1029
|
+
`github.com/FailproofAI, and this one came from ${spec.owner}/${spec.repo}.`,
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
998
1032
|
const available = fetched.policies.map((p) => p.name);
|
|
999
1033
|
|
|
1000
1034
|
const prior = priorRecordFor(fetched.id, fetched.artifactDigest);
|
|
@@ -1124,6 +1158,9 @@ export async function addPack(
|
|
|
1124
1158
|
selection: reason,
|
|
1125
1159
|
categories: [...new Set(fetched.policies.map((p) => slugifyCategory(p.category)))],
|
|
1126
1160
|
semantic: fetched.semantic.length,
|
|
1161
|
+
...(fetched.minCliVersionNote ? { minCliVersionNote: fetched.minCliVersionNote } : {}),
|
|
1162
|
+
...(fetched.effect ? { effect: fetched.effect } : {}),
|
|
1163
|
+
...(record.clis ? { clis: record.clis } : {}),
|
|
1127
1164
|
artifact: artifactAbs,
|
|
1128
1165
|
};
|
|
1129
1166
|
}
|