failproofai 1.0.7-beta.2 → 1.0.7
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 +4 -4
- 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 +4 -4
- 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 +4 -4
- 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 +7 -8
- 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 +5 -7
- 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 +6 -6
- 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 +11 -13
- 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 +7 -8
- 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 +7 -7
- 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 +6 -7
- 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 +8 -41
- package/.next/standalone/.next/server/app/settings/page.js +9 -12
- 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]__1_bftcl._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__19_pzeq._.js → [externals]__1msfs-h._.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]__1bf34x4._.js → [root-of-the-server]__1_r2rbg._.js} +7 -5
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
- 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/{_1q5i8mb._.js → _1c3k-8x._.js} +2 -2
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +2 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0l44ual._.js → [root-of-the-server]__0bd3mje._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__0cxe_2_._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +22 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1w_5l7t._.js → _0h_douw._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1mel6y1._.js → _1-i_gzc._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_1v-jvrv._.js → _166t73i._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1q46vxx._.js → _1_qswah._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_1gb0ifp._.js → _1es2j7i._.js} +5 -5
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +3 -0
- 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 +2 -2
- 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 +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0w6mzq5._.js → node_modules_next_dist_0drixxt._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +10 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.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/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 +23 -56
- package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +1 -0
- package/.next/standalone/.next/static/chunks/{129ag2bw93bdh.js → 0__8a7m868fvf.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3ugmd_7dyn0id.js → 0o6qlkgubtoex.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0fqd7m_u81mi5.js → 13i7-9is-vhys.js} +1 -1
- package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +6 -0
- package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +1 -0
- package/.next/standalone/.next/static/chunks/{2_pltstd8-xgs.js → 2klitrtzpaoe0.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3brze37td_wnc.js → 2mdh397ghgnvv.js} +1 -1
- package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +2 -0
- package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +1 -0
- package/.next/standalone/.next/static/chunks/3rh5o7e16irrm.js +69 -0
- package/.next/standalone/.next/static/chunks/{1qd741hzlmjbo.js → 43ufqrz8qo3h-.js} +1 -1
- package/.next/standalone/SECURITY.md +53 -0
- package/.next/standalone/app/actions/pack-actions.ts +0 -12
- package/.next/standalone/app/policies/hooks-client.tsx +0 -9
- package/.next/standalone/app/settings/page.tsx +1 -20
- package/.next/standalone/app/settings/settings-client.tsx +1 -27
- package/.next/standalone/app/settings/settings.css +0 -79
- package/.next/standalone/package.json +10 -10
- 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/server.js +1 -1
- package/README.md +2 -2
- package/bin/failproofai.mjs +2 -115
- package/dist/cli.mjs +6543 -13872
- package/dist/index.js +1 -19
- package/dist/worker.mjs +2022 -8055
- package/package.json +10 -10
- package/pi-extension/index.ts +0 -11
- package/scripts/build-policy-pack.mjs +2 -53
- package/src/audit/features.ts +2 -3
- package/src/hooks/builtin-policies.ts +8 -177
- package/src/hooks/cloud-enrollment-cli.ts +1 -1
- package/src/hooks/cloud-managed-policies.ts +0 -22
- package/src/hooks/custom-hooks-loader.ts +7 -45
- package/src/hooks/custom-hooks-registry.ts +1 -45
- package/src/hooks/first-run-gate.ts +0 -5
- package/src/hooks/fp-home.ts +0 -23
- package/src/hooks/handler.ts +6 -265
- package/src/hooks/hook-activity-store.ts +1 -105
- package/src/hooks/hook-telemetry.ts +0 -41
- package/src/hooks/loader-utils.ts +0 -6
- package/src/hooks/manager.ts +1 -1
- package/src/hooks/pack-cli.ts +19 -412
- package/src/hooks/pack-manifest.ts +7 -479
- package/src/hooks/pack-store.ts +11 -157
- package/src/hooks/policy-catalog.ts +0 -204
- package/src/hooks/policy-evaluator.ts +796 -940
- package/src/hooks/policy-registry.ts +0 -25
- package/src/hooks/policy-types.ts +0 -126
- package/src/hooks/types.ts +1 -1
- package/src/hooks/worker-server.ts +26 -119
- package/src/index.ts +0 -6
- 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]__01bmjsj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +0 -22
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qxnccm._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0soxz2z._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +0 -151
- 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/static/chunks/043j99m8ykg__.css +0 -2
- package/.next/standalone/.next/static/chunks/2c8j9l6j_b1ci.js +0 -1
- package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +0 -1
- package/.next/standalone/.next/static/chunks/3-k569wzcli8q.js +0 -1
- package/.next/standalone/.next/static/chunks/3otmypm6j_xfo.js +0 -6
- package/.next/standalone/.next/static/chunks/3yxro_r2_o9ad.js +0 -69
- package/.next/standalone/PROBE-FOLLOWUP.md +0 -186
- package/.next/standalone/app/actions/get-jev-config.ts +0 -424
- package/.next/standalone/app/actions/update-jev-config.ts +0 -420
- package/.next/standalone/app/components/jev-notices.tsx +0 -96
- package/.next/standalone/app/settings/jev-panel.tsx +0 -473
- package/src/hooks/effective-reviewers.ts +0 -79
- package/src/hooks/jev-activity.ts +0 -385
- package/src/hooks/jev-cli.ts +0 -1249
- package/src/hooks/policy-authority.ts +0 -333
- package/src/hooks/policy-reviewability.ts +0 -229
- package/src/hooks/semantic/combine.ts +0 -541
- package/src/hooks/semantic/compile.ts +0 -176
- package/src/hooks/semantic/decide.ts +0 -392
- package/src/hooks/semantic/envelope.ts +0 -1296
- package/src/hooks/semantic/evaluator.ts +0 -547
- package/src/hooks/semantic/facts.ts +0 -292
- package/src/hooks/semantic/intent.ts +0 -1190
- package/src/hooks/semantic/jev-client.ts +0 -643
- package/src/hooks/semantic/jev-config.ts +0 -594
- package/src/hooks/semantic/jev-review.ts +0 -374
- package/src/hooks/semantic/jev-stats.ts +0 -289
- package/src/hooks/semantic/jev-throttle.ts +0 -421
- package/src/hooks/semantic/pack-policies.ts +0 -251
- package/src/hooks/semantic/policies.ts +0 -596
- package/src/hooks/semantic/precondition-names.ts +0 -60
- package/src/hooks/semantic/preconditions.ts +0 -58
- package/src/hooks/semantic/redact.ts +0 -2910
- package/src/hooks/semantic/types.ts +0 -145
- package/src/hooks/semver-precedence.ts +0 -128
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_ssgManifest.js +0 -0
package/src/hooks/pack-cli.ts
CHANGED
|
@@ -13,20 +13,7 @@ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "nod
|
|
|
13
13
|
import { INTEGRATION_TYPES } from "./types";
|
|
14
14
|
import { PACK_COMMIT_RE, PACK_VERSION_RE } from "./pack-manifest";
|
|
15
15
|
import { detectInstalledClis } from "./integrations";
|
|
16
|
-
import {
|
|
17
|
-
packParamsProblem,
|
|
18
|
-
parsePackIdentity,
|
|
19
|
-
parsePackPolicy,
|
|
20
|
-
parsePackSemanticPolicy,
|
|
21
|
-
readInstalledPacks,
|
|
22
|
-
type SemanticManifestEntry,
|
|
23
|
-
} from "./pack-manifest";
|
|
24
|
-
import { parseSemver } from "./semver-precedence";
|
|
25
|
-
// The build step measures a pack's question set against the envelope's own
|
|
26
|
-
// budget, which means reaching the semantic side. This is a CLI module — loaded
|
|
27
|
-
// by `failproofai publish`, never by a hook — so the rule that keeps those
|
|
28
|
-
// modules off an unconfigured machine's hook path does not apply here.
|
|
29
|
-
import { MAX_PACK_QUESTION_CHARS, questionChars } from "./semantic/pack-policies";
|
|
16
|
+
import { parsePackIdentity, parsePackPolicy, readInstalledPacks } from "./pack-manifest";
|
|
30
17
|
import {
|
|
31
18
|
AmbiguousPackId,
|
|
32
19
|
PACK_CHECKSUMS_ASSET,
|
|
@@ -43,10 +30,7 @@ import {
|
|
|
43
30
|
} from "./pack-store";
|
|
44
31
|
import type { PolicyEffect } from "./cloud-managed-policies";
|
|
45
32
|
import { loadCustomHooks } from "./custom-hooks-loader";
|
|
46
|
-
import {
|
|
47
|
-
import { authorityFieldsOf, authorityProblem, resolvePolicyAuthority } from "./policy-authority";
|
|
48
|
-
import type { PolicyCatalogEntry, SemanticPolicyDeclaration } from "./policy-types";
|
|
49
|
-
import type { MultiChoice, RenderOpts, TTYIn, TTYOut } from "./tui";
|
|
33
|
+
import type { MultiChoice, TTYIn, TTYOut } from "./tui";
|
|
50
34
|
import {
|
|
51
35
|
chip,
|
|
52
36
|
emptyState,
|
|
@@ -310,23 +294,12 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
310
294
|
const version = flag("version");
|
|
311
295
|
const commit = flag("commit");
|
|
312
296
|
const effect = flag("effect") ?? "enforce";
|
|
313
|
-
const minCliVersion = flag("min-cli-version");
|
|
314
297
|
const outDir = resolve(flag("out") ?? "dist-pack");
|
|
315
298
|
|
|
316
299
|
if (!entry || !id || !version) {
|
|
317
300
|
return fail([
|
|
318
301
|
"Usage: failproofai publish <entry.mjs> --repo <owner>/<repo> --version <version>",
|
|
319
|
-
" [--out <dir>] [--effect enforce|observe]
|
|
320
|
-
]);
|
|
321
|
-
}
|
|
322
|
-
// Refused here rather than written and refused by every machine that installs
|
|
323
|
-
// it. A `minCliVersion` nobody can order is a requirement nobody can check,
|
|
324
|
-
// and the loader treats one as grounds to refuse the whole pack — so a typo
|
|
325
|
-
// published here is a pack that installs nowhere.
|
|
326
|
-
if (minCliVersion !== undefined && parseSemver(minCliVersion) === null) {
|
|
327
|
-
return fail([
|
|
328
|
-
`--min-cli-version ${JSON.stringify(minCliVersion)} is not a version that can be compared.`,
|
|
329
|
-
"It must be plain semver — 1.0.7, or 1.0.7-beta.0 for a pre-release.",
|
|
302
|
+
" [--out <dir>] [--effect enforce|observe]",
|
|
330
303
|
]);
|
|
331
304
|
}
|
|
332
305
|
let identity: { id: string; version: string; effect: PolicyEffect; commit?: string };
|
|
@@ -342,34 +315,24 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
342
315
|
// Refused rather than bundled: see the note above. A relative specifier is the
|
|
343
316
|
// only kind that would be rewritten by the loader and left outside the digest.
|
|
344
317
|
const source = readFileSync(entryPath, "utf8");
|
|
345
|
-
const localImport =
|
|
318
|
+
const localImport = /(?:^|\n)\s*(?:import|export)[^;\n]*from\s+["'](\.[^"']*)["']/.exec(source);
|
|
346
319
|
if (localImport) {
|
|
347
320
|
return fail([
|
|
348
|
-
`${entryPath} imports ${localImport}, and only the entry file is digest-pinned.`,
|
|
321
|
+
`${entryPath} imports ${localImport[1]}, and only the entry file is digest-pinned.`,
|
|
349
322
|
"Bundle it to a single file first (esbuild, bun build, rollup), then build the pack from that.",
|
|
350
323
|
]);
|
|
351
324
|
}
|
|
352
325
|
|
|
353
326
|
let hooks;
|
|
354
|
-
let semanticDeclarations: SemanticPolicyDeclaration[];
|
|
355
327
|
try {
|
|
356
328
|
hooks = await loadCustomHooks(entryPath, { strict: true });
|
|
357
|
-
// Read AFTER the load, from the same registration pass `clearCustomHooks`
|
|
358
|
-
// resets: a semantic policy has no `fn`, so it never travels back through
|
|
359
|
-
// `loadCustomHooks`'s return value.
|
|
360
|
-
semanticDeclarations = [...getSemanticRegistrations()];
|
|
361
329
|
} catch (err) {
|
|
362
330
|
return fail([`Could not load ${entryPath}: ${err instanceof Error ? err.message : String(err)}`]);
|
|
363
331
|
}
|
|
364
|
-
|
|
365
|
-
// thing to publish, since the regex floor may already be somebody else's pack
|
|
366
|
-
// — is refused for registering nothing.
|
|
367
|
-
if (hooks.length === 0 && semanticDeclarations.length === 0) {
|
|
332
|
+
if (hooks.length === 0) {
|
|
368
333
|
return fail([
|
|
369
334
|
`${entryPath} registered no policies.`,
|
|
370
|
-
"A pack entry calls customPolicies.add({ name, description, match, fn }) for each one
|
|
371
|
-
"and semanticPolicies.add({ name, title, appliesTo, mode, userCanOverride, probes, guidance })",
|
|
372
|
-
"for each Jev question set.",
|
|
335
|
+
"A pack entry calls customPolicies.add({ name, description, match, fn }) for each one.",
|
|
373
336
|
]);
|
|
374
337
|
}
|
|
375
338
|
|
|
@@ -396,66 +359,6 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
396
359
|
]);
|
|
397
360
|
}
|
|
398
361
|
|
|
399
|
-
// The pack's Jev question sets, validated with the LOADER's own rules — the
|
|
400
|
-
// same deal `parsePackPolicy` gets above, so a semantic policy that could
|
|
401
|
-
// never load fails here, where the author can fix it.
|
|
402
|
-
const semantic: SemanticManifestEntry[] = [];
|
|
403
|
-
for (const [index, declaration] of semanticDeclarations.entries()) {
|
|
404
|
-
try {
|
|
405
|
-
const parsed = parsePackSemanticPolicy(identity.id, declaration, index);
|
|
406
|
-
// Same reason two regex policies cannot share a name, one layer over: the
|
|
407
|
-
// answer map is keyed `<policy>.<probe>`, so a duplicate would overwrite
|
|
408
|
-
// the first policy's questions in the compiled request.
|
|
409
|
-
if (semantic.some((s) => s.name === parsed.name)) {
|
|
410
|
-
throw new Error(`two semantic policies are called ${JSON.stringify(parsed.name)}`);
|
|
411
|
-
}
|
|
412
|
-
semantic.push(parsed);
|
|
413
|
-
} catch (err) {
|
|
414
|
-
return fail([err instanceof Error ? err.message : String(err)]);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// The question budget, checked against what one Jev request can carry. A pack
|
|
419
|
-
// over it installs and then has its overflow policies dropped one by one on
|
|
420
|
-
// the user's machine, in manifest order — a pack that enforces less than it
|
|
421
|
-
// says, which is the failure this whole lane is built to avoid.
|
|
422
|
-
const questionCost = semantic.reduce((total, entry) => total + questionChars(entry), 0);
|
|
423
|
-
if (questionCost > MAX_PACK_QUESTION_CHARS) {
|
|
424
|
-
return fail([
|
|
425
|
-
`This pack's ${semantic.length} semantic policies compile to ${questionCost} characters of questions, ` +
|
|
426
|
-
`over the ${MAX_PACK_QUESTION_CHARS} one Jev request has room for.`,
|
|
427
|
-
"Shorten the probe instructions and criteria, or ship fewer policies per pack.",
|
|
428
|
-
]);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// An authority declaration the manifest would lose, or that registration
|
|
432
|
-
// would quietly downgrade to hard, is refused HERE. Every machine that
|
|
433
|
-
// installs the pack would otherwise make the policy hard with only a warning
|
|
434
|
-
// in its own log — the author's decision, silently not taking effect — while
|
|
435
|
-
// at build time nothing is installed yet and refusing costs nobody anything.
|
|
436
|
-
// The same rule `scripts/build-policy-pack.mjs` applies to the core pack.
|
|
437
|
-
//
|
|
438
|
-
// Judged against the reviewers the PACK SHIPS WITH, not this build's: a pack
|
|
439
|
-
// that carries both tiers replaces the compiled semantic set on every machine
|
|
440
|
-
// that installs it, so `reviewedBy: ["its-own-check"]` is exactly right and
|
|
441
|
-
// the builtin list would call it a name "this build does not have". A pack
|
|
442
|
-
// with no semantic entries still answers to the builtin set, which is what its
|
|
443
|
-
// machines will be running.
|
|
444
|
-
const reviewers = semantic.length > 0 ? new Set(semantic.map((s) => s.name)) : undefined;
|
|
445
|
-
const authorityProblems = hooks.flatMap((hook) => {
|
|
446
|
-
const problem = authorityProblem({ authority: hook.authority, reviewedBy: hook.reviewedBy }, reviewers);
|
|
447
|
-
return problem ? [` ${hook.name}: ${problem}`] : [];
|
|
448
|
-
});
|
|
449
|
-
if (authorityProblems.length > 0) {
|
|
450
|
-
return fail([
|
|
451
|
-
authorityProblems.length === 1
|
|
452
|
-
? "One policy declares an authority this build cannot publish:"
|
|
453
|
-
: `${authorityProblems.length} policies declare an authority this build cannot publish:`,
|
|
454
|
-
...authorityProblems,
|
|
455
|
-
'Fix the declaration, or leave authority out and the policy is "hard". See https://docs.befailproof.ai/policies/authority',
|
|
456
|
-
]);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
362
|
const policies: unknown[] = [];
|
|
460
363
|
for (const [index, hook] of hooks.entries()) {
|
|
461
364
|
// `category` and `defaultEnabled` are pack-manifest fields a plain custom
|
|
@@ -464,36 +367,13 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
464
367
|
// FALSE: a pack's declared defaults are what `pack add` switches on with no
|
|
465
368
|
// flags, and switching on a stranger's every policy unattended is the
|
|
466
369
|
// installer opinion this lane already refused once.
|
|
467
|
-
const extra = hook as unknown as { category?: unknown; defaultEnabled?: unknown
|
|
468
|
-
// `params` is read off the registration for the same reason, and leaving it
|
|
469
|
-
// out was not cosmetic: `registerPolicy` takes a pack policy's schema from the
|
|
470
|
-
// MANIFEST by name, so a pack published without it evaluates every one of its
|
|
471
|
-
// policies with `ctx.params = {}` — which discards the user's own configured
|
|
472
|
-
// values too, not just the defaults. Of the 39 builtins, 20 carry a schema:
|
|
473
|
-
// `prefer-package-manager` would have been inert, and `block-sudo`'s
|
|
474
|
-
// `allowPatterns`, `block-rm-rf`'s and `block-read-outside-cwd`'s `allowPaths`
|
|
475
|
-
// would all have stopped working — every one of them failing silently
|
|
476
|
-
// STRICTER, which is the direction users work around instead of reporting.
|
|
477
|
-
const paramsProblem = packParamsProblem(extra.params);
|
|
478
|
-
if (paramsProblem) {
|
|
479
|
-
return fail([
|
|
480
|
-
`${hook.name} declares a params schema this build cannot publish: ${paramsProblem}`,
|
|
481
|
-
"A machine reads the schema from the manifest, so an unusable one is a policy",
|
|
482
|
-
"that quietly runs without its parameters. Fix the declaration, or leave params out.",
|
|
483
|
-
]);
|
|
484
|
-
}
|
|
370
|
+
const extra = hook as unknown as { category?: unknown; defaultEnabled?: unknown };
|
|
485
371
|
const candidate = {
|
|
486
372
|
name: hook.name,
|
|
487
373
|
description: hook.description ?? "",
|
|
488
374
|
category: typeof extra.category === "string" && extra.category ? extra.category : "General",
|
|
489
375
|
defaultEnabled: extra.defaultEnabled === true,
|
|
490
376
|
match: hook.match ?? {},
|
|
491
|
-
...(extra.params !== undefined ? { params: extra.params } : {}),
|
|
492
|
-
// Whether Jev may clear this policy's verdict. A pack's MANIFEST is what
|
|
493
|
-
// a machine reads it from, so a declaration left on the registration
|
|
494
|
-
// alone would be published as nothing — silently hard. Checked above, so
|
|
495
|
-
// everything declared here is shape-valid; absent stays absent.
|
|
496
|
-
...authorityFieldsOf(hook as unknown as Record<string, unknown>),
|
|
497
377
|
};
|
|
498
378
|
try {
|
|
499
379
|
policies.push(parsePackPolicy(identity.id, candidate, index));
|
|
@@ -506,21 +386,14 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
506
386
|
// The manifest is hashed and the hash is the pin, so every byte in here is
|
|
507
387
|
// part of what a machine verifies — a field carrying "there was nothing to
|
|
508
388
|
// say" earns none of that cost. Readers already treat absence as ordinary.
|
|
509
|
-
//
|
|
510
|
-
// `minCliVersion` and `semantic` follow the same rule, and for `semantic` it is
|
|
511
|
-
// load-bearing rather than tidy: an EMPTY array would still be "a pack that
|
|
512
|
-
// declares semantic entries" to a careless reader, and the replacement rule
|
|
513
|
-
// turns that into "this pack replaced the compiled-in set with nothing".
|
|
514
389
|
const manifest =
|
|
515
390
|
JSON.stringify(
|
|
516
391
|
{
|
|
517
392
|
id: identity.id,
|
|
518
393
|
version: identity.version,
|
|
519
|
-
...(minCliVersion ? { minCliVersion } : {}),
|
|
520
394
|
effect: identity.effect,
|
|
521
395
|
...(identity.commit ? { commit: identity.commit } : {}),
|
|
522
396
|
policies,
|
|
523
|
-
...(semantic.length > 0 ? { semantic } : {}),
|
|
524
397
|
},
|
|
525
398
|
null,
|
|
526
399
|
2,
|
|
@@ -540,16 +413,6 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
540
413
|
const on = policies.filter((p) => (p as { defaultEnabled?: boolean }).defaultEnabled).length;
|
|
541
414
|
return ok([
|
|
542
415
|
`Built ${identity.id}@${identity.version} — ${policies.length} policies, ${on} on by default.`,
|
|
543
|
-
// Said separately, and said at all, because this half replaces the compiled-in
|
|
544
|
-
// semantic set on every machine that installs the pack. An author who did not
|
|
545
|
-
// mean to ship it should find that out here.
|
|
546
|
-
...(semantic.length > 0
|
|
547
|
-
? [
|
|
548
|
-
` ${semantic.length} semantic ${semantic.length === 1 ? "policy" : "policies"} for Jev ` +
|
|
549
|
-
`(${questionCost} characters of questions), replacing this build's own set where it installs.`,
|
|
550
|
-
]
|
|
551
|
-
: []),
|
|
552
|
-
...(minCliVersion ? [` Requires failproofai ${minCliVersion} or newer.`] : []),
|
|
553
416
|
` ${outDir}/${PACK_MANIFEST_ASSET}`,
|
|
554
417
|
` ${outDir}/${PACK_ENTRY_ASSET}`,
|
|
555
418
|
` ${outDir}/${PACK_CHECKSUMS_ASSET}`,
|
|
@@ -1136,13 +999,7 @@ function inferTaggedVersion(entryPath: string, sources?: string[]): string | nul
|
|
|
1136
999
|
* Identified by CONTENT, not by filename. A name convention would either miss
|
|
1137
1000
|
* `guards.mjs` or match an unrelated `policies.mjs` that configures something
|
|
1138
1001
|
* else entirely; a file that imports `failproofai` and calls
|
|
1139
|
-
* `customPolicies.add`
|
|
1140
|
-
* it is called.
|
|
1141
|
-
*
|
|
1142
|
-
* Both registries count, and leaving one out was not a cosmetic gap: a file that
|
|
1143
|
-
* registers only Jev questions was undiscoverable, so `failproofai publish` in
|
|
1144
|
-
* its own directory reported no policy file at all rather than reaching the
|
|
1145
|
-
* "registered no policies" check and explaining itself.
|
|
1002
|
+
* `customPolicies.add` is a policy file whatever it is called.
|
|
1146
1003
|
*
|
|
1147
1004
|
* Returns ALL of them. Splitting policies across files is the normal thing to
|
|
1148
1005
|
* do past about three, and they are one pack — so several files is an answer,
|
|
@@ -1151,11 +1008,8 @@ function inferTaggedVersion(entryPath: string, sources?: string[]): string | nul
|
|
|
1151
1008
|
*
|
|
1152
1009
|
* Non-recursive on purpose. Walking the tree finds fixtures, examples and
|
|
1153
1010
|
* anything vendored, and publishing those is the failure this avoids.
|
|
1154
|
-
*
|
|
1155
|
-
* Takes the directory rather than reading `process.cwd()` so the discovery rule
|
|
1156
|
-
* can be tested against a real folder without a `chdir`.
|
|
1157
1011
|
*/
|
|
1158
|
-
|
|
1012
|
+
function findEntry(dir: string): string[] {
|
|
1159
1013
|
let names: string[];
|
|
1160
1014
|
try {
|
|
1161
1015
|
names = readdirSync(dir);
|
|
@@ -1171,10 +1025,7 @@ export function findEntry(dir: string): string[] {
|
|
|
1171
1025
|
// would be the expensive half of this scan.
|
|
1172
1026
|
if (statSync(full).size > 512 * 1024) continue;
|
|
1173
1027
|
const text = readFileSync(full, "utf8");
|
|
1174
|
-
if (
|
|
1175
|
-
/from\s+["']failproofai["']/.test(text) &&
|
|
1176
|
-
/(?:customPolicies|semanticPolicies)\s*\.\s*add\s*\(/.test(text)
|
|
1177
|
-
) {
|
|
1028
|
+
if (/from\s+["']failproofai["']/.test(text) && /customPolicies\s*\.\s*add\s*\(/.test(text)) {
|
|
1178
1029
|
found.push(full);
|
|
1179
1030
|
}
|
|
1180
1031
|
} catch {
|
|
@@ -1876,46 +1727,8 @@ function bundleEntry(
|
|
|
1876
1727
|
*/
|
|
1877
1728
|
const PUBLISH_VALUE_FLAGS = new Set([
|
|
1878
1729
|
"--repo", "--version", "--id", "--tag", "--notes", "--out", "--effect", "--entry", "--init",
|
|
1879
|
-
"--commit",
|
|
1730
|
+
"--commit",
|
|
1880
1731
|
]);
|
|
1881
|
-
/**
|
|
1882
|
-
* The first relative specifier an entry imports, or null.
|
|
1883
|
-
*
|
|
1884
|
-
* Two callers read this and they must agree, because between them they are the
|
|
1885
|
-
* only two outcomes: `publish` bundles a multi-file entry into the one artifact
|
|
1886
|
-
* a pack has to be, and `build` REFUSES one, since only the entry file is
|
|
1887
|
-
* digest-pinned. A shape that neither recognises gets bundled by neither and
|
|
1888
|
-
* refused by neither, and the author sees whatever the loader's rewrite makes of
|
|
1889
|
-
* it — which is an esbuild parse error naming a token, not a file.
|
|
1890
|
-
*
|
|
1891
|
-
* Both sites previously used `[^;\n]*` between `import` and `from`, which cannot
|
|
1892
|
-
* cross a newline and requires a `from` clause to exist. So both missed:
|
|
1893
|
-
*
|
|
1894
|
-
* import "./policies/regex" // side-effect only: there is no `from`
|
|
1895
|
-
* import {
|
|
1896
|
-
* aLongList,
|
|
1897
|
-
* } from "./shared" // the `from` is on another line
|
|
1898
|
-
*
|
|
1899
|
-
* and a pack entry written either way — the natural way to write one whose
|
|
1900
|
-
* halves register at module scope — failed to publish with `Expected "from" but
|
|
1901
|
-
* found "{"`. Now `[^;]*?` spans newlines and stops at a statement end, and the
|
|
1902
|
-
* bare form is matched on its own. Over-reaching to a later `from` is harmless:
|
|
1903
|
-
* the answer is only ever used as "is there a relative import, and name one".
|
|
1904
|
-
*/
|
|
1905
|
-
export function firstRelativeSpecifier(source: string): string | null {
|
|
1906
|
-
const patterns = [
|
|
1907
|
-
/(?:^|\n)\s*(?:import|export)\b[^;]*?\bfrom\s*["'](\.[^"']*)["']/,
|
|
1908
|
-
/(?:^|\n)\s*import\s*["'](\.[^"']*)["']/,
|
|
1909
|
-
/\bimport\s*\(\s*["'](\.[^"']*)["']\s*\)/,
|
|
1910
|
-
/\brequire\s*\(\s*["'](\.[^"']*)["']\s*\)/,
|
|
1911
|
-
];
|
|
1912
|
-
for (const re of patterns) {
|
|
1913
|
-
const m = re.exec(source);
|
|
1914
|
-
if (m) return m[1];
|
|
1915
|
-
}
|
|
1916
|
-
return null;
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
1732
|
function publishEntryArg(rest: string[]): string | undefined {
|
|
1920
1733
|
const consumed = new Set<number>();
|
|
1921
1734
|
for (let i = 0; i < rest.length; i += 1) {
|
|
@@ -2293,7 +2106,10 @@ async function publish(rest: string[]): Promise<PackCliResult> {
|
|
|
2293
2106
|
// a bundler for a step this tool already performs for its own pack.
|
|
2294
2107
|
let entryToBuild = entry;
|
|
2295
2108
|
const needsBundle =
|
|
2296
|
-
discovered.length > 1 ||
|
|
2109
|
+
discovered.length > 1 ||
|
|
2110
|
+
/(?:^|\n)\s*(?:import|export)[^;\n]*from\s+["']\.[^"']*["']/.test(
|
|
2111
|
+
readFileSync(entry, "utf8"),
|
|
2112
|
+
);
|
|
2297
2113
|
if (needsBundle) {
|
|
2298
2114
|
const sources = discovered.length > 1 ? discovered : [entry];
|
|
2299
2115
|
const bundled = bundleEntry(sources, outDirEarly);
|
|
@@ -2313,7 +2129,6 @@ async function publish(rest: string[]): Promise<PackCliResult> {
|
|
|
2313
2129
|
...(provenance && !provenance.dirty ? ["--commit", provenance.sha] : []),
|
|
2314
2130
|
...outFlagFrom(rest),
|
|
2315
2131
|
...effectFlagFrom(rest),
|
|
2316
|
-
...minCliVersionFlagFrom(rest),
|
|
2317
2132
|
]);
|
|
2318
2133
|
if (built.exitCode !== 0) return built;
|
|
2319
2134
|
|
|
@@ -2477,18 +2292,6 @@ function effectFlagFrom(rest: string[]): string[] {
|
|
|
2477
2292
|
if (i === -1) return [];
|
|
2478
2293
|
return rest[i].includes("=") ? [rest[i]] : [rest[i], rest[i + 1]];
|
|
2479
2294
|
}
|
|
2480
|
-
/**
|
|
2481
|
-
* Same again for `--min-cli-version`, and it has to be forwarded explicitly
|
|
2482
|
-
* because `publish` hands `build` a list it assembles rather than its own
|
|
2483
|
-
* `rest`. Left off, the flag parsed, validated, refused an uncomparable value —
|
|
2484
|
-
* and then never reached the manifest, so the field the whole ordering
|
|
2485
|
-
* constraint rests on was silently absent from the published artifact.
|
|
2486
|
-
*/
|
|
2487
|
-
function minCliVersionFlagFrom(rest: string[]): string[] {
|
|
2488
|
-
const i = rest.findIndex((a) => a === "--min-cli-version" || a.startsWith("--min-cli-version="));
|
|
2489
|
-
if (i === -1) return [];
|
|
2490
|
-
return rest[i].includes("=") ? [rest[i]] : [rest[i], rest[i + 1]];
|
|
2491
|
-
}
|
|
2492
2295
|
|
|
2493
2296
|
/** `failproofai publish` — exported for bin/failproofai.mjs and the tests. */
|
|
2494
2297
|
export async function runPublishCommand(rest: string[]): Promise<PackCliResult> {
|
|
@@ -2542,35 +2345,11 @@ async function pickClis(
|
|
|
2542
2345
|
* Returns null when the user cancelled, which must not be confused with an
|
|
2543
2346
|
* empty selection: one means "install nothing", the other means "do nothing".
|
|
2544
2347
|
*/
|
|
2545
|
-
/**
|
|
2546
|
-
* How a pack's semantic half is named to a person: "3 Jev checks", never "3
|
|
2547
|
-
* semantic policies".
|
|
2548
|
-
*
|
|
2549
|
-
* They are not policies in the sense every other number on these screens uses —
|
|
2550
|
-
* nothing switches them on or off, `--policy` cannot name one, and they do not
|
|
2551
|
-
* appear in `failproofai policies`. Calling them policies would make every count
|
|
2552
|
-
* beside them read as the same kind of thing.
|
|
2553
|
-
*/
|
|
2554
|
-
function semanticPhrase(count: number): string {
|
|
2555
|
-
return `${count} Jev ${count === 1 ? "check" : "checks"}`;
|
|
2556
|
-
}
|
|
2557
|
-
|
|
2558
2348
|
async function pickFromSource(
|
|
2559
2349
|
source: string,
|
|
2560
2350
|
io: { stdin: TTYIn; stdout: TTYOut },
|
|
2561
2351
|
): Promise<string[] | null> {
|
|
2562
2352
|
const preview = await fetchPackPreview(source);
|
|
2563
|
-
// Nothing to pick. A pack of Jev questions alone has no selectable policies —
|
|
2564
|
-
// its semantic set is not switchable per entry — so the picker would draw an
|
|
2565
|
-
// empty list and ask a question with no answers. `[]` rather than `null`: the
|
|
2566
|
-
// user cancelled nothing, there was simply nothing to choose.
|
|
2567
|
-
if (preview.policies.length === 0) {
|
|
2568
|
-
io.stdout.write(
|
|
2569
|
-
`\n ${preview.id}@${preview.version} carries no selectable policies` +
|
|
2570
|
-
`${preview.semantic.length > 0 ? `, only ${semanticPhrase(preview.semantic.length)}` : ""}.\n\n`,
|
|
2571
|
-
);
|
|
2572
|
-
return [];
|
|
2573
|
-
}
|
|
2574
2353
|
const categories = [...new Set(preview.policies.map((p) => p.category))];
|
|
2575
2354
|
const rows: MultiChoice<string>[] = [];
|
|
2576
2355
|
for (const category of categories) {
|
|
@@ -2585,15 +2364,6 @@ async function pickFromSource(
|
|
|
2585
2364
|
}
|
|
2586
2365
|
}
|
|
2587
2366
|
const on = preview.policies.filter((p) => p.defaultEnabled).length;
|
|
2588
|
-
// Said BEFORE the list, because the list cannot say it: a pack's Jev checks are
|
|
2589
|
-
// not rows here, they arrive whole, and they replace the ones this build ships.
|
|
2590
|
-
// Somebody ticking boxes should know that is part of what they are agreeing to.
|
|
2591
|
-
if (preview.semantic.length > 0) {
|
|
2592
|
-
io.stdout.write(
|
|
2593
|
-
`\n This pack also brings ${semanticPhrase(preview.semantic.length)}, which are not selectable —\n` +
|
|
2594
|
-
` they replace the ones this build ships with. See: failproofai policies show ${source}\n\n`,
|
|
2595
|
-
);
|
|
2596
|
-
}
|
|
2597
2367
|
const picked = await multiSelect<string>({
|
|
2598
2368
|
message: `${preview.id}@${preview.version} — which of these should be on?`,
|
|
2599
2369
|
choices: rows,
|
|
@@ -2693,7 +2463,7 @@ async function add(rest: string[]): Promise<PackCliResult> {
|
|
|
2693
2463
|
];
|
|
2694
2464
|
const skipped = result.available.filter((n) => !result.enabled.includes(n));
|
|
2695
2465
|
|
|
2696
|
-
// Say WHY this set is on. Without it, "10 of
|
|
2466
|
+
// Say WHY this set is on. Without it, "10 of 38 enabled" reads like
|
|
2697
2467
|
// something went wrong rather than like the pack's own recommendation.
|
|
2698
2468
|
const why = {
|
|
2699
2469
|
defaults: "the pack's defaults",
|
|
@@ -2710,25 +2480,9 @@ async function add(rest: string[]): Promise<PackCliResult> {
|
|
|
2710
2480
|
// rendered as if something had gone missing.
|
|
2711
2481
|
lines.push(
|
|
2712
2482
|
result.enabled.length === 0
|
|
2713
|
-
?
|
|
2714
|
-
// A pack of Jev checks alone enforces through the semantic tier with no
|
|
2715
|
-
// regex policy enabled at all, and telling that user their install does
|
|
2716
|
-
// nothing is worse than saying nothing.
|
|
2717
|
-
` enabled (0/${result.available.length}, ${why}): none` +
|
|
2718
|
-
(result.semantic > 0 ? "" : " — the pack is installed and enforcing nothing")
|
|
2483
|
+
? ` enabled (0/${result.available.length}, ${why}): none — the pack is installed and enforcing nothing`
|
|
2719
2484
|
: ` enabled (${result.enabled.length}/${result.available.length}, ${why}): ${summarise(result.enabled)}`,
|
|
2720
2485
|
);
|
|
2721
|
-
// Counted separately, and without an on/off ratio, because there is none:
|
|
2722
|
-
// a pack's Jev checks arrive whole and REPLACE the ones this build ships.
|
|
2723
|
-
// Silence here was the shape of the original bug — the manifest's semantic
|
|
2724
|
-
// half was fetched, verified and then never written to `installed.json`, so
|
|
2725
|
-
// the half somebody installed did nothing and nothing said so.
|
|
2726
|
-
if (result.semantic > 0) {
|
|
2727
|
-
lines.push(
|
|
2728
|
-
` ${semanticPhrase(result.semantic)} for Jev, replacing this build's own set. ` +
|
|
2729
|
-
"They apply only where you configured Jev (`failproofai jev status`).",
|
|
2730
|
-
);
|
|
2731
|
-
}
|
|
2732
2486
|
|
|
2733
2487
|
if (skipped.length > 0) {
|
|
2734
2488
|
lines.push(` not enabled (${skipped.length}): ${summarise(skipped)}`);
|
|
@@ -2987,147 +2741,6 @@ function relativeAge(iso: string): string {
|
|
|
2987
2741
|
return `${value} ${label}${value === 1 ? "" : "s"} ago`;
|
|
2988
2742
|
}
|
|
2989
2743
|
|
|
2990
|
-
/**
|
|
2991
|
-
* Which of a pack's OWN regex policies each of its Jev checks can clear, keyed
|
|
2992
|
-
* by check name and empty for a check nothing names.
|
|
2993
|
-
*
|
|
2994
|
-
* INVERTED out of the same manifest's `reviewedBy` rather than declared beside
|
|
2995
|
-
* the checks. A semantic entry says nothing about the policies that name it, so
|
|
2996
|
-
* the only other way to show this column is a second list kept by hand — which
|
|
2997
|
-
* is wrong the first time a publisher adds a reviewer to one policy, and wrong
|
|
2998
|
-
* in the direction that overstates what Jev may clear.
|
|
2999
|
-
*
|
|
3000
|
-
* Judged with `resolvePolicyAuthority` against the pack's OWN check names,
|
|
3001
|
-
* because that is the rule the installing machine applies: a pack declaring
|
|
3002
|
-
* semantic entries replaces the compiled-in set (`effectiveReviewerNames`), so
|
|
3003
|
-
* its `reviewedBy` may name only its own checks, and a declaration that names
|
|
3004
|
-
* anything else — or one sitting under `authority: "hard"`, which a manifest may
|
|
3005
|
-
* carry because the two fields are parsed independently — registers HARD. A
|
|
3006
|
-
* policy in either state can never be cleared, so naming it under a check would
|
|
3007
|
-
* promise a clear that cannot happen.
|
|
3008
|
-
*/
|
|
3009
|
-
function reviewersByCheck(
|
|
3010
|
-
policies: ReadonlyArray<PolicyCatalogEntry>,
|
|
3011
|
-
semantic: ReadonlyArray<SemanticManifestEntry>,
|
|
3012
|
-
): Map<string, string[]> {
|
|
3013
|
-
const ownChecks = new Set(semantic.map((s) => s.name));
|
|
3014
|
-
const byCheck = new Map<string, string[]>(semantic.map((s) => [s.name, []]));
|
|
3015
|
-
for (const policy of policies) {
|
|
3016
|
-
const { authority, reviewedBy } = resolvePolicyAuthority(policy, ownChecks);
|
|
3017
|
-
if (authority !== "reviewable") continue;
|
|
3018
|
-
for (const check of reviewedBy ?? []) byCheck.get(check)?.push(policy.name);
|
|
3019
|
-
}
|
|
3020
|
-
return byCheck;
|
|
3021
|
-
}
|
|
3022
|
-
|
|
3023
|
-
/** Reviewers named in full. Past this the cell names two and counts the rest. */
|
|
3024
|
-
const REVIEWERS_LISTED = 3;
|
|
3025
|
-
|
|
3026
|
-
/**
|
|
3027
|
-
* A check's reviewers as one cell.
|
|
3028
|
-
*
|
|
3029
|
-
* The question a reader brings to this column is "can this check clear anything
|
|
3030
|
-
* of mine", which two names and a count answer as well as ten do — and ten
|
|
3031
|
-
* names in a cell push the column past the terminal, where `table` cuts one of
|
|
3032
|
-
* them mid-word and the row stops being readable at all.
|
|
3033
|
-
*/
|
|
3034
|
-
function reviewersCell(names: ReadonlyArray<string>): string {
|
|
3035
|
-
if (names.length <= REVIEWERS_LISTED) return names.join(", ");
|
|
3036
|
-
return `${names.slice(0, 2).join(", ")}, +${names.length - 2}`;
|
|
3037
|
-
}
|
|
3038
|
-
|
|
3039
|
-
/**
|
|
3040
|
-
* Why a check that no policy names is in the pack at all — and the two answers
|
|
3041
|
-
* are different facts, so the row has to tell them apart.
|
|
3042
|
-
*
|
|
3043
|
-
* A `deny` check with no reviewer is a concern the pack's regex half does not
|
|
3044
|
-
* cover: it can only ever ADD a deny, never clear one, and that is a gap in the
|
|
3045
|
-
* regex tier rather than a mistake in the manifest. An `instruct` check can
|
|
3046
|
-
* never answer deny, so every conjunction naming it has one reachable outcome —
|
|
3047
|
-
* a clear — which is why nothing names it and why nothing should (the
|
|
3048
|
-
* `block-work-on-main` entry in CHANGELOG 1.0.7-beta.1 is that bug, shipped).
|
|
3049
|
-
*/
|
|
3050
|
-
function unreviewedCell(mode: SemanticManifestEntry["mode"], policies: number): string {
|
|
3051
|
-
if (mode === "instruct") return "instruct-only — it can never deny";
|
|
3052
|
-
// "the 38" borrows the count from the heading above, where it has just been
|
|
3053
|
-
// read. The other two spellings exist because that sentence has no grammatical
|
|
3054
|
-
// form at one or zero — and a pack of Jev checks alone, with no regex half at
|
|
3055
|
-
// all, is a legitimate thing to publish.
|
|
3056
|
-
if (policies === 0) return "this pack has no policies to clear";
|
|
3057
|
-
if (policies === 1) return "the one policy here does not cover this";
|
|
3058
|
-
return `nothing in the ${policies} covers this`;
|
|
3059
|
-
}
|
|
3060
|
-
|
|
3061
|
-
/**
|
|
3062
|
-
* A pack's Jev checks as a section of rows — the shape the category sections
|
|
3063
|
-
* use, deliberately NOT their columns.
|
|
3064
|
-
*
|
|
3065
|
-
* It replaces a prose paragraph that listed sixteen names comma-separated above
|
|
3066
|
-
* a screen where everything else was a scannable row. The rows are not policy
|
|
3067
|
-
* rows and must not read as ones: there is no on/off chip, because nothing
|
|
3068
|
-
* toggles a check, `--policy` cannot name one, and none of them appears in
|
|
3069
|
-
* `failproofai policies`. What the columns carry instead is the pair of facts
|
|
3070
|
-
* that decide what a check does on a real machine — its mode, and which of this
|
|
3071
|
-
* pack's own policies it can clear.
|
|
3072
|
-
*
|
|
3073
|
-
* Null when the pack declares none, so a pack with no semantic half prints no
|
|
3074
|
-
* section rather than an empty one.
|
|
3075
|
-
*/
|
|
3076
|
-
export function jevChecksSection(
|
|
3077
|
-
pack: {
|
|
3078
|
-
policies: ReadonlyArray<PolicyCatalogEntry>;
|
|
3079
|
-
semantic: ReadonlyArray<SemanticManifestEntry>;
|
|
3080
|
-
},
|
|
3081
|
-
opts?: RenderOpts,
|
|
3082
|
-
): string[] | null {
|
|
3083
|
-
if (pack.semantic.length === 0) return null;
|
|
3084
|
-
const reviewers = reviewersByCheck(pack.policies, pack.semantic);
|
|
3085
|
-
const rows = pack.semantic.map((check) => {
|
|
3086
|
-
const named = reviewers.get(check.name) ?? [];
|
|
3087
|
-
// `reviews` and `—` lead the same cell rather than sitting in a column of
|
|
3088
|
-
// their own. A fourth column would pad every `—` out to the width of
|
|
3089
|
-
// `reviews`, which costs nine characters of the only column allowed to
|
|
3090
|
-
// shrink — enough to ellipsize the reason on an 80-column terminal, where
|
|
3091
|
-
// this way everything fits whole.
|
|
3092
|
-
return [
|
|
3093
|
-
check.mode,
|
|
3094
|
-
check.name,
|
|
3095
|
-
named.length > 0
|
|
3096
|
-
? `reviews ${reviewersCell(named)}`
|
|
3097
|
-
: `— ${unreviewedCell(check.mode, pack.policies.length)}`,
|
|
3098
|
-
];
|
|
3099
|
-
});
|
|
3100
|
-
return [
|
|
3101
|
-
// The count is on the heading rather than in a row, the way every other
|
|
3102
|
-
// section on this screen carries its own total.
|
|
3103
|
-
...rule(
|
|
3104
|
-
`Jev checks — ${pack.semantic.length} · not selectable · only where Jev is configured`,
|
|
3105
|
-
opts,
|
|
3106
|
-
),
|
|
3107
|
-
// The reviewers column is the only one allowed to shrink: a mode or a check
|
|
3108
|
-
// name cut in half is a fact nobody can act on, while a cut reviewer list is
|
|
3109
|
-
// one that was already abbreviated.
|
|
3110
|
-
...table({ head: ["", "", ""], rows, protect: [0, 1] }, opts),
|
|
3111
|
-
"",
|
|
3112
|
-
...note(
|
|
3113
|
-
"Nothing toggles them: `--policy` cannot name one, `failproofai policies` never lists them, " +
|
|
3114
|
-
"and a pack's checks replace the ones this build ships with.",
|
|
3115
|
-
opts,
|
|
3116
|
-
),
|
|
3117
|
-
...note(
|
|
3118
|
-
// A pack of Jev checks alone has no regex half for the column to be about,
|
|
3119
|
-
// so the sentence says what IS true of it rather than "its own 0 policies".
|
|
3120
|
-
pack.policies.length === 0
|
|
3121
|
-
? "This pack ships no regex policies, so none of these checks clears anything here: a deny " +
|
|
3122
|
-
"check can only ADD a deny, and an instruct check only ever warns."
|
|
3123
|
-
: `\`reviews\` is which of this pack's own ${pack.policies.length} policies each check can clear. ` +
|
|
3124
|
-
"A deny check that reviews nothing can only ever ADD a deny; an instruct check never answers " +
|
|
3125
|
-
"deny, so nothing pairs with one.",
|
|
3126
|
-
opts,
|
|
3127
|
-
),
|
|
3128
|
-
];
|
|
3129
|
-
}
|
|
3130
|
-
|
|
3131
2744
|
async function listRemote(source: string): Promise<PackCliResult> {
|
|
3132
2745
|
const opts = optsFor(process.stdout);
|
|
3133
2746
|
let preview;
|
|
@@ -3161,8 +2774,7 @@ async function listRemote(source: string): Promise<PackCliResult> {
|
|
|
3161
2774
|
stack(
|
|
3162
2775
|
title(
|
|
3163
2776
|
`${preview.id}@${preview.version}`,
|
|
3164
|
-
`${preview.policies.length} policies · ${categories.length} categories
|
|
3165
|
-
(preview.semantic.length > 0 ? ` · ${semanticPhrase(preview.semantic.length)}` : ""),
|
|
2777
|
+
`${preview.policies.length} policies · ${categories.length} categories`,
|
|
3166
2778
|
opts,
|
|
3167
2779
|
),
|
|
3168
2780
|
note(
|
|
@@ -3170,13 +2782,8 @@ async function listRemote(source: string): Promise<PackCliResult> {
|
|
|
3170
2782
|
(preview.effect === "observe" ? " This pack OBSERVES — it records and blocks nothing." : ""),
|
|
3171
2783
|
opts,
|
|
3172
2784
|
),
|
|
3173
|
-
preview.minCliVersion ? note(`Requires failproofai ${preview.minCliVersion} or newer.`, opts) : null,
|
|
3174
2785
|
preview.resolvedFromLatest ? note(`Newest release: ${preview.source}`, opts) : null,
|
|
3175
2786
|
table({ head: ["", "", ""], rows }, opts),
|
|
3176
|
-
// The half the table above cannot hold: its own section, under the
|
|
3177
|
-
// policies rather than above them, because a check is read against the
|
|
3178
|
-
// policies it can clear and those are the rows just passed.
|
|
3179
|
-
jevChecksSection(preview, opts),
|
|
3180
2787
|
nextStep(`failproofai policies add ${source}`, "Install the defaults with:", opts),
|
|
3181
2788
|
note("Or take part of it: --policy <a,b>, --category <x,y>, --all", opts),
|
|
3182
2789
|
),
|