@deftai/directive-core 0.88.0 → 0.89.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/dist/authz/actions.d.ts +55 -0
- package/dist/authz/actions.js +125 -0
- package/dist/authz/classify.d.ts +23 -0
- package/dist/authz/classify.js +217 -0
- package/dist/authz/closed-verb.d.ts +42 -0
- package/dist/authz/closed-verb.js +279 -0
- package/dist/authz/evaluate.d.ts +41 -0
- package/dist/authz/evaluate.js +298 -0
- package/dist/authz/index.d.ts +15 -0
- package/dist/authz/index.js +15 -0
- package/dist/authz/origin.d.ts +28 -0
- package/dist/authz/origin.js +64 -0
- package/dist/authz/paths.d.ts +6 -0
- package/dist/authz/paths.js +19 -0
- package/dist/authz/store.d.ts +40 -0
- package/dist/authz/store.js +317 -0
- package/dist/authz/templates.d.ts +139 -0
- package/dist/authz/templates.js +241 -0
- package/dist/authz/types.d.ts +111 -0
- package/dist/authz/types.js +41 -0
- package/dist/authz/verb-classification.d.ts +44 -0
- package/dist/authz/verb-classification.js +237 -0
- package/dist/branch/evaluate.js +6 -1
- package/dist/cache/fetch.js +10 -5
- package/dist/cache/io.d.ts +9 -2
- package/dist/cache/io.js +30 -10
- package/dist/cache/task-cache/store.js +11 -7
- package/dist/capacity/backfill.js +10 -4
- package/dist/category-b-namespace/index.js +10 -4
- package/dist/codebase/default-extractor.js +3 -0
- package/dist/codebase/map.js +11 -4
- package/dist/doctor/doctor-state.js +28 -4
- package/dist/doctor/main.d.ts +10 -0
- package/dist/doctor/main.js +160 -0
- package/dist/doctor/types.d.ts +11 -0
- package/dist/escalation/actions.d.ts +63 -0
- package/dist/escalation/actions.js +137 -0
- package/dist/escalation/index.d.ts +8 -0
- package/dist/escalation/index.js +8 -0
- package/dist/escalation/paths.d.ts +3 -0
- package/dist/escalation/paths.js +10 -0
- package/dist/escalation/store.d.ts +17 -0
- package/dist/escalation/store.js +172 -0
- package/dist/escalation/types.d.ts +73 -0
- package/dist/escalation/types.js +43 -0
- package/dist/eval/crud-telemetry.js +30 -10
- package/dist/eval/health.js +22 -7
- package/dist/eval/readback.js +11 -10
- package/dist/eval/run.js +32 -16
- package/dist/events/attribution-enrichment.js +10 -4
- package/dist/finish-loop/directive-finish-loop.d.ts +37 -0
- package/dist/finish-loop/directive-finish-loop.js +239 -0
- package/dist/finish-loop/grant-gate.d.ts +31 -0
- package/dist/finish-loop/grant-gate.js +169 -0
- package/dist/finish-loop/index.d.ts +11 -0
- package/dist/finish-loop/index.js +11 -0
- package/dist/finish-loop/main.d.ts +35 -0
- package/dist/finish-loop/main.js +365 -0
- package/dist/finish-loop/pr-finish-loop.d.ts +34 -0
- package/dist/finish-loop/pr-finish-loop.js +217 -0
- package/dist/finish-loop/progress.d.ts +17 -0
- package/dist/finish-loop/progress.js +45 -0
- package/dist/finish-loop/queue.d.ts +16 -0
- package/dist/finish-loop/queue.js +65 -0
- package/dist/finish-loop/types.d.ts +52 -0
- package/dist/finish-loop/types.js +10 -0
- package/dist/fs/contained-write.d.ts +101 -0
- package/dist/fs/contained-write.js +281 -0
- package/dist/hooks/classify/classify.d.ts +10 -0
- package/dist/hooks/classify/classify.js +37 -0
- package/dist/hooks/classify/index.d.ts +14 -0
- package/dist/hooks/classify/index.js +13 -0
- package/dist/hooks/classify/paths.d.ts +22 -0
- package/dist/hooks/classify/paths.js +72 -0
- package/dist/hooks/classify/payload.d.ts +16 -0
- package/dist/hooks/classify/payload.js +45 -0
- package/dist/hooks/classify/stdin.d.ts +12 -0
- package/dist/hooks/classify/stdin.js +63 -0
- package/dist/hooks/classify/tool-name.d.ts +18 -0
- package/dist/hooks/classify/tool-name.js +85 -0
- package/dist/hooks/classify/types.d.ts +41 -0
- package/dist/hooks/classify/types.js +7 -0
- package/dist/hooks/dispatcher.d.ts +21 -18
- package/dist/hooks/dispatcher.js +212 -163
- package/dist/hooks/fixtures/cases.d.ts +44 -0
- package/dist/hooks/fixtures/cases.js +595 -0
- package/dist/hooks/fixtures/index.d.ts +2 -0
- package/dist/hooks/fixtures/index.js +2 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/init-deposit/agent-hooks.js +27 -8
- package/dist/init-deposit/gitignore.js +11 -3
- package/dist/init-deposit/headless-manifest.js +12 -4
- package/dist/init-deposit/migrate.d.ts +1 -1
- package/dist/init-deposit/migrate.js +13 -3
- package/dist/init-deposit/prettierignore.js +10 -3
- package/dist/init-deposit/scaffold.js +35 -26
- package/dist/init-deposit/xbrief-projections.js +11 -4
- package/dist/intake/candidates-log.js +46 -35
- package/dist/intake/github-body-cli.d.ts +6 -0
- package/dist/intake/github-body-cli.js +17 -0
- package/dist/intake/github-body.d.ts +46 -3
- package/dist/intake/github-body.js +191 -20
- package/dist/intake/issue-emit.d.ts +1 -0
- package/dist/intake/issue-emit.js +41 -23
- package/dist/lifecycle/event-detect.js +12 -4
- package/dist/lifecycle/events.js +29 -20
- package/dist/lifecycle/lifecycle-hygiene.js +9 -3
- package/dist/packs/pack-render.js +19 -6
- package/dist/plan-sequence/store.js +11 -4
- package/dist/policy/hotfix-criteria.d.ts +79 -0
- package/dist/policy/hotfix-criteria.js +197 -0
- package/dist/policy/index.d.ts +4 -0
- package/dist/policy/index.js +31 -1
- package/dist/policy/intent-ceiling.d.ts +79 -0
- package/dist/policy/intent-ceiling.js +181 -0
- package/dist/policy/no-deft-directive.js +10 -3
- package/dist/policy/org-force-on-migration.js +9 -5
- package/dist/policy/require-human-merge.d.ts +75 -0
- package/dist/policy/require-human-merge.js +324 -0
- package/dist/policy/resolve.js +17 -6
- package/dist/policy/runtime-authority.d.ts +15 -2
- package/dist/policy/runtime-authority.js +23 -3
- package/dist/policy/write-fence.d.ts +78 -0
- package/dist/policy/write-fence.js +164 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +10 -0
- package/dist/pr-wait-mergeable/cascade.js +28 -0
- package/dist/preflight/evaluate.js +10 -0
- package/dist/product-signal/consent.js +21 -5
- package/dist/product-signal/submit.js +22 -12
- package/dist/release/build-dist-runner.js +5 -2
- package/dist/release/build-dist.d.ts +19 -1
- package/dist/release/build-dist.js +50 -2
- package/dist/release/native-steps.js +7 -2
- package/dist/release-publish/pipeline.d.ts +13 -0
- package/dist/release-publish/pipeline.js +46 -1
- package/dist/scope/audit-log.js +19 -24
- package/dist/scope/decompose.js +10 -5
- package/dist/scope/decomposed-refs.js +18 -3
- package/dist/scope/demote.js +9 -2
- package/dist/scope/undo.js +9 -2
- package/dist/session/release-availability.js +26 -6
- package/dist/session/ritual-sentinel.js +19 -12
- package/dist/session/session-start.js +7 -0
- package/dist/staleness-tickler/state.js +26 -6
- package/dist/swarm/launch.js +13 -3
- package/dist/swarm/routing.js +9 -3
- package/dist/task-surface/index.js +24 -9
- package/dist/triage/bootstrap/gitignore.js +53 -10
- package/dist/triage/cache-path.js +10 -3
- package/dist/triage/welcome/summary.js +11 -4
- package/dist/triage/welcome/writers.js +45 -16
- package/dist/validate-content/validate-links.js +5 -0
- package/dist/value/readback.js +11 -6
- package/dist/vbrief-activate/activate.js +12 -4
- package/dist/vbrief-build/project-definition-io.js +14 -6
- package/dist/verify-source/contained-writes.d.ts +59 -0
- package/dist/verify-source/contained-writes.js +272 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/openclaw-tier1.js +14 -1
- package/dist/verify-source/verify-stubs.js +3 -0
- package/dist/xbrief-migrate/migrate-project.js +26 -12
- package/dist/xbrief-migrate/transforms.d.ts +4 -2
- package/dist/xbrief-migrate/transforms.js +37 -14
- package/package.json +15 -3
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { sortKeysDeep } from "../codebase/json.js";
|
|
4
4
|
import { readCorePackageVersion } from "../engine-version.js";
|
|
5
|
-
import {
|
|
5
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
6
6
|
import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
|
|
7
7
|
import { isNoDeftDirectivePresent } from "./no-deft-directive.js";
|
|
8
8
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
@@ -88,9 +88,13 @@ export function readOrgForceOnMarker(projectRoot) {
|
|
|
88
88
|
}
|
|
89
89
|
function writeOrgForceOnMarker(projectRoot, marker) {
|
|
90
90
|
const path = markerPath(projectRoot);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
// #2951 Phase 2: product write sink routes through containedWrite.
|
|
92
|
+
containedWrite({
|
|
93
|
+
root: resolve(projectRoot),
|
|
94
|
+
target: path,
|
|
95
|
+
data: `${JSON.stringify(marker, null, 2)}\n`,
|
|
96
|
+
mode: "replace",
|
|
97
|
+
});
|
|
94
98
|
}
|
|
95
99
|
function readTypedBoolean(raw, key, fallback) {
|
|
96
100
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human merge gate (#1193 R3 / Wave 2 of #2948).
|
|
3
|
+
*
|
|
4
|
+
* When requireHumanMerge is true, agents may open PRs but must not merge.
|
|
5
|
+
* Three enforcement surfaces (mirror #747):
|
|
6
|
+
* (1) merge preflight refuses agent-initiated merge
|
|
7
|
+
* (2) verify:branch aggregate surfaces the policy
|
|
8
|
+
* (3) branch-protection / setup docs require ≥1 human reviewer
|
|
9
|
+
*
|
|
10
|
+
* Override: `task policy:allow-bot-merge -- --confirm` + `DEFT_ALLOW_BOT_MERGE=1`.
|
|
11
|
+
*/
|
|
12
|
+
export declare const FIELD_REQUIRE_HUMAN_MERGE = "plan.policy.requireHumanMerge";
|
|
13
|
+
export declare const FIELD_REQUIRE_HUMAN_MERGE_CLI_ALIAS = "requireHumanMerge";
|
|
14
|
+
export declare const FIELD_AUTO_DEPLOY_ON_MERGE = "plan.policy.autoDeployOnMerge";
|
|
15
|
+
/** Emergency env-var bypass for agent merge (#1193). */
|
|
16
|
+
export declare const ENV_ALLOW_BOT_MERGE = "DEFT_ALLOW_BOT_MERGE";
|
|
17
|
+
/** Legacy narrative key recognized at read time with deprecation warning. */
|
|
18
|
+
export declare const LEGACY_REQUIRE_HUMAN_MERGE_KEY = "Require human merge";
|
|
19
|
+
export type HumanMergeSource = "typed" | "legacy-narrative" | "auto-deploy-default" | "env-bypass" | "default";
|
|
20
|
+
export interface HumanMergePolicyResult {
|
|
21
|
+
/** True when agents must not merge. */
|
|
22
|
+
readonly requireHumanMerge: boolean;
|
|
23
|
+
readonly source: HumanMergeSource;
|
|
24
|
+
readonly deprecationWarning: string | null;
|
|
25
|
+
readonly error: string | null;
|
|
26
|
+
readonly autoDeployOnMerge: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Resolve effective human-merge policy (#1193).
|
|
30
|
+
*
|
|
31
|
+
* Order:
|
|
32
|
+
* 1. DEFT_ALLOW_BOT_MERGE=1 → requireHumanMerge effective false (env-bypass)
|
|
33
|
+
* 2. typed plan.policy.requireHumanMerge
|
|
34
|
+
* 3. legacy narrative "Require human merge"
|
|
35
|
+
* 4. autoDeployOnMerge true → default requireHumanMerge true
|
|
36
|
+
* 5. default false (no auto-deploy → agents may merge when other gates allow)
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveHumanMergePolicy(projectRoot: string, env?: NodeJS.ProcessEnv): HumanMergePolicyResult;
|
|
39
|
+
/** Session-start / AGENTS disclosure line when the human merge gate is ON. */
|
|
40
|
+
export declare function humanMergeDisclosureLine(result: HumanMergePolicyResult): string | null;
|
|
41
|
+
export interface AgentMergeEvaluateResult {
|
|
42
|
+
readonly exitCode: 0 | 1;
|
|
43
|
+
readonly allowed: boolean;
|
|
44
|
+
readonly message: string;
|
|
45
|
+
readonly policy: HumanMergePolicyResult;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Merge preflight: refuse agent-initiated merge when requireHumanMerge is true.
|
|
49
|
+
* Surface (1) of the three-surface enforcement pattern.
|
|
50
|
+
*/
|
|
51
|
+
export declare function evaluateAgentMerge(projectRoot: string, env?: NodeJS.ProcessEnv): AgentMergeEvaluateResult;
|
|
52
|
+
/**
|
|
53
|
+
* verify:branch aggregate surface (2): advisory line when gate is ON.
|
|
54
|
+
* Does not fail the branch gate on feature branches — merge refuse is surface (1).
|
|
55
|
+
*/
|
|
56
|
+
export declare function humanMergeBranchNote(result: HumanMergePolicyResult): string | null;
|
|
57
|
+
export interface HumanMergePolicyField {
|
|
58
|
+
readonly name: string;
|
|
59
|
+
readonly current: boolean;
|
|
60
|
+
readonly default: boolean;
|
|
61
|
+
readonly source: string;
|
|
62
|
+
}
|
|
63
|
+
/** Inspector for policy:show --field=requireHumanMerge. */
|
|
64
|
+
export declare function inspectRequireHumanMerge(data: Record<string, unknown> | null, projectRoot?: string): HumanMergePolicyField;
|
|
65
|
+
export declare const ALLOW_BOT_MERGE_CAPABILITY_COST: string;
|
|
66
|
+
/** Write requireHumanMerge=false (allow bot merge) with audit trail. */
|
|
67
|
+
export declare function setRequireHumanMerge(projectRoot: string, options: {
|
|
68
|
+
requireHumanMerge: boolean;
|
|
69
|
+
actor?: string;
|
|
70
|
+
note?: string;
|
|
71
|
+
}): {
|
|
72
|
+
changed: boolean;
|
|
73
|
+
auditEntry: string;
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=require-human-merge.d.ts.map
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human merge gate (#1193 R3 / Wave 2 of #2948).
|
|
3
|
+
*
|
|
4
|
+
* When requireHumanMerge is true, agents may open PRs but must not merge.
|
|
5
|
+
* Three enforcement surfaces (mirror #747):
|
|
6
|
+
* (1) merge preflight refuses agent-initiated merge
|
|
7
|
+
* (2) verify:branch aggregate surfaces the policy
|
|
8
|
+
* (3) branch-protection / setup docs require ≥1 human reviewer
|
|
9
|
+
*
|
|
10
|
+
* Override: `task policy:allow-bot-merge -- --confirm` + `DEFT_ALLOW_BOT_MERGE=1`.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
|
|
14
|
+
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
15
|
+
import { policyColonInvocation } from "./policy-invocation.js";
|
|
16
|
+
import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
|
|
17
|
+
export const FIELD_REQUIRE_HUMAN_MERGE = "plan.policy.requireHumanMerge";
|
|
18
|
+
export const FIELD_REQUIRE_HUMAN_MERGE_CLI_ALIAS = "requireHumanMerge";
|
|
19
|
+
export const FIELD_AUTO_DEPLOY_ON_MERGE = "plan.policy.autoDeployOnMerge";
|
|
20
|
+
/** Emergency env-var bypass for agent merge (#1193). */
|
|
21
|
+
export const ENV_ALLOW_BOT_MERGE = "DEFT_ALLOW_BOT_MERGE";
|
|
22
|
+
/** Legacy narrative key recognized at read time with deprecation warning. */
|
|
23
|
+
export const LEGACY_REQUIRE_HUMAN_MERGE_KEY = "Require human merge";
|
|
24
|
+
const TRUTHY = new Set(["1", "true", "yes", "on"]);
|
|
25
|
+
function envBypassActive(env = process.env) {
|
|
26
|
+
const raw = env[ENV_ALLOW_BOT_MERGE] ?? "";
|
|
27
|
+
return TRUTHY.has(raw.trim().toLowerCase());
|
|
28
|
+
}
|
|
29
|
+
function coerceLegacyNarrative(value) {
|
|
30
|
+
if (typeof value === "boolean")
|
|
31
|
+
return value;
|
|
32
|
+
if (typeof value !== "string")
|
|
33
|
+
return false;
|
|
34
|
+
const low = value.trim().toLowerCase();
|
|
35
|
+
if (["true", "yes", "on", "1"].includes(low))
|
|
36
|
+
return true;
|
|
37
|
+
if (/:\s*(true|yes|on|1)\b/.test(low))
|
|
38
|
+
return true;
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
function readAutoDeploy(policyBlock) {
|
|
42
|
+
const raw = policyBlock.autoDeployOnMerge;
|
|
43
|
+
return typeof raw === "boolean" ? raw : false;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve effective human-merge policy (#1193).
|
|
47
|
+
*
|
|
48
|
+
* Order:
|
|
49
|
+
* 1. DEFT_ALLOW_BOT_MERGE=1 → requireHumanMerge effective false (env-bypass)
|
|
50
|
+
* 2. typed plan.policy.requireHumanMerge
|
|
51
|
+
* 3. legacy narrative "Require human merge"
|
|
52
|
+
* 4. autoDeployOnMerge true → default requireHumanMerge true
|
|
53
|
+
* 5. default false (no auto-deploy → agents may merge when other gates allow)
|
|
54
|
+
*/
|
|
55
|
+
export function resolveHumanMergePolicy(projectRoot, env = process.env) {
|
|
56
|
+
if (envBypassActive(env)) {
|
|
57
|
+
return {
|
|
58
|
+
requireHumanMerge: false,
|
|
59
|
+
source: "env-bypass",
|
|
60
|
+
deprecationWarning: null,
|
|
61
|
+
error: null,
|
|
62
|
+
autoDeployOnMerge: false,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const [data, err] = loadProjectDefinition(projectRoot);
|
|
66
|
+
if (data === null) {
|
|
67
|
+
return {
|
|
68
|
+
requireHumanMerge: false,
|
|
69
|
+
source: "default",
|
|
70
|
+
deprecationWarning: null,
|
|
71
|
+
error: err,
|
|
72
|
+
autoDeployOnMerge: false,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const plan = data.plan;
|
|
76
|
+
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
77
|
+
return {
|
|
78
|
+
requireHumanMerge: false,
|
|
79
|
+
source: "default",
|
|
80
|
+
deprecationWarning: null,
|
|
81
|
+
error: "PROJECT-DEFINITION 'plan' is not an object",
|
|
82
|
+
autoDeployOnMerge: false,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const planObj = plan;
|
|
86
|
+
const policyRaw = readPlanPolicy(planObj);
|
|
87
|
+
const policyBlock = typeof policyRaw === "object" && policyRaw !== null && !Array.isArray(policyRaw)
|
|
88
|
+
? policyRaw
|
|
89
|
+
: {};
|
|
90
|
+
const autoDeployOnMerge = readAutoDeploy(policyBlock);
|
|
91
|
+
if ("requireHumanMerge" in policyBlock) {
|
|
92
|
+
const raw = policyBlock.requireHumanMerge;
|
|
93
|
+
if (typeof raw !== "boolean") {
|
|
94
|
+
return {
|
|
95
|
+
requireHumanMerge: true, // fail closed on malformed flag when present
|
|
96
|
+
source: "typed",
|
|
97
|
+
deprecationWarning: null,
|
|
98
|
+
error: `plan.policy.requireHumanMerge must be a boolean; got ${typeof raw}`,
|
|
99
|
+
autoDeployOnMerge,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
requireHumanMerge: raw,
|
|
104
|
+
source: "typed",
|
|
105
|
+
deprecationWarning: null,
|
|
106
|
+
error: null,
|
|
107
|
+
autoDeployOnMerge,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const narratives = planObj.narratives;
|
|
111
|
+
if (typeof narratives === "object" &&
|
|
112
|
+
narratives !== null &&
|
|
113
|
+
!Array.isArray(narratives) &&
|
|
114
|
+
LEGACY_REQUIRE_HUMAN_MERGE_KEY in narratives) {
|
|
115
|
+
const allow = coerceLegacyNarrative(narratives[LEGACY_REQUIRE_HUMAN_MERGE_KEY]);
|
|
116
|
+
const warn = `DEPRECATED: PROJECT-DEFINITION uses the legacy narrative key ` +
|
|
117
|
+
`'${LEGACY_REQUIRE_HUMAN_MERGE_KEY}'. Migrate to typed ` +
|
|
118
|
+
`plan.policy.requireHumanMerge (#1193).`;
|
|
119
|
+
return {
|
|
120
|
+
requireHumanMerge: allow,
|
|
121
|
+
source: "legacy-narrative",
|
|
122
|
+
deprecationWarning: warn,
|
|
123
|
+
error: null,
|
|
124
|
+
autoDeployOnMerge,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (autoDeployOnMerge) {
|
|
128
|
+
return {
|
|
129
|
+
requireHumanMerge: true,
|
|
130
|
+
source: "auto-deploy-default",
|
|
131
|
+
deprecationWarning: null,
|
|
132
|
+
error: null,
|
|
133
|
+
autoDeployOnMerge: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
requireHumanMerge: false,
|
|
138
|
+
source: "default",
|
|
139
|
+
deprecationWarning: null,
|
|
140
|
+
error: null,
|
|
141
|
+
autoDeployOnMerge: false,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** Session-start / AGENTS disclosure line when the human merge gate is ON. */
|
|
145
|
+
export function humanMergeDisclosureLine(result) {
|
|
146
|
+
if (result.source === "env-bypass") {
|
|
147
|
+
return (`[deft policy] ${ENV_ALLOW_BOT_MERGE} is set -- ` +
|
|
148
|
+
"human merge gate bypassed for this session (agent may merge).");
|
|
149
|
+
}
|
|
150
|
+
if (!result.requireHumanMerge) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return (`[deft policy] Human merge gate is ON for this project (source: ${result.source}); ` +
|
|
154
|
+
"agent may open PRs, may not merge.");
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Merge preflight: refuse agent-initiated merge when requireHumanMerge is true.
|
|
158
|
+
* Surface (1) of the three-surface enforcement pattern.
|
|
159
|
+
*/
|
|
160
|
+
export function evaluateAgentMerge(projectRoot, env = process.env) {
|
|
161
|
+
const policy = resolveHumanMergePolicy(projectRoot, env);
|
|
162
|
+
if (!policy.requireHumanMerge) {
|
|
163
|
+
return {
|
|
164
|
+
exitCode: 0,
|
|
165
|
+
allowed: true,
|
|
166
|
+
message: policy.source === "env-bypass"
|
|
167
|
+
? `✓ human-merge gate: ${ENV_ALLOW_BOT_MERGE}=1 bypass -- agent merge allowed.`
|
|
168
|
+
: "✓ human-merge gate: requireHumanMerge is off -- agent merge allowed by policy.",
|
|
169
|
+
policy,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const parts = [
|
|
173
|
+
"❌ deft human-merge gate: refusing agent-initiated merge (#1193).",
|
|
174
|
+
"",
|
|
175
|
+
` Source: policy=${policy.source}`,
|
|
176
|
+
" plan.policy.requireHumanMerge is true — agents may open PRs, may not merge.",
|
|
177
|
+
];
|
|
178
|
+
if (policy.error !== null) {
|
|
179
|
+
parts.push(` Error: ${policy.error}`);
|
|
180
|
+
}
|
|
181
|
+
if (policy.deprecationWarning !== null) {
|
|
182
|
+
parts.push(` Note: ${policy.deprecationWarning}`);
|
|
183
|
+
}
|
|
184
|
+
parts.push("", " How to proceed:", " • Ask a human to review and merge the PR.", " • Or opt out via the typed surface (capability cost):", ` ${policyColonInvocation("allow-bot-merge", " -- --confirm")}`, ` • Or set the emergency-escape env-var: ${ENV_ALLOW_BOT_MERGE}=1`);
|
|
185
|
+
return {
|
|
186
|
+
exitCode: 1,
|
|
187
|
+
allowed: false,
|
|
188
|
+
message: parts.join("\n"),
|
|
189
|
+
policy,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* verify:branch aggregate surface (2): advisory line when gate is ON.
|
|
194
|
+
* Does not fail the branch gate on feature branches — merge refuse is surface (1).
|
|
195
|
+
*/
|
|
196
|
+
export function humanMergeBranchNote(result) {
|
|
197
|
+
if (result.source === "env-bypass") {
|
|
198
|
+
return humanMergeDisclosureLine(result);
|
|
199
|
+
}
|
|
200
|
+
if (!result.requireHumanMerge)
|
|
201
|
+
return null;
|
|
202
|
+
return (`ℹ deft human-merge: requireHumanMerge=true (source: ${result.source}) — ` +
|
|
203
|
+
"agent-initiated merge will be refused; human must merge.");
|
|
204
|
+
}
|
|
205
|
+
/** Inspector for policy:show --field=requireHumanMerge. */
|
|
206
|
+
export function inspectRequireHumanMerge(data, projectRoot) {
|
|
207
|
+
if (projectRoot !== undefined && projectRoot.length > 0) {
|
|
208
|
+
const resolved = resolveHumanMergePolicy(projectRoot);
|
|
209
|
+
return {
|
|
210
|
+
name: FIELD_REQUIRE_HUMAN_MERGE,
|
|
211
|
+
current: resolved.requireHumanMerge,
|
|
212
|
+
default: false,
|
|
213
|
+
source: resolved.source,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (data === null) {
|
|
217
|
+
return {
|
|
218
|
+
name: FIELD_REQUIRE_HUMAN_MERGE,
|
|
219
|
+
current: false,
|
|
220
|
+
default: false,
|
|
221
|
+
source: "default",
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const policyBlock = readPlanPolicy(data.plan);
|
|
225
|
+
if (typeof policyBlock === "object" &&
|
|
226
|
+
policyBlock !== null &&
|
|
227
|
+
!Array.isArray(policyBlock) &&
|
|
228
|
+
"requireHumanMerge" in policyBlock) {
|
|
229
|
+
const raw = policyBlock.requireHumanMerge;
|
|
230
|
+
return {
|
|
231
|
+
name: FIELD_REQUIRE_HUMAN_MERGE,
|
|
232
|
+
current: typeof raw === "boolean" ? raw : true,
|
|
233
|
+
default: false,
|
|
234
|
+
source: "typed",
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (typeof policyBlock === "object" &&
|
|
238
|
+
policyBlock !== null &&
|
|
239
|
+
!Array.isArray(policyBlock) &&
|
|
240
|
+
policyBlock.autoDeployOnMerge === true) {
|
|
241
|
+
return {
|
|
242
|
+
name: FIELD_REQUIRE_HUMAN_MERGE,
|
|
243
|
+
current: true,
|
|
244
|
+
default: false,
|
|
245
|
+
source: "auto-deploy-default",
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
name: FIELD_REQUIRE_HUMAN_MERGE,
|
|
250
|
+
current: false,
|
|
251
|
+
default: false,
|
|
252
|
+
source: "default",
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
export const ALLOW_BOT_MERGE_CAPABILITY_COST = "\u26a0 Capability-cost disclosure -- allowing bot/agent merge turns OFF the " +
|
|
256
|
+
"deft human merge gate (#1193).\n" +
|
|
257
|
+
" \u2022 Agents may call `gh pr merge` / `task pr:wait-mergeable-and-merge`.\n" +
|
|
258
|
+
" \u2022 If autoDeployOnMerge is true, agent merges can reach production unattended.\n" +
|
|
259
|
+
" \u2022 Reversible: set plan.policy.requireHumanMerge=true or re-run setup.\n" +
|
|
260
|
+
" \u2022 Change is recorded to meta/policy-changes.log for auditability.";
|
|
261
|
+
/** Write requireHumanMerge=false (allow bot merge) with audit trail. */
|
|
262
|
+
export function setRequireHumanMerge(projectRoot, options) {
|
|
263
|
+
const { requireHumanMerge, actor = "agent", note = "" } = options;
|
|
264
|
+
const path = projectDefinitionPath(projectRoot);
|
|
265
|
+
if (!existsSync(path)) {
|
|
266
|
+
throw new Error(`PROJECT-DEFINITION not found at ${path}`);
|
|
267
|
+
}
|
|
268
|
+
return projectDefinitionMutationLock(projectRoot, () => {
|
|
269
|
+
const parsed = JSON.parse(readFileSync(path, { encoding: "utf8" }));
|
|
270
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
271
|
+
throw new Error(`PROJECT-DEFINITION at ${path} top-level value is not a JSON object`);
|
|
272
|
+
}
|
|
273
|
+
const data = parsed;
|
|
274
|
+
if (typeof data.plan !== "object" || data.plan === null || Array.isArray(data.plan)) {
|
|
275
|
+
if (data.plan === undefined) {
|
|
276
|
+
data.plan = {};
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
throw new Error("PROJECT-DEFINITION 'plan' is not an object");
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const plan = data.plan;
|
|
283
|
+
migrateLegacyPolicyKey(plan);
|
|
284
|
+
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
285
|
+
if (typeof existingPolicy !== "object" ||
|
|
286
|
+
existingPolicy === null ||
|
|
287
|
+
Array.isArray(existingPolicy)) {
|
|
288
|
+
if (existingPolicy === undefined) {
|
|
289
|
+
plan[PLAN_POLICY_KEY] = {};
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
throw new Error("plan.policy is not an object");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const policyBlock = plan[PLAN_POLICY_KEY];
|
|
296
|
+
const previous = policyBlock.requireHumanMerge;
|
|
297
|
+
policyBlock.requireHumanMerge = Boolean(requireHumanMerge);
|
|
298
|
+
let legacyDropped = false;
|
|
299
|
+
const narratives = plan.narratives;
|
|
300
|
+
if (typeof narratives === "object" &&
|
|
301
|
+
narratives !== null &&
|
|
302
|
+
!Array.isArray(narratives) &&
|
|
303
|
+
LEGACY_REQUIRE_HUMAN_MERGE_KEY in narratives) {
|
|
304
|
+
delete narratives[LEGACY_REQUIRE_HUMAN_MERGE_KEY];
|
|
305
|
+
legacyDropped = true;
|
|
306
|
+
}
|
|
307
|
+
atomicWriteProjectDefinition(path, data);
|
|
308
|
+
const changed = previous !== Boolean(requireHumanMerge) || legacyDropped;
|
|
309
|
+
const parts = [
|
|
310
|
+
`actor=${actor}`,
|
|
311
|
+
`requireHumanMerge=${requireHumanMerge ? "true" : "false"}`,
|
|
312
|
+
`previous=${previous === undefined ? "None" : String(previous)}`,
|
|
313
|
+
];
|
|
314
|
+
if (legacyDropped)
|
|
315
|
+
parts.push("legacy-narrative-migrated=true");
|
|
316
|
+
if (note) {
|
|
317
|
+
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
318
|
+
}
|
|
319
|
+
const auditEntry = parts.join(" ");
|
|
320
|
+
appendAuditLog(projectRoot, auditEntry);
|
|
321
|
+
return { changed, auditEntry };
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=require-human-merge.js.map
|
package/dist/policy/resolve.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve as pathResolve } from "node:path";
|
|
3
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
3
4
|
import { resolveProjectDefinitionPath } from "../layout/resolve.js";
|
|
4
5
|
import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
|
|
5
6
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
@@ -177,15 +178,25 @@ function nowIso() {
|
|
|
177
178
|
}
|
|
178
179
|
/** Append a one-line audit entry to meta/policy-changes.log (#746). */
|
|
179
180
|
export function appendAuditLog(projectRoot, entry) {
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
const root = pathResolve(projectRoot);
|
|
182
|
+
const logPath = join(root, AUDIT_LOG_REL_PATH);
|
|
183
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
183
184
|
if (!existsSync(logPath)) {
|
|
184
185
|
const header = "# meta/policy-changes.log -- audit trail for " +
|
|
185
186
|
"policy.allowDirectCommitsToMaster transitions (#746)\n";
|
|
186
|
-
|
|
187
|
+
containedWrite({
|
|
188
|
+
root,
|
|
189
|
+
target: AUDIT_LOG_REL_PATH,
|
|
190
|
+
data: header,
|
|
191
|
+
mode: "create",
|
|
192
|
+
});
|
|
187
193
|
}
|
|
188
|
-
|
|
194
|
+
containedWrite({
|
|
195
|
+
root,
|
|
196
|
+
target: AUDIT_LOG_REL_PATH,
|
|
197
|
+
data: `${nowIso()} ${entry}\n`,
|
|
198
|
+
mode: "append",
|
|
199
|
+
});
|
|
189
200
|
return logPath;
|
|
190
201
|
}
|
|
191
202
|
/** Write the typed policy flag back to PROJECT-DEFINITION (#746). */
|
|
@@ -9,6 +9,8 @@ export interface RuntimeAuthorityScopes {
|
|
|
9
9
|
/** merge / close / release — enforced only when Shell payload is classifiable (host gap). */
|
|
10
10
|
readonly merge: boolean;
|
|
11
11
|
}
|
|
12
|
+
/** Layers that contribute to a resolved write fence (#516 / #2443 / #2948 Wave 3). */
|
|
13
|
+
export type WriteFenceSource = "project" | "story";
|
|
12
14
|
export interface RuntimeAuthorityPolicy {
|
|
13
15
|
readonly enabled: boolean;
|
|
14
16
|
/** When non-empty, write targets must match at least one pattern. Empty = allow all paths. */
|
|
@@ -16,6 +18,17 @@ export interface RuntimeAuthorityPolicy {
|
|
|
16
18
|
/** Deny wins over allow. */
|
|
17
19
|
readonly denyPaths: readonly string[];
|
|
18
20
|
readonly scopes: RuntimeAuthorityScopes;
|
|
21
|
+
/**
|
|
22
|
+
* Optional story-layer allow globs from `plan.metadata.swarm.file_scope`
|
|
23
|
+
* (populated by `resolveWriteFence` only). When non-empty, path must match
|
|
24
|
+
* project allowPaths (if any) **and** this layer. Empty/undefined = no story narrowing.
|
|
25
|
+
*/
|
|
26
|
+
readonly storyAllowPaths?: readonly string[];
|
|
27
|
+
/**
|
|
28
|
+
* Which layers contributed to this policy (for stable deny reason codes).
|
|
29
|
+
* Set by `resolveWriteFence`; absent for bare project policy from disk.
|
|
30
|
+
*/
|
|
31
|
+
readonly fenceSources?: readonly WriteFenceSource[];
|
|
19
32
|
}
|
|
20
33
|
export declare const DEFAULT_RUNTIME_AUTHORITY_SCOPES: RuntimeAuthorityScopes;
|
|
21
34
|
/** Safe default: opt-in only — existing consumers see no runtime authority until enabled. */
|
|
@@ -34,8 +47,8 @@ export declare function inspectRuntimeAuthority(data: Record<string, unknown> |
|
|
|
34
47
|
export declare function loadRuntimeAuthorityPolicy(data: Record<string, unknown> | null): RuntimeAuthorityPolicy;
|
|
35
48
|
/** Load runtime authority from a project root (PROJECT-DEFINITION on disk). */
|
|
36
49
|
export declare function loadRuntimeAuthorityFromProject(projectRoot: string): RuntimeAuthorityPolicy;
|
|
37
|
-
export type RuntimeAuthorityPathVerdict = "allow" | "deny-allowlist" | "deny-denylist";
|
|
38
|
-
/** Evaluate a project-relative POSIX path against allow/deny globs. */
|
|
50
|
+
export type RuntimeAuthorityPathVerdict = "allow" | "deny-allowlist" | "deny-denylist" | "deny-story-scope";
|
|
51
|
+
/** Evaluate a project-relative POSIX path against allow/deny globs (+ optional story layer). */
|
|
39
52
|
export declare function evaluateRuntimeAuthorityPath(policy: RuntimeAuthorityPolicy, relPathPosix: string): RuntimeAuthorityPathVerdict;
|
|
40
53
|
export interface RuntimeAuthorityDirectWriteInput {
|
|
41
54
|
readonly policy: RuntimeAuthorityPolicy;
|
|
@@ -127,7 +127,7 @@ export function loadRuntimeAuthorityFromProject(projectRoot) {
|
|
|
127
127
|
const [data] = loadProjectDefinition(projectRoot);
|
|
128
128
|
return loadRuntimeAuthorityPolicy(data);
|
|
129
129
|
}
|
|
130
|
-
/** Evaluate a project-relative POSIX path against allow/deny globs. */
|
|
130
|
+
/** Evaluate a project-relative POSIX path against allow/deny globs (+ optional story layer). */
|
|
131
131
|
export function evaluateRuntimeAuthorityPath(policy, relPathPosix) {
|
|
132
132
|
if (!policy.enabled)
|
|
133
133
|
return "allow";
|
|
@@ -136,8 +136,20 @@ export function evaluateRuntimeAuthorityPath(policy, relPathPosix) {
|
|
|
136
136
|
if (policy.allowPaths.length > 0 && !matchAny(policy.allowPaths, relPathPosix)) {
|
|
137
137
|
return "deny-allowlist";
|
|
138
138
|
}
|
|
139
|
+
// Story file_scope layer (#516 / #2443): path must match when non-empty.
|
|
140
|
+
const storyAllow = policy.storyAllowPaths ?? [];
|
|
141
|
+
if (storyAllow.length > 0 && !matchAny(storyAllow, relPathPosix)) {
|
|
142
|
+
return "deny-story-scope";
|
|
143
|
+
}
|
|
139
144
|
return "allow";
|
|
140
145
|
}
|
|
146
|
+
/** Human-readable fence source label for deny reasons. */
|
|
147
|
+
function fenceSourceLabel(policy) {
|
|
148
|
+
const sources = policy.fenceSources;
|
|
149
|
+
if (sources === undefined || sources.length === 0)
|
|
150
|
+
return "project";
|
|
151
|
+
return sources.join("+");
|
|
152
|
+
}
|
|
141
153
|
/**
|
|
142
154
|
* Evaluate direct-write runtime authority after ritual/scope gates (#1394).
|
|
143
155
|
* Unclassifiable paths (null) pass — host gap documented for Shell/MCP.
|
|
@@ -164,7 +176,7 @@ export function evaluateRuntimeAuthorityDirectWrite(input) {
|
|
|
164
176
|
allowed: false,
|
|
165
177
|
code: "runtime-policy-deny-path",
|
|
166
178
|
reason: `Directive denied this direct write: path ${relPathPosix} matches ` +
|
|
167
|
-
|
|
179
|
+
`write-fence denyPaths (source: ${fenceSourceLabel(policy)}).`,
|
|
168
180
|
};
|
|
169
181
|
}
|
|
170
182
|
if (pathVerdict === "deny-allowlist") {
|
|
@@ -172,7 +184,15 @@ export function evaluateRuntimeAuthorityDirectWrite(input) {
|
|
|
172
184
|
allowed: false,
|
|
173
185
|
code: "runtime-policy-deny-path",
|
|
174
186
|
reason: `Directive denied this direct write: path ${relPathPosix} is outside ` +
|
|
175
|
-
|
|
187
|
+
`write fence project allowPaths (source: ${fenceSourceLabel(policy)}).`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (pathVerdict === "deny-story-scope") {
|
|
191
|
+
return {
|
|
192
|
+
allowed: false,
|
|
193
|
+
code: "runtime-policy-deny-path",
|
|
194
|
+
reason: `Directive denied this direct write: path ${relPathPosix} is outside ` +
|
|
195
|
+
`write fence story file_scope (source: ${fenceSourceLabel(policy)}).`,
|
|
176
196
|
};
|
|
177
197
|
}
|
|
178
198
|
return { allowed: true, reason: null, code: null };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified path write fence (#516 / #2443 / #2948 Wave 3).
|
|
3
|
+
*
|
|
4
|
+
* Single evaluation SoT: path checks always go through
|
|
5
|
+
* `evaluateRuntimeAuthorityPath` / `evaluateRuntimeAuthorityDirectWrite`.
|
|
6
|
+
* This module only **resolves** a RuntimeAuthorityPolicy by intersecting
|
|
7
|
+
* project `runtimeAuthority.allowPaths` / `denyPaths` with optional story
|
|
8
|
+
* `plan.metadata.swarm.file_scope` (and a read-time `writeScope` alias).
|
|
9
|
+
*
|
|
10
|
+
* There is no second independent writeScope evaluation engine.
|
|
11
|
+
*/
|
|
12
|
+
import { type RuntimeAuthorityPolicy, type WriteFenceSource } from "./runtime-authority.js";
|
|
13
|
+
/**
|
|
14
|
+
* Normalize a legacy #2443 `writeScope` alias into `file_scope` + deny globs.
|
|
15
|
+
* Accepts:
|
|
16
|
+
* - `string[]` → allow list
|
|
17
|
+
* - `{ allow?: string[], deny?: string[] }`
|
|
18
|
+
* - `{ file_scope?: string[], denyPaths?: string[] }` (already-normalized shape)
|
|
19
|
+
*
|
|
20
|
+
* Callers MUST feed the result into {@link resolveWriteFence}; do not evaluate
|
|
21
|
+
* writeScope with a separate matcher.
|
|
22
|
+
*/
|
|
23
|
+
export declare function normalizeStoryWriteScope(raw: unknown): {
|
|
24
|
+
readonly fileScope: string[];
|
|
25
|
+
readonly denyPaths: string[];
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Extract story write fence from an xBRIEF/vBRIEF document.
|
|
29
|
+
* Prefer `plan.metadata.swarm.file_scope`; fall back to normalized `writeScope`
|
|
30
|
+
* on `plan.metadata.swarm` or `plan.metadata` (read-time alias only).
|
|
31
|
+
*/
|
|
32
|
+
export declare function extractStoryFileScope(storyData: unknown): {
|
|
33
|
+
readonly fileScope: string[];
|
|
34
|
+
readonly denyPaths: string[];
|
|
35
|
+
};
|
|
36
|
+
/** Load story fence from an active xBRIEF path (fail-open on IO/parse errors). */
|
|
37
|
+
export declare function loadStoryWriteFenceFromPath(scopePath: string | null | undefined): {
|
|
38
|
+
readonly fileScope: string[];
|
|
39
|
+
readonly denyPaths: string[];
|
|
40
|
+
};
|
|
41
|
+
export interface ResolveWriteFenceOptions {
|
|
42
|
+
/** Extra deny globs from normalized writeScope.deny (merged; deny always wins). */
|
|
43
|
+
readonly storyDenyPaths?: readonly string[] | null;
|
|
44
|
+
}
|
|
45
|
+
export interface ResolvedWriteFence {
|
|
46
|
+
/**
|
|
47
|
+
* Policy for `evaluateRuntimeAuthorityPath` / `evaluateRuntimeAuthorityDirectWrite`.
|
|
48
|
+
* This is the only evaluation shape — no parallel engine.
|
|
49
|
+
*/
|
|
50
|
+
readonly policy: RuntimeAuthorityPolicy;
|
|
51
|
+
/** Layers that contribute to this fence (empty when inactive). */
|
|
52
|
+
readonly sources: readonly WriteFenceSource[];
|
|
53
|
+
/** True when project and/or story path fence is active. */
|
|
54
|
+
readonly fenceActive: boolean;
|
|
55
|
+
readonly storyAllowPaths: readonly string[];
|
|
56
|
+
readonly storyDenyPaths: readonly string[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Intersect project `runtimeAuthority` with optional story `file_scope`.
|
|
60
|
+
*
|
|
61
|
+
* Rules:
|
|
62
|
+
* - Empty project `allowPaths` when enabled = all paths until story narrows.
|
|
63
|
+
* - Empty story scope = project policy only.
|
|
64
|
+
* - Story `file_scope` alone enables a fence even when project policy is disabled.
|
|
65
|
+
* - `denyPaths` always win (project + story-normalized denys).
|
|
66
|
+
* - When both project allowPaths and story file_scope are non-empty, a path must
|
|
67
|
+
* match **both** layers (AND / intersection semantics via dual allow layers).
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveWriteFence(projectPolicy: RuntimeAuthorityPolicy, storyFileScope?: readonly string[] | null, options?: ResolveWriteFenceOptions): ResolvedWriteFence;
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the write fence for PreToolUse / product sinks.
|
|
72
|
+
* Composes project policy with story fence extracted from an active xBRIEF path.
|
|
73
|
+
*/
|
|
74
|
+
export declare function resolveWriteFenceForScope(projectPolicy: RuntimeAuthorityPolicy, scopePath: string | null | undefined, loadStory?: (path: string | null | undefined) => {
|
|
75
|
+
fileScope: readonly string[];
|
|
76
|
+
denyPaths: readonly string[];
|
|
77
|
+
}): ResolvedWriteFence;
|
|
78
|
+
//# sourceMappingURL=write-fence.d.ts.map
|