@deftai/directive-core 0.105.0 → 0.107.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/classify.js +591 -34
- package/dist/check/gate-lists.js +2 -0
- package/dist/check/named-cause.js +1 -1
- package/dist/design-critique/exclusive-chip.d.ts +33 -0
- package/dist/design-critique/exclusive-chip.js +68 -0
- package/dist/design-critique/parent-audit.d.ts +59 -0
- package/dist/design-critique/parent-audit.js +121 -0
- package/dist/doctor/checks.d.ts +6 -0
- package/dist/doctor/checks.js +35 -0
- package/dist/forward-coverage/diff-coverage.d.ts +39 -0
- package/dist/forward-coverage/diff-coverage.js +112 -0
- package/dist/forward-coverage/diff-lines.d.ts +19 -0
- package/dist/forward-coverage/diff-lines.js +173 -0
- package/dist/forward-coverage/evaluate.d.ts +24 -9
- package/dist/forward-coverage/evaluate.js +125 -19
- package/dist/hooks/dest-form.d.ts +46 -0
- package/dist/hooks/dest-form.js +575 -0
- package/dist/hooks/dispatcher.d.ts +17 -1
- package/dist/hooks/dispatcher.js +82 -11
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +1 -1
- package/dist/intake/clause-derivation.d.ts +9 -0
- package/dist/intake/clause-derivation.js +30 -3
- package/dist/intake/github-auth-modes-cli.js +14 -2
- package/dist/intake/github-auth-modes.d.ts +54 -9
- package/dist/intake/github-auth-modes.js +326 -123
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
- package/dist/lifecycle/completed-write-guard.d.ts +48 -0
- package/dist/lifecycle/completed-write-guard.js +373 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/lifecycle-visible/evaluate.d.ts +98 -0
- package/dist/lifecycle-visible/evaluate.js +710 -0
- package/dist/lifecycle-visible/index.d.ts +2 -0
- package/dist/lifecycle-visible/index.js +2 -0
- package/dist/literal-acceptance/capture.d.ts +16 -0
- package/dist/literal-acceptance/capture.js +78 -10
- package/dist/literal-acceptance/evaluate.js +25 -3
- package/dist/literal-acceptance/index.d.ts +2 -2
- package/dist/literal-acceptance/index.js +2 -2
- package/dist/literal-acceptance/run.js +9 -3
- package/dist/literal-acceptance/types.d.ts +6 -0
- package/dist/literal-acceptance/types.js +6 -0
- package/dist/orchestration/judgment-policy.d.ts +13 -0
- package/dist/orchestration/judgment-policy.js +15 -0
- package/dist/orchestration/probe-session.js +13 -8
- package/dist/orchestration/verify-judgment-gates.d.ts +4 -0
- package/dist/orchestration/verify-judgment-gates.js +43 -11
- package/dist/policy/ceremony-dial.js +7 -7
- package/dist/policy/host-hooks.d.ts +20 -0
- package/dist/policy/host-hooks.js +121 -2
- package/dist/policy/org-force-on-migration.js +2 -1
- package/dist/policy/plan-extensions.d.ts +1 -1
- package/dist/policy/plan-extensions.js +2 -0
- package/dist/policy/product-signal.js +6 -7
- package/dist/policy/require-human-merge.js +8 -6
- package/dist/policy/resolve.d.ts +7 -2
- package/dist/policy/resolve.js +25 -8
- package/dist/policy/runtime-authority.d.ts +18 -0
- package/dist/policy/runtime-authority.js +11 -0
- package/dist/policy/value-feedback.js +7 -8
- package/dist/policy/write-fence.js +3 -0
- package/dist/preflight-cache/evaluate.d.ts +12 -0
- package/dist/preflight-cache/evaluate.js +24 -4
- package/dist/preflight-cache/index.d.ts +1 -1
- package/dist/preflight-cache/index.js +1 -1
- package/dist/product-first-done-gate/evaluate.d.ts +4 -0
- package/dist/product-first-done-gate/evaluate.js +82 -10
- package/dist/release/closed-verb-gate.d.ts +12 -0
- package/dist/release/closed-verb-gate.js +51 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.js +1 -0
- package/dist/release/pipeline.js +7 -0
- package/dist/release/types.d.ts +12 -0
- package/dist/render/constants.d.ts +2 -1
- package/dist/render/constants.js +2 -1
- package/dist/render/framework-commands.js +4 -0
- package/dist/render/spec-validate.js +23 -12
- package/dist/run-summary/types.d.ts +4 -0
- package/dist/scm/design-critique-chip.d.ts +37 -0
- package/dist/scm/design-critique-chip.js +171 -0
- package/dist/scm/index.d.ts +1 -0
- package/dist/scm/index.js +1 -0
- package/dist/scm/main.d.ts +3 -0
- package/dist/scm/main.js +15 -1
- package/dist/scm/readiness-cli.d.ts +2 -0
- package/dist/scm/readiness-cli.js +42 -1
- package/dist/scm/readiness.d.ts +2 -1
- package/dist/scm/readiness.js +7 -1
- package/dist/scope/decompose.js +2 -1
- package/dist/scope/delivery-evidence.d.ts +27 -1
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/lifecycle-write.d.ts +24 -0
- package/dist/scope/lifecycle-write.js +64 -0
- package/dist/scope/main.js +18 -6
- package/dist/scope/promote-from-issue.js +5 -5
- package/dist/scope/transition.js +90 -11
- package/dist/scope-provenance/evaluate.js +8 -4
- package/dist/session/ac-pass-banking.d.ts +5 -0
- package/dist/session/ac-pass-banking.js +3 -2
- package/dist/session/git.d.ts +18 -0
- package/dist/session/git.js +81 -1
- package/dist/session/occupancy.d.ts +2 -1
- package/dist/session/occupancy.js +11 -4
- package/dist/session/process-cost.d.ts +32 -10
- package/dist/session/process-cost.js +189 -1
- package/dist/session/product-state-hash.js +41 -24
- package/dist/session/ritual-entrypoint.js +16 -1
- package/dist/session/session-start.d.ts +18 -0
- package/dist/session/session-start.js +49 -2
- package/dist/session/verify-session-ritual.d.ts +46 -4
- package/dist/session/verify-session-ritual.js +118 -10
- package/dist/swarm/finalize-cohort-cli.d.ts +7 -2
- package/dist/swarm/finalize-cohort-cli.js +165 -35
- package/dist/swarm/finalize-cohort.d.ts +1 -0
- package/dist/swarm/finalize-cohort.js +14 -2
- package/dist/swarm/launch.d.ts +69 -0
- package/dist/swarm/launch.js +191 -1
- package/dist/swarm/routing-set-cli.js +2 -0
- package/dist/swarm/routing-verify.d.ts +1 -1
- package/dist/swarm/routing-verify.js +2 -2
- package/dist/triage/actions/index.d.ts +4 -0
- package/dist/triage/actions/index.js +6 -0
- package/dist/triage/bootstrap/index.js +6 -6
- package/dist/triage/evaluate/evaluate.d.ts +11 -0
- package/dist/triage/evaluate/evaluate.js +169 -0
- package/dist/triage/evaluate/github.d.ts +16 -0
- package/dist/triage/evaluate/github.js +153 -0
- package/dist/triage/evaluate/index.d.ts +9 -0
- package/dist/triage/evaluate/index.js +8 -0
- package/dist/triage/evaluate/paths.d.ts +7 -0
- package/dist/triage/evaluate/paths.js +25 -0
- package/dist/triage/evaluate/sink.d.ts +5 -0
- package/dist/triage/evaluate/sink.js +52 -0
- package/dist/triage/evaluate/types.d.ts +93 -0
- package/dist/triage/evaluate/types.js +13 -0
- package/dist/triage/evaluate/validity.d.ts +8 -0
- package/dist/triage/evaluate/validity.js +87 -0
- package/dist/triage/evaluate/value.d.ts +12 -0
- package/dist/triage/evaluate/value.js +33 -0
- package/dist/triage/evaluate/wip-census.d.ts +5 -0
- package/dist/triage/evaluate/wip-census.js +39 -0
- package/dist/triage/evaluate/worktrees.d.ts +8 -0
- package/dist/triage/evaluate/worktrees.js +68 -0
- package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
- package/dist/triage/evaluate/xbrief-refs.js +60 -0
- package/dist/triage/help/registry-data.d.ts +22 -8
- package/dist/triage/help/registry-data.js +54 -6
- package/dist/triage/index.d.ts +1 -0
- package/dist/triage/index.js +1 -0
- package/dist/triage/welcome/writers.d.ts +1 -1
- package/dist/triage/welcome/writers.js +10 -5
- package/dist/value/feedback-file.d.ts +17 -2
- package/dist/value/feedback-file.js +80 -6
- package/dist/value/readback.d.ts +4 -1
- package/dist/value/readback.js +51 -12
- package/dist/vbrief-reconcile/labels.d.ts +1 -0
- package/dist/vbrief-reconcile/labels.js +30 -0
- package/dist/vbrief-validate/schema.d.ts +4 -0
- package/dist/vbrief-validate/schema.js +1 -1
- package/dist/vbrief-validation/story-quality.d.ts +7 -0
- package/dist/vbrief-validation/story-quality.js +8 -1
- package/dist/verify-env/agent-hook-readiness.d.ts +2 -2
- package/dist/verify-env/agent-hook-readiness.js +12 -8
- package/dist/verify-env/agent-hooks-live-probe.d.ts +5 -1
- package/dist/verify-env/agent-hooks-live-probe.js +26 -5
- package/dist/verify-env/agent-hooks.js +3 -4
- package/dist/xbrief-migrate/agents-header.js +69 -7
- package/package.json +7 -3
|
@@ -1,8 +1,36 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { HOOK_HOSTS } from "../hooks/dispatcher.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
3
|
+
import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
|
|
4
|
+
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
5
|
+
import { policyColonInvocation } from "./policy-invocation.js";
|
|
6
|
+
import { appendAuditLog, loadProjectDefinition, POLICY_AUDIT_NOOP_STDOUT, projectDefinitionPath, } from "./resolve.js";
|
|
4
7
|
export const FIELD_HOST_HOOKS = "plan.policy.hostHooks";
|
|
5
8
|
export const FIELD_HOST_HOOKS_CLI_ALIAS = "hostHooks";
|
|
9
|
+
export const DISABLE_HOST_HOOKS_SUBCOMMAND = "disable-host-hooks";
|
|
10
|
+
/** Confirm-gated disable verb for unused-host recovery copy (#3571). CLI form: no go-task `--`. */
|
|
11
|
+
export function disableHostHooksInvocation(trailing = " --host <host> --confirm") {
|
|
12
|
+
return policyColonInvocation(DISABLE_HOST_HOOKS_SUBCOMMAND, trailing);
|
|
13
|
+
}
|
|
14
|
+
export const HOST_HOOKS_DISABLE_CAPABILITY_COST_DISCLOSURE = "\u26a0 Capability-cost disclosure -- disabling hostHooks for a host removes " +
|
|
15
|
+
"deft-hook pre-execution guardrails for anyone who later opens this repo in that host. " +
|
|
16
|
+
"The result is tracked.\n" +
|
|
17
|
+
" \u2022 Opted-out hosts skip Directive hook deposit; leftover-free files write {}.\n" +
|
|
18
|
+
" \u2022 This is not a timeout or live-probe fix. Retry the gated ritual when load is the cause.\n" +
|
|
19
|
+
" \u2022 Inspect: `" +
|
|
20
|
+
policyColonInvocation("show", " --field=hostHooks") +
|
|
21
|
+
"`.\n" +
|
|
22
|
+
" \u2022 Apply: `" +
|
|
23
|
+
disableHostHooksInvocation() +
|
|
24
|
+
"`.\n" +
|
|
25
|
+
" \u2022 Reversible: set the host back to true and run `deft update`.\n" +
|
|
26
|
+
" \u2022 Hand-edit of plan.policy.hostHooks plus `deft update` still strips " +
|
|
27
|
+
"(human high-trust bypass).\n" +
|
|
28
|
+
" \u2022 Changes are recorded to meta/policy-changes.log for auditability.";
|
|
29
|
+
export const UNUSED_HOST_HOOKS_RECOVERY = "If an affected host is unused, inspect `" +
|
|
30
|
+
policyColonInvocation("show", " --field=hostHooks") +
|
|
31
|
+
"`, then run `" +
|
|
32
|
+
disableHostHooksInvocation() +
|
|
33
|
+
"`. That removes deft-hook pre-execution guardrails for that host and the result is tracked.";
|
|
6
34
|
export const DEFAULT_HOST_HOOKS_POLICY = {
|
|
7
35
|
claude: true,
|
|
8
36
|
cursor: true,
|
|
@@ -93,4 +121,95 @@ export function loadHostHooksPolicyFromProject(projectRoot) {
|
|
|
93
121
|
}
|
|
94
122
|
return resolveHostHooksPolicy(policyBlock.hostHooks);
|
|
95
123
|
}
|
|
124
|
+
export function parseHookHost(value) {
|
|
125
|
+
if (value === undefined)
|
|
126
|
+
return null;
|
|
127
|
+
return HOOK_HOSTS.includes(value) ? value : null;
|
|
128
|
+
}
|
|
129
|
+
/** Persist `hostHooks.<host>=false` after capability-cost disclosure (#3571). */
|
|
130
|
+
export function disableHostHooks(projectRoot, options) {
|
|
131
|
+
if (!HOOK_HOSTS.includes(options.host)) {
|
|
132
|
+
return {
|
|
133
|
+
exitCode: 2,
|
|
134
|
+
stdout: `\u274c ${FIELD_HOST_HOOKS}.${String(options.host)} is not a deposited host (${HOOK_HOSTS.join(", ")}).\n`,
|
|
135
|
+
changed: false,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (!options.confirm) {
|
|
139
|
+
return {
|
|
140
|
+
exitCode: 1,
|
|
141
|
+
stdout: `${HOST_HOOKS_DISABLE_CAPABILITY_COST_DISCLOSURE}\n\n` +
|
|
142
|
+
`Re-run with --confirm to apply: ${disableHostHooksInvocation(` --host ${options.host} --confirm`)}\n`,
|
|
143
|
+
changed: false,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const path = projectDefinitionPath(projectRoot);
|
|
147
|
+
try {
|
|
148
|
+
const { changed } = projectDefinitionMutationLock(projectRoot, () => {
|
|
149
|
+
const parsed = JSON.parse(readFileSync(path, { encoding: "utf8" }));
|
|
150
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
151
|
+
throw new Error(`PROJECT-DEFINITION at ${path} top-level value is not a JSON object`);
|
|
152
|
+
}
|
|
153
|
+
const data = parsed;
|
|
154
|
+
if (typeof data.plan !== "object" || data.plan === null || Array.isArray(data.plan)) {
|
|
155
|
+
if (data.plan === undefined) {
|
|
156
|
+
data.plan = {};
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
throw new Error("PROJECT-DEFINITION 'plan' is not an object");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const plan = data.plan;
|
|
163
|
+
const legacyKeyMigrated = migrateLegacyPolicyKey(plan);
|
|
164
|
+
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
165
|
+
if (typeof existingPolicy !== "object" ||
|
|
166
|
+
existingPolicy === null ||
|
|
167
|
+
Array.isArray(existingPolicy)) {
|
|
168
|
+
if (existingPolicy === undefined) {
|
|
169
|
+
plan[PLAN_POLICY_KEY] = {};
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
throw new Error("plan.policy is not an object");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const policyBlock = plan[PLAN_POLICY_KEY];
|
|
176
|
+
const previous = resolveHostHooksPolicy(policyBlock.hostHooks);
|
|
177
|
+
const next = { ...previous, [options.host]: false };
|
|
178
|
+
const changedFlag = previous[options.host] !== false || legacyKeyMigrated;
|
|
179
|
+
policyBlock.hostHooks = next;
|
|
180
|
+
if (changedFlag) {
|
|
181
|
+
atomicWriteProjectDefinition(path, data);
|
|
182
|
+
}
|
|
183
|
+
const actor = options.actor ?? disableHostHooksInvocation();
|
|
184
|
+
const note = options.note ?? "";
|
|
185
|
+
const parts = [
|
|
186
|
+
`actor=${actor}`,
|
|
187
|
+
`${FIELD_HOST_HOOKS}.${options.host}=false`,
|
|
188
|
+
`previous=${JSON.stringify(previous)}`,
|
|
189
|
+
];
|
|
190
|
+
if (note) {
|
|
191
|
+
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
192
|
+
}
|
|
193
|
+
appendAuditLog(projectRoot, parts.join(" "), changedFlag);
|
|
194
|
+
return { changed: changedFlag };
|
|
195
|
+
});
|
|
196
|
+
const lines = [
|
|
197
|
+
`\u2713 ${FIELD_HOST_HOOKS}.${options.host}=false (deft-hook guardrails removed for ${options.host}; result is tracked).`,
|
|
198
|
+
changed ? " audit: meta/policy-changes.log updated." : POLICY_AUDIT_NOOP_STDOUT,
|
|
199
|
+
" Run `deft update` to strip Directive-managed hook entries for that host.",
|
|
200
|
+
];
|
|
201
|
+
return { exitCode: 0, stdout: `${lines.join("\n")}\n`, changed };
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
205
|
+
if (message.includes("ENOENT") || message.includes("PROJECT-DEFINITION not found")) {
|
|
206
|
+
return {
|
|
207
|
+
exitCode: 2,
|
|
208
|
+
stdout: `\u274c PROJECT-DEFINITION not found at ${path}\n`,
|
|
209
|
+
changed: false,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return { exitCode: 2, stdout: `\u274c Config error: ${message}\n`, changed: false };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
96
215
|
//# sourceMappingURL=host-hooks.js.map
|
|
@@ -421,6 +421,7 @@ function applyForceOn(projectRoot, options, intent) {
|
|
|
421
421
|
? existing.previousProductSignal
|
|
422
422
|
: ORG_FORCE_ON_NO_BASELINE;
|
|
423
423
|
const actor = options.actor ?? "directive-update";
|
|
424
|
+
const changed = valueFeedbackChanged || productSignalChanged;
|
|
424
425
|
appendAuditLog(projectRoot, [
|
|
425
426
|
`actor=${actor}`,
|
|
426
427
|
"org-force-on-v2822",
|
|
@@ -428,7 +429,7 @@ function applyForceOn(projectRoot, options, intent) {
|
|
|
428
429
|
`productSignal=${needsPs ? "forced-on" : "unchanged"}`,
|
|
429
430
|
`previousValueFeedback=${JSON.stringify(normalizePolicySnapshot(markerPreviousVf))}`,
|
|
430
431
|
`previousProductSignal=${JSON.stringify(normalizePolicySnapshot(markerPreviousPs))}`,
|
|
431
|
-
].join(" "));
|
|
432
|
+
].join(" "), changed);
|
|
432
433
|
writeMarker(projectRoot, options, {
|
|
433
434
|
// OR with existing so partial re-apply does not clear install-force-on source.
|
|
434
435
|
valueFeedback: needsVf || (existing?.valueFeedback ?? false),
|
|
@@ -41,7 +41,7 @@ export declare function readPlanOnboarding(plan: unknown): unknown;
|
|
|
41
41
|
* policy key is present. Used by writers so a mutation never strands a legacy
|
|
42
42
|
* bare block alongside the namespaced one.
|
|
43
43
|
*/
|
|
44
|
-
export declare function migrateLegacyPolicyKey(plan: Record<string, unknown>):
|
|
44
|
+
export declare function migrateLegacyPolicyKey(plan: Record<string, unknown>): boolean;
|
|
45
45
|
/** The namespaced/legacy key pairs a bare block can silently shadow (#2301). */
|
|
46
46
|
export declare const SHADOWABLE_PLAN_EXTENSIONS: ReadonlyArray<{
|
|
47
47
|
readonly namespacedKey: string;
|
|
@@ -68,7 +68,9 @@ export function migrateLegacyPolicyKey(plan) {
|
|
|
68
68
|
if (plan[PLAN_POLICY_KEY] === undefined && plan[LEGACY_PLAN_POLICY_KEY] !== undefined) {
|
|
69
69
|
plan[PLAN_POLICY_KEY] = plan[LEGACY_PLAN_POLICY_KEY];
|
|
70
70
|
delete plan[LEGACY_PLAN_POLICY_KEY];
|
|
71
|
+
return true;
|
|
71
72
|
}
|
|
73
|
+
return false;
|
|
72
74
|
}
|
|
73
75
|
/** The namespaced/legacy key pairs a bare block can silently shadow (#2301). */
|
|
74
76
|
export const SHADOWABLE_PLAN_EXTENSIONS = [
|
|
@@ -3,7 +3,7 @@ import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "..
|
|
|
3
3
|
import { productSignalInstallForceOnSource } from "./org-force-on-migration.js";
|
|
4
4
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
5
5
|
import { policyColonInvocation } from "./policy-invocation.js";
|
|
6
|
-
import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
|
|
6
|
+
import { appendAuditLog, loadProjectDefinition, POLICY_AUDIT_NOOP_STDOUT, projectDefinitionPath, } from "./resolve.js";
|
|
7
7
|
/** Canonical registered policy field name (#2693). */
|
|
8
8
|
export const FIELD_PRODUCT_SIGNAL = "plan.policy.productSignal";
|
|
9
9
|
/** Short alias for `policy:show --field=productSignal`. */
|
|
@@ -157,7 +157,7 @@ export function enableProductSignal(projectRoot, options) {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
const plan = data.plan;
|
|
160
|
-
migrateLegacyPolicyKey(plan);
|
|
160
|
+
const legacyKeyMigrated = migrateLegacyPolicyKey(plan);
|
|
161
161
|
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
162
162
|
if (typeof existingPolicy !== "object" ||
|
|
163
163
|
existingPolicy === null ||
|
|
@@ -181,7 +181,8 @@ export function enableProductSignal(projectRoot, options) {
|
|
|
181
181
|
const nextBlock = { enabled: true, sinkRepo };
|
|
182
182
|
const previousNormalized = resolveProductSignalFromTypedBlock(previous);
|
|
183
183
|
const changedFlag = previousNormalized.enabled !== nextBlock.enabled ||
|
|
184
|
-
previousNormalized.sinkRepo !== nextBlock.sinkRepo
|
|
184
|
+
previousNormalized.sinkRepo !== nextBlock.sinkRepo ||
|
|
185
|
+
legacyKeyMigrated;
|
|
185
186
|
policyBlock.productSignal = nextBlock;
|
|
186
187
|
if (changedFlag) {
|
|
187
188
|
atomicWriteProjectDefinition(path, data);
|
|
@@ -197,15 +198,13 @@ export function enableProductSignal(projectRoot, options) {
|
|
|
197
198
|
if (note) {
|
|
198
199
|
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
199
200
|
}
|
|
200
|
-
appendAuditLog(projectRoot, parts.join(" "));
|
|
201
|
+
appendAuditLog(projectRoot, parts.join(" "), changedFlag);
|
|
201
202
|
return { changed: changedFlag };
|
|
202
203
|
});
|
|
203
204
|
const resolved = resolveProductSignal(projectRoot);
|
|
204
205
|
const lines = [
|
|
205
206
|
`\u2713 ${FIELD_PRODUCT_SIGNAL}.enabled=true (product-signal ON).`,
|
|
206
|
-
changed
|
|
207
|
-
? " audit: meta/policy-changes.log updated."
|
|
208
|
-
: " no-op: value already matched (audit entry still appended for trail).",
|
|
207
|
+
changed ? " audit: meta/policy-changes.log updated." : POLICY_AUDIT_NOOP_STDOUT,
|
|
209
208
|
formatProductSignalStatusLine(resolved),
|
|
210
209
|
];
|
|
211
210
|
return { exitCode: 0, stdout: `${lines.join("\n")}\n`, changed };
|
|
@@ -18,7 +18,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
18
18
|
import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
|
|
19
19
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
20
20
|
import { policyColonInvocation } from "./policy-invocation.js";
|
|
21
|
-
import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
|
|
21
|
+
import { appendAuditLog, loadProjectDefinition, projectDefinitionPath, stampChangedToken, } from "./resolve.js";
|
|
22
22
|
export const FIELD_REQUIRE_HUMAN_MERGE = "plan.policy.requireHumanMerge";
|
|
23
23
|
export const FIELD_REQUIRE_HUMAN_MERGE_CLI_ALIAS = "requireHumanMerge";
|
|
24
24
|
export const FIELD_AUTO_DEPLOY_ON_MERGE = "plan.policy.autoDeployOnMerge";
|
|
@@ -285,7 +285,7 @@ export function setRequireHumanMerge(projectRoot, options) {
|
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
const plan = data.plan;
|
|
288
|
-
migrateLegacyPolicyKey(plan);
|
|
288
|
+
const legacyKeyMigrated = migrateLegacyPolicyKey(plan);
|
|
289
289
|
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
290
290
|
if (typeof existingPolicy !== "object" ||
|
|
291
291
|
existingPolicy === null ||
|
|
@@ -309,8 +309,7 @@ export function setRequireHumanMerge(projectRoot, options) {
|
|
|
309
309
|
delete narratives[LEGACY_REQUIRE_HUMAN_MERGE_KEY];
|
|
310
310
|
legacyDropped = true;
|
|
311
311
|
}
|
|
312
|
-
|
|
313
|
-
const changed = previous !== Boolean(requireHumanMerge) || legacyDropped;
|
|
312
|
+
const changed = previous !== Boolean(requireHumanMerge) || legacyDropped || legacyKeyMigrated;
|
|
314
313
|
const parts = [
|
|
315
314
|
`actor=${actor}`,
|
|
316
315
|
`requireHumanMerge=${requireHumanMerge ? "true" : "false"}`,
|
|
@@ -321,8 +320,11 @@ export function setRequireHumanMerge(projectRoot, options) {
|
|
|
321
320
|
if (note) {
|
|
322
321
|
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
323
322
|
}
|
|
324
|
-
const auditEntry = parts.join(" ");
|
|
325
|
-
|
|
323
|
+
const auditEntry = stampChangedToken(parts.join(" "), changed);
|
|
324
|
+
if (changed) {
|
|
325
|
+
atomicWriteProjectDefinition(path, data);
|
|
326
|
+
}
|
|
327
|
+
appendAuditLog(projectRoot, auditEntry, changed);
|
|
326
328
|
return { changed, auditEntry };
|
|
327
329
|
});
|
|
328
330
|
}
|
package/dist/policy/resolve.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export declare const ENV_BYPASS = "DEFT_ALLOW_DEFAULT_BRANCH_COMMIT";
|
|
|
6
6
|
export declare const LEGACY_NARRATIVE_KEY = "Allow direct commits to master";
|
|
7
7
|
/** Audit log relative path (#746). */
|
|
8
8
|
export declare const AUDIT_LOG_REL_PATH = "meta/policy-changes.log";
|
|
9
|
+
/** Operator line when a policy writer matches current state and does not append (#3528). */
|
|
10
|
+
export declare const POLICY_AUDIT_NOOP_STDOUT = " no-op: value already matched (ledger unchanged).";
|
|
11
|
+
/** Stamp a machine-readable `changed=true|false` token (#3528 / UPGRADING.md). */
|
|
12
|
+
export declare function stampChangedToken(entry: string, changed: boolean): string;
|
|
9
13
|
export type PolicySource = "typed" | "legacy-narrative" | "env-bypass" | "default-fail-closed";
|
|
10
14
|
export interface PolicyResult {
|
|
11
15
|
readonly allowDirectCommits: boolean;
|
|
@@ -28,8 +32,9 @@ export declare function coerceLegacyNarrative(value: unknown): {
|
|
|
28
32
|
export declare function loadProjectDefinition(projectRoot: string): [Record<string, unknown> | null, string | null];
|
|
29
33
|
/** Resolve the effective branch-commit policy (#746 / #747). */
|
|
30
34
|
export declare function resolvePolicy(projectRoot: string): PolicyResult;
|
|
31
|
-
/** Append a one-line audit entry to meta/policy-changes.log (#746).
|
|
32
|
-
|
|
35
|
+
/** Append a one-line audit entry to meta/policy-changes.log (#746).
|
|
36
|
+
* No-op (`changed=false`) does not create or modify the tracked file (#3528). */
|
|
37
|
+
export declare function appendAuditLog(projectRoot: string, entry: string, changed?: boolean): string;
|
|
33
38
|
/** Write the typed policy flag back to PROJECT-DEFINITION (#746). */
|
|
34
39
|
export declare function setPolicy(projectRoot: string, options: {
|
|
35
40
|
allowDirectCommits: boolean;
|
package/dist/policy/resolve.js
CHANGED
|
@@ -13,6 +13,16 @@ export const ENV_BYPASS = "DEFT_ALLOW_DEFAULT_BRANCH_COMMIT";
|
|
|
13
13
|
export const LEGACY_NARRATIVE_KEY = "Allow direct commits to master";
|
|
14
14
|
/** Audit log relative path (#746). */
|
|
15
15
|
export const AUDIT_LOG_REL_PATH = "meta/policy-changes.log";
|
|
16
|
+
/** Operator line when a policy writer matches current state and does not append (#3528). */
|
|
17
|
+
export const POLICY_AUDIT_NOOP_STDOUT = " no-op: value already matched (ledger unchanged).";
|
|
18
|
+
const CHANGED_TOKEN_RE = /(?:^|\s)changed=(?:true|false)(?:\s|$)/;
|
|
19
|
+
/** Stamp a machine-readable `changed=true|false` token (#3528 / UPGRADING.md). */
|
|
20
|
+
export function stampChangedToken(entry, changed) {
|
|
21
|
+
if (CHANGED_TOKEN_RE.test(entry)) {
|
|
22
|
+
return entry;
|
|
23
|
+
}
|
|
24
|
+
return `${entry} changed=${changed ? "true" : "false"}`;
|
|
25
|
+
}
|
|
16
26
|
const TRUTHY = new Set(["1", "true", "yes", "on"]);
|
|
17
27
|
/**
|
|
18
28
|
* Resolve absolute path to PROJECT-DEFINITION. Layout-aware (#2109 part 2a):
|
|
@@ -176,10 +186,15 @@ export function resolvePolicy(projectRoot) {
|
|
|
176
186
|
function nowIso() {
|
|
177
187
|
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
178
188
|
}
|
|
179
|
-
/** Append a one-line audit entry to meta/policy-changes.log (#746).
|
|
180
|
-
|
|
189
|
+
/** Append a one-line audit entry to meta/policy-changes.log (#746).
|
|
190
|
+
* No-op (`changed=false`) does not create or modify the tracked file (#3528). */
|
|
191
|
+
export function appendAuditLog(projectRoot, entry, changed = true) {
|
|
181
192
|
const root = pathResolve(projectRoot);
|
|
182
193
|
const logPath = join(root, AUDIT_LOG_REL_PATH);
|
|
194
|
+
const stamped = stampChangedToken(entry, changed);
|
|
195
|
+
if (!changed) {
|
|
196
|
+
return logPath;
|
|
197
|
+
}
|
|
183
198
|
// #2980 wave D: product write sink routes through containedWrite.
|
|
184
199
|
if (!existsSync(logPath)) {
|
|
185
200
|
const header = "# meta/policy-changes.log -- audit trail for " +
|
|
@@ -194,7 +209,7 @@ export function appendAuditLog(projectRoot, entry) {
|
|
|
194
209
|
containedWrite({
|
|
195
210
|
root,
|
|
196
211
|
target: AUDIT_LOG_REL_PATH,
|
|
197
|
-
data: `${nowIso()} ${
|
|
212
|
+
data: `${nowIso()} ${stamped}\n`,
|
|
198
213
|
mode: "append",
|
|
199
214
|
});
|
|
200
215
|
return logPath;
|
|
@@ -224,7 +239,7 @@ export function setPolicy(projectRoot, options) {
|
|
|
224
239
|
}
|
|
225
240
|
}
|
|
226
241
|
const plan = data.plan;
|
|
227
|
-
migrateLegacyPolicyKey(plan);
|
|
242
|
+
const legacyKeyMigrated = migrateLegacyPolicyKey(plan);
|
|
228
243
|
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
229
244
|
if (typeof existingPolicy !== "object" ||
|
|
230
245
|
existingPolicy === null ||
|
|
@@ -248,8 +263,7 @@ export function setPolicy(projectRoot, options) {
|
|
|
248
263
|
delete narratives[LEGACY_NARRATIVE_KEY];
|
|
249
264
|
legacyDropped = true;
|
|
250
265
|
}
|
|
251
|
-
|
|
252
|
-
const changed = previous !== Boolean(allowDirectCommits) || legacyDropped;
|
|
266
|
+
const changed = previous !== Boolean(allowDirectCommits) || legacyDropped || legacyKeyMigrated;
|
|
253
267
|
const parts = [
|
|
254
268
|
`actor=${actor}`,
|
|
255
269
|
`allowDirectCommitsToMaster=${allowDirectCommits ? "true" : "false"}`,
|
|
@@ -261,8 +275,11 @@ export function setPolicy(projectRoot, options) {
|
|
|
261
275
|
if (note) {
|
|
262
276
|
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
263
277
|
}
|
|
264
|
-
const auditEntry = parts.join(" ");
|
|
265
|
-
|
|
278
|
+
const auditEntry = stampChangedToken(parts.join(" "), changed);
|
|
279
|
+
if (changed) {
|
|
280
|
+
atomicWriteProjectDefinition(path, data);
|
|
281
|
+
}
|
|
282
|
+
appendAuditLog(projectRoot, auditEntry, changed);
|
|
266
283
|
return { changed, auditEntry };
|
|
267
284
|
});
|
|
268
285
|
}
|
|
@@ -11,6 +11,22 @@ export interface RuntimeAuthorityScopes {
|
|
|
11
11
|
}
|
|
12
12
|
/** Layers that contribute to a resolved write fence (#516 / #2443 / #2948 Wave 3). */
|
|
13
13
|
export type WriteFenceSource = "project" | "story";
|
|
14
|
+
/**
|
|
15
|
+
* Whether recognized Shell dest-forms are enforced (#3438 / #3594).
|
|
16
|
+
*
|
|
17
|
+
* `off` (default) leaves Shell mutations exactly as they were before #3438:
|
|
18
|
+
* unrecognized and fail-open. `enforce` routes recognized dest-forms through
|
|
19
|
+
* `inspectMutationGates` and fail-closes targets it cannot prove.
|
|
20
|
+
*
|
|
21
|
+
* ! Default is `off` on purpose. Before #3438 Bash mutations were not gated at
|
|
22
|
+
* all, so defaulting to `enforce` would land new denials on every consumer with
|
|
23
|
+
* no opt-out. Opting IN via tracked project policy is a narrowing, which this
|
|
24
|
+
* fence model permits; a tracked switch that DISABLED enforcement would not be
|
|
25
|
+
* (see policy/deft-directive-disable.ts — repository-controlled content must
|
|
26
|
+
* not disable hooks for downstream clones).
|
|
27
|
+
*/
|
|
28
|
+
export type ShellDestFormsMode = "off" | "enforce";
|
|
29
|
+
export declare const SHELL_DEST_FORMS_MODES: readonly ShellDestFormsMode[];
|
|
14
30
|
export interface RuntimeAuthorityPolicy {
|
|
15
31
|
readonly enabled: boolean;
|
|
16
32
|
/** When non-empty, write targets must match at least one pattern. Empty = allow all paths. */
|
|
@@ -18,6 +34,8 @@ export interface RuntimeAuthorityPolicy {
|
|
|
18
34
|
/** Deny wins over allow. */
|
|
19
35
|
readonly denyPaths: readonly string[];
|
|
20
36
|
readonly scopes: RuntimeAuthorityScopes;
|
|
37
|
+
/** Shell dest-form enforcement (#3438 / #3594). Default `off`. */
|
|
38
|
+
readonly shellDestForms: ShellDestFormsMode;
|
|
21
39
|
/**
|
|
22
40
|
* Optional story-layer allow globs from `plan.metadata.swarm.file_scope`
|
|
23
41
|
* (populated by `resolveWriteFence` only). When non-empty, path must match
|
|
@@ -3,6 +3,7 @@ import { readPlanPolicy } from "./plan-extensions.js";
|
|
|
3
3
|
import { loadProjectDefinition } from "./resolve.js";
|
|
4
4
|
export const FIELD_RUNTIME_AUTHORITY = "plan.policy.runtimeAuthority";
|
|
5
5
|
export const FIELD_RUNTIME_AUTHORITY_CLI_ALIAS = "runtimeAuthority";
|
|
6
|
+
export const SHELL_DEST_FORMS_MODES = ["off", "enforce"];
|
|
6
7
|
export const DEFAULT_RUNTIME_AUTHORITY_SCOPES = {
|
|
7
8
|
edits: true,
|
|
8
9
|
push: false,
|
|
@@ -14,6 +15,7 @@ export const DEFAULT_RUNTIME_AUTHORITY_POLICY = {
|
|
|
14
15
|
allowPaths: [],
|
|
15
16
|
denyPaths: [],
|
|
16
17
|
scopes: DEFAULT_RUNTIME_AUTHORITY_SCOPES,
|
|
18
|
+
shellDestForms: "off",
|
|
17
19
|
};
|
|
18
20
|
function readBoolean(rec, key, fallback) {
|
|
19
21
|
if (key in rec && typeof rec[key] === "boolean") {
|
|
@@ -37,6 +39,11 @@ function readScopes(raw) {
|
|
|
37
39
|
merge: readBoolean(rec, "merge", DEFAULT_RUNTIME_AUTHORITY_SCOPES.merge),
|
|
38
40
|
};
|
|
39
41
|
}
|
|
42
|
+
function readShellDestForms(raw) {
|
|
43
|
+
// Unknown / malformed resolves to the safe default rather than throwing, but
|
|
44
|
+
// validateRuntimeAuthority reports it so a typo is not silent (#3594).
|
|
45
|
+
return raw === "enforce" ? "enforce" : DEFAULT_RUNTIME_AUTHORITY_POLICY.shellDestForms;
|
|
46
|
+
}
|
|
40
47
|
export function resolveRuntimeAuthorityPolicy(raw) {
|
|
41
48
|
if (raw === null || raw === undefined) {
|
|
42
49
|
return DEFAULT_RUNTIME_AUTHORITY_POLICY;
|
|
@@ -50,6 +57,7 @@ export function resolveRuntimeAuthorityPolicy(raw) {
|
|
|
50
57
|
allowPaths: readStringArray(rec.allowPaths),
|
|
51
58
|
denyPaths: readStringArray(rec.denyPaths),
|
|
52
59
|
scopes: readScopes(rec.scopes),
|
|
60
|
+
shellDestForms: readShellDestForms(rec.shellDestForms),
|
|
53
61
|
};
|
|
54
62
|
}
|
|
55
63
|
export function validateRuntimeAuthority(value) {
|
|
@@ -69,6 +77,9 @@ export function validateRuntimeAuthority(value) {
|
|
|
69
77
|
errors.push(`${FIELD_RUNTIME_AUTHORITY}.${key} must be an array of path globs`);
|
|
70
78
|
}
|
|
71
79
|
}
|
|
80
|
+
if ("shellDestForms" in rec && !SHELL_DEST_FORMS_MODES.includes(rec.shellDestForms)) {
|
|
81
|
+
errors.push(`${FIELD_RUNTIME_AUTHORITY}.shellDestForms must be one of ${SHELL_DEST_FORMS_MODES.join(" | ")}`);
|
|
82
|
+
}
|
|
72
83
|
if ("scopes" in rec) {
|
|
73
84
|
const scopes = rec.scopes;
|
|
74
85
|
if (typeof scopes !== "object" || scopes === null || Array.isArray(scopes)) {
|
|
@@ -3,7 +3,7 @@ import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "..
|
|
|
3
3
|
import { valueFeedbackInstallForceOnSource } from "./org-force-on-migration.js";
|
|
4
4
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
5
5
|
import { policyColonInvocation } from "./policy-invocation.js";
|
|
6
|
-
import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
|
|
6
|
+
import { appendAuditLog, loadProjectDefinition, POLICY_AUDIT_NOOP_STDOUT, projectDefinitionPath, } from "./resolve.js";
|
|
7
7
|
import { isTrustedOrgAutoEnable } from "./value-feedback-autoenable.js";
|
|
8
8
|
/** Canonical registered policy field name (matches other FIELD_* dotted paths). */
|
|
9
9
|
export const FIELD_VALUE_FEEDBACK = "plan.policy.valueFeedback";
|
|
@@ -236,7 +236,7 @@ export function enableValueFeedback(projectRoot, options) {
|
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
const plan = data.plan;
|
|
239
|
-
migrateLegacyPolicyKey(plan);
|
|
239
|
+
const legacyKeyMigrated = migrateLegacyPolicyKey(plan);
|
|
240
240
|
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
241
241
|
if (typeof existingPolicy !== "object" ||
|
|
242
242
|
existingPolicy === null ||
|
|
@@ -273,7 +273,8 @@ export function enableValueFeedback(projectRoot, options) {
|
|
|
273
273
|
const changedFlag = previousNormalized.enabled !== nextBlock.enabled ||
|
|
274
274
|
previousNormalized.emitEvents !== nextBlock.emitEvents ||
|
|
275
275
|
previousNormalized.sessionLine !== nextBlock.sessionLine ||
|
|
276
|
-
previousNormalized.upstreamPrompt !== nextBlock.upstreamPrompt
|
|
276
|
+
previousNormalized.upstreamPrompt !== nextBlock.upstreamPrompt ||
|
|
277
|
+
legacyKeyMigrated;
|
|
277
278
|
policyBlock.valueFeedback = nextBlock;
|
|
278
279
|
if (changedFlag) {
|
|
279
280
|
atomicWriteProjectDefinition(path, data);
|
|
@@ -291,15 +292,13 @@ export function enableValueFeedback(projectRoot, options) {
|
|
|
291
292
|
if (note) {
|
|
292
293
|
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
293
294
|
}
|
|
294
|
-
appendAuditLog(projectRoot, parts.join(" "));
|
|
295
|
+
appendAuditLog(projectRoot, parts.join(" "), changedFlag);
|
|
295
296
|
return { changed: changedFlag };
|
|
296
297
|
});
|
|
297
298
|
const resolved = resolveValueFeedback(projectRoot);
|
|
298
299
|
const lines = [
|
|
299
300
|
`\u2713 ${FIELD_VALUE_FEEDBACK}.enabled=true (value-feedback ON).`,
|
|
300
|
-
changed
|
|
301
|
-
? " audit: meta/policy-changes.log updated."
|
|
302
|
-
: " no-op: value already matched (audit entry still appended for trail).",
|
|
301
|
+
changed ? " audit: meta/policy-changes.log updated." : POLICY_AUDIT_NOOP_STDOUT,
|
|
303
302
|
formatValueFeedbackStatusLine(resolved),
|
|
304
303
|
];
|
|
305
304
|
return { exitCode: 0, stdout: `${lines.join("\n")}\n`, changed };
|
|
@@ -354,7 +353,7 @@ export function clearValueFeedback(projectRoot, options = {}) {
|
|
|
354
353
|
if (note) {
|
|
355
354
|
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
356
355
|
}
|
|
357
|
-
appendAuditLog(projectRoot, parts.join(" "));
|
|
356
|
+
appendAuditLog(projectRoot, parts.join(" "), true);
|
|
358
357
|
return { changed: true };
|
|
359
358
|
});
|
|
360
359
|
const resolved = resolveValueFeedback(projectRoot);
|
|
@@ -140,6 +140,9 @@ export function resolveWriteFence(projectPolicy, storyFileScope, options) {
|
|
|
140
140
|
allowPaths,
|
|
141
141
|
denyPaths,
|
|
142
142
|
scopes,
|
|
143
|
+
// Carry the project setting through: resolving a write fence must not
|
|
144
|
+
// silently enable Shell dest-form enforcement for a story (#3438 / #3594).
|
|
145
|
+
shellDestForms: projectPolicy.shellDestForms,
|
|
143
146
|
storyAllowPaths: storyAllow,
|
|
144
147
|
fenceSources: sources,
|
|
145
148
|
};
|
|
@@ -32,6 +32,12 @@ export interface EvaluateOptions {
|
|
|
32
32
|
forIssue?: number | null;
|
|
33
33
|
allowStale?: boolean;
|
|
34
34
|
allowMissingBootstrap?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Skip the live GitHub drift probe only (#3507). Age staleness stays hard.
|
|
37
|
+
* Explicit flag — never inferred from a missing `--for-issue`.
|
|
38
|
+
* `--for-issue` (work selection) wins over this skip.
|
|
39
|
+
*/
|
|
40
|
+
skipDriftProbe?: boolean;
|
|
35
41
|
/** Injectable clock for tests. */
|
|
36
42
|
nowFn?: () => Date;
|
|
37
43
|
/** Injectable drift probe for tests. */
|
|
@@ -40,6 +46,12 @@ export interface EvaluateOptions {
|
|
|
40
46
|
autoPopulateEmpty?: boolean;
|
|
41
47
|
}
|
|
42
48
|
export declare function normaliseRepoUrl(url: string): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Drift skip is an explicit `--skip-drift-probe` request (#3507).
|
|
51
|
+
* Presence of `--for-issue` is work selection and re-arms the probe.
|
|
52
|
+
* Absence of `--for-issue` is not a skip signal.
|
|
53
|
+
*/
|
|
54
|
+
export declare function shouldSkipDriftProbe(options: EvaluateOptions): boolean;
|
|
43
55
|
/**
|
|
44
56
|
* Evaluate cache freshness for the given project root.
|
|
45
57
|
*
|
|
@@ -300,16 +300,32 @@ function formatDriftFailure(repo, drift) {
|
|
|
300
300
|
};
|
|
301
301
|
}
|
|
302
302
|
function runDriftProbe(resolvedRepo, cacheRoot, source, options) {
|
|
303
|
-
if (options.probeDriftFn) {
|
|
304
|
-
return options.probeDriftFn(resolvedRepo, cacheRoot, source);
|
|
305
|
-
}
|
|
306
303
|
try {
|
|
304
|
+
if (options.probeDriftFn) {
|
|
305
|
+
return options.probeDriftFn(resolvedRepo, cacheRoot, source);
|
|
306
|
+
}
|
|
307
307
|
return probeCacheDrift({ repo: resolvedRepo, source, cacheRoot });
|
|
308
308
|
}
|
|
309
309
|
catch {
|
|
310
|
+
// Deliberate fail-open (#3738 / #3422): a thrown probe (429 after retry,
|
|
311
|
+
// 502/503, DNS, auth) returns null; evaluate treats null as pass.
|
|
312
|
+
// Work-selection / session:start still inherit this. The write path no
|
|
313
|
+
// longer executes cache_fresh, so this is not a write-authorization bypass.
|
|
314
|
+
// Do not silently convert to fail-closed without revisiting #3422 (local
|
|
315
|
+
// work MAY continue when it does not need the forge).
|
|
310
316
|
return null;
|
|
311
317
|
}
|
|
312
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Drift skip is an explicit `--skip-drift-probe` request (#3507).
|
|
321
|
+
* Presence of `--for-issue` is work selection and re-arms the probe.
|
|
322
|
+
* Absence of `--for-issue` is not a skip signal.
|
|
323
|
+
*/
|
|
324
|
+
export function shouldSkipDriftProbe(options) {
|
|
325
|
+
if (options.forIssue !== undefined && options.forIssue !== null)
|
|
326
|
+
return false;
|
|
327
|
+
return options.skipDriftProbe === true;
|
|
328
|
+
}
|
|
313
329
|
const REMEDIATION_NO_CANDIDATES = [
|
|
314
330
|
" Recovery: run `deft triage:bootstrap` to initialise the triage log.",
|
|
315
331
|
].join("\n");
|
|
@@ -471,7 +487,8 @@ function evaluateWithContext(projectRoot, options, ctx) {
|
|
|
471
487
|
].join("\n"),
|
|
472
488
|
};
|
|
473
489
|
}
|
|
474
|
-
|
|
490
|
+
const skipDrift = shouldSkipDriftProbe(options);
|
|
491
|
+
if (resolvedRepo !== null && !allowStale && !skipDrift) {
|
|
475
492
|
const drift = runDriftProbe(resolvedRepo, cacheRoot, source, options);
|
|
476
493
|
if (drift !== null &&
|
|
477
494
|
(drift.stateDriftNumbers.length > 0 || drift.contentDriftNumbers.length > 0)) {
|
|
@@ -506,6 +523,9 @@ function evaluateWithContext(projectRoot, options, ctx) {
|
|
|
506
523
|
if (options.forIssue !== undefined && options.forIssue !== null) {
|
|
507
524
|
msg += ` Issue #${options.forIssue} latest decision = accept; in subscription scope.`;
|
|
508
525
|
}
|
|
526
|
+
if (skipDrift) {
|
|
527
|
+
msg += " Drift probe skipped (no work selection).";
|
|
528
|
+
}
|
|
509
529
|
return { code: 0, message: msg };
|
|
510
530
|
}
|
|
511
531
|
function evaluateForIssue(repo, issueNumber, candidatesPath, cacheRoot, source, scopeRules, _projectRoot) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { EvaluateOptions, GateResult } from "./evaluate.js";
|
|
2
|
-
export { CACHE_DIR_NAME, CANDIDATES_RELPATH, DEFAULT_MAX_AGE_HOURS, DEFAULT_SOURCE, ENV_MAX_AGE_HOURS, ENV_RELEASE_PREFLIGHT, ENV_TRIAGE_REPO, evaluate, REQUIRED_DECISION, } from "./evaluate.js";
|
|
2
|
+
export { CACHE_DIR_NAME, CANDIDATES_RELPATH, DEFAULT_MAX_AGE_HOURS, DEFAULT_SOURCE, ENV_MAX_AGE_HOURS, ENV_RELEASE_PREFLIGHT, ENV_TRIAGE_REPO, evaluate, REQUIRED_DECISION, shouldSkipDriftProbe, } from "./evaluate.js";
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { CACHE_DIR_NAME, CANDIDATES_RELPATH, DEFAULT_MAX_AGE_HOURS, DEFAULT_SOURCE, ENV_MAX_AGE_HOURS, ENV_RELEASE_PREFLIGHT, ENV_TRIAGE_REPO, evaluate, REQUIRED_DECISION, } from "./evaluate.js";
|
|
1
|
+
export { CACHE_DIR_NAME, CANDIDATES_RELPATH, DEFAULT_MAX_AGE_HOURS, DEFAULT_SOURCE, ENV_MAX_AGE_HOURS, ENV_RELEASE_PREFLIGHT, ENV_TRIAGE_REPO, evaluate, REQUIRED_DECISION, shouldSkipDriftProbe, } from "./evaluate.js";
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -23,6 +23,10 @@ export interface VerifyAcResult extends LiteralAcceptanceGateResult {
|
|
|
23
23
|
readonly clauseWalked?: boolean;
|
|
24
24
|
/** How the result was obtained (#3387). */
|
|
25
25
|
readonly servedFrom?: AcServedFrom;
|
|
26
|
+
/** Config-error cause when resolution is config (#3559). */
|
|
27
|
+
readonly cause?: string;
|
|
28
|
+
/** Reuse-gate miss cause when servedFrom is executed (#3558). */
|
|
29
|
+
readonly missReason?: string;
|
|
26
30
|
}
|
|
27
31
|
export interface EvaluateVerifyAcOptions extends EvaluateLiteralAcceptanceOptions {
|
|
28
32
|
/**
|