@deftai/directive-core 0.103.0 → 0.105.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 +682 -35
- package/dist/check/cached-orchestrator.d.ts +1 -1
- package/dist/check/cached-orchestrator.js +2 -2
- package/dist/check/gate-lists.d.ts +3 -2
- package/dist/check/gate-lists.js +5 -2
- package/dist/check/index.d.ts +1 -0
- package/dist/check/index.js +1 -0
- package/dist/check/named-cause.d.ts +1 -1
- package/dist/check/named-cause.js +33 -3
- package/dist/check/session-completed-ac.d.ts +39 -0
- package/dist/check/session-completed-ac.js +198 -0
- package/dist/codebase/default-extractor.d.ts +5 -0
- package/dist/codebase/default-extractor.js +7 -15
- package/dist/deposit/copy-tree.js +60 -0
- package/dist/deposit/python-free.js +26 -18
- package/dist/doctor/checks.d.ts +8 -0
- package/dist/doctor/checks.js +37 -25
- package/dist/doctor/doctor-state.d.ts +4 -0
- package/dist/doctor/doctor-state.js +7 -1
- package/dist/doctor/main.js +9 -14
- package/dist/doctor/types.d.ts +9 -0
- package/dist/fs/contained-write.d.ts +115 -1
- package/dist/fs/contained-write.js +242 -5
- package/dist/fs/mutation-ledger.d.ts +54 -0
- package/dist/fs/mutation-ledger.js +133 -0
- package/dist/fs/non-product-dirs.d.ts +79 -0
- package/dist/fs/non-product-dirs.js +96 -0
- package/dist/hooks/dispatcher.d.ts +3 -1
- package/dist/hooks/dispatcher.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/init-deposit/agent-hooks.js +17 -15
- package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
- package/dist/init-deposit/core-guard-pin-content.js +393 -0
- package/dist/init-deposit/gitignore.js +9 -0
- package/dist/init-deposit/hygiene.d.ts +47 -11
- package/dist/init-deposit/hygiene.js +172 -37
- package/dist/init-deposit/legacy-detect.js +6 -2
- package/dist/init-deposit/refresh.d.ts +21 -1
- package/dist/init-deposit/refresh.js +106 -9
- package/dist/init-deposit/scaffold.js +28 -279
- package/dist/init-deposit/slash-deposit.js +13 -7
- package/dist/init-deposit/xbrief-projections.d.ts +8 -1
- package/dist/init-deposit/xbrief-projections.js +24 -11
- package/dist/intake/clause-derivation.d.ts +74 -0
- package/dist/intake/clause-derivation.js +409 -0
- package/dist/intake/index.d.ts +1 -0
- package/dist/intake/index.js +1 -0
- package/dist/intake/issue-ingest.d.ts +3 -1
- package/dist/intake/issue-ingest.js +36 -43
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
- package/dist/literal-acceptance/capture.d.ts +10 -0
- package/dist/literal-acceptance/capture.js +224 -75
- package/dist/literal-acceptance/evaluate.d.ts +29 -4
- package/dist/literal-acceptance/evaluate.js +70 -2
- package/dist/literal-acceptance/index.d.ts +3 -3
- package/dist/literal-acceptance/index.js +3 -3
- package/dist/literal-acceptance/safety.d.ts +41 -0
- package/dist/literal-acceptance/safety.js +126 -2
- package/dist/literal-acceptance/types.d.ts +8 -0
- package/dist/orphan-active/evaluate.d.ts +7 -1
- package/dist/orphan-active/evaluate.js +102 -23
- package/dist/platform/platform-capabilities.js +2 -3
- package/dist/policy/base-branch.d.ts +37 -0
- package/dist/policy/base-branch.js +114 -0
- package/dist/policy/branch-sync.d.ts +80 -0
- package/dist/policy/branch-sync.js +211 -0
- package/dist/policy/ceremony-dial-escalation.js +1 -0
- package/dist/policy/ceremony-dial.js +4 -14
- package/dist/policy/delivery-branch.d.ts +5 -3
- package/dist/policy/delivery-branch.js +7 -3
- package/dist/policy/file-size-thresholds.d.ts +24 -0
- package/dist/policy/file-size-thresholds.js +23 -0
- package/dist/policy/forge-outage-retry.d.ts +47 -0
- package/dist/policy/forge-outage-retry.js +157 -0
- package/dist/policy/index.d.ts +7 -0
- package/dist/policy/index.js +66 -7
- package/dist/policy/project-invariants.d.ts +50 -0
- package/dist/policy/project-invariants.js +205 -0
- package/dist/policy/sync-default.d.ts +129 -0
- package/dist/policy/sync-default.js +452 -0
- package/dist/policy/sync-max-files.d.ts +93 -0
- package/dist/policy/sync-max-files.js +192 -0
- package/dist/pr-monitor/absent-required.d.ts +11 -0
- package/dist/pr-monitor/absent-required.js +61 -0
- package/dist/pr-monitor/constants.d.ts +7 -0
- package/dist/pr-monitor/constants.js +7 -0
- package/dist/pr-monitor/index.d.ts +2 -1
- package/dist/pr-monitor/index.js +2 -1
- package/dist/pr-monitor/main.js +4 -0
- package/dist/pr-monitor/monitor.js +24 -1
- package/dist/pr-wait-mergeable/classify.js +6 -0
- package/dist/preflight/evaluate.d.ts +9 -0
- package/dist/preflight/evaluate.js +49 -1
- package/dist/preflight/index.d.ts +5 -1
- package/dist/preflight/index.js +2 -0
- package/dist/preflight/intended-placement.d.ts +62 -0
- package/dist/preflight/intended-placement.js +275 -0
- package/dist/preflight/project-invariants-gate.d.ts +29 -0
- package/dist/preflight/project-invariants-gate.js +82 -0
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
- package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
- package/dist/product-first-done-gate/acceptance.d.ts +7 -4
- package/dist/product-first-done-gate/acceptance.js +79 -8
- package/dist/product-first-done-gate/empty-resolution.d.ts +5 -2
- package/dist/product-first-done-gate/empty-resolution.js +13 -2
- package/dist/product-first-done-gate/evaluate.d.ts +29 -0
- package/dist/product-first-done-gate/evaluate.js +307 -33
- package/dist/product-first-done-gate/index.d.ts +3 -1
- package/dist/product-first-done-gate/index.js +2 -1
- package/dist/release/build-dist.d.ts +101 -0
- package/dist/release/build-dist.js +428 -46
- package/dist/run-summary/emit.d.ts +50 -4
- package/dist/run-summary/emit.js +262 -26
- package/dist/run-summary/index.d.ts +3 -2
- package/dist/run-summary/index.js +2 -1
- package/dist/run-summary/session-id.d.ts +21 -0
- package/dist/run-summary/session-id.js +92 -0
- package/dist/run-summary/share.d.ts +1 -1
- package/dist/run-summary/share.js +2 -2
- package/dist/run-summary/types.d.ts +38 -6
- package/dist/run-summary/types.js +1 -0
- package/dist/scope/acceptance-activate-gate.d.ts +1 -1
- package/dist/scope/acceptance-activate-gate.js +4 -4
- package/dist/scope/acceptance-evidence.d.ts +27 -0
- package/dist/scope/acceptance-evidence.js +66 -0
- package/dist/scope/capacity-stamp.d.ts +2 -0
- package/dist/scope/capacity-stamp.js +4 -0
- package/dist/scope/delivery-evidence.d.ts +9 -3
- package/dist/scope/delivery-evidence.js +39 -27
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/parent-lineage.js +7 -6
- package/dist/scope/project-invariant-coverage.d.ts +54 -0
- package/dist/scope/project-invariant-coverage.js +322 -0
- package/dist/scope/transition.js +66 -4
- package/dist/scope-provenance/compare-intent.d.ts +27 -0
- package/dist/scope-provenance/compare-intent.js +233 -0
- package/dist/scope-provenance/digest.d.ts +18 -1
- package/dist/scope-provenance/digest.js +16 -11
- package/dist/scope-provenance/evaluate.d.ts +5 -1
- package/dist/scope-provenance/evaluate.js +64 -4
- package/dist/scope-provenance/extract-intent.d.ts +43 -0
- package/dist/scope-provenance/extract-intent.js +262 -0
- package/dist/scope-provenance/index.d.ts +7 -1
- package/dist/scope-provenance/index.js +7 -1
- package/dist/scope-provenance/intent-digest.d.ts +12 -0
- package/dist/scope-provenance/intent-digest.js +33 -0
- package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
- package/dist/scope-provenance/intent-evaluate.js +149 -0
- package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
- package/dist/scope-provenance/json-tokenizer.js +225 -0
- package/dist/scope-provenance/known-machine.d.ts +38 -0
- package/dist/scope-provenance/known-machine.js +157 -0
- package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
- package/dist/scope-provenance/mint-artifacts.js +425 -0
- package/dist/session/ac-pass-banking.d.ts +4 -36
- package/dist/session/ac-pass-banking.js +30 -77
- package/dist/session/ac-pass-reuse.d.ts +53 -0
- package/dist/session/ac-pass-reuse.js +95 -0
- package/dist/session/ceremony-dial-evidence.d.ts +42 -0
- package/dist/session/ceremony-dial-evidence.js +194 -0
- package/dist/session/git.js +19 -4
- package/dist/session/index.d.ts +5 -0
- package/dist/session/index.js +5 -0
- package/dist/session/occupancy-stress-worker.d.ts +7 -0
- package/dist/session/occupancy-stress-worker.js +36 -0
- package/dist/session/occupancy.d.ts +97 -0
- package/dist/session/occupancy.js +422 -0
- package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
- package/dist/session/product-state-hash.d.ts +26 -0
- package/dist/session/product-state-hash.js +683 -0
- package/dist/session/session-ready.d.ts +2 -0
- package/dist/session/session-ready.js +52 -30
- package/dist/session/session-start.d.ts +7 -0
- package/dist/session/session-start.js +88 -5
- package/dist/session/verify-ac-session-cache.d.ts +42 -0
- package/dist/session/verify-ac-session-cache.js +126 -0
- package/dist/session/verify-session-ritual.d.ts +5 -0
- package/dist/session/verify-session-ritual.js +9 -0
- package/dist/slash/openclaw-deposit.js +13 -9
- package/dist/slice/constants.d.ts +2 -2
- package/dist/slice/constants.js +2 -2
- package/dist/slice/existing.js +21 -4
- package/dist/slice/lock.d.ts +16 -1
- package/dist/slice/lock.js +137 -11
- package/dist/slice/record.d.ts +8 -5
- package/dist/slice/record.js +21 -13
- package/dist/story-ready/evaluate.d.ts +2 -0
- package/dist/story-ready/evaluate.js +13 -0
- package/dist/swarm/complete-cohort.d.ts +3 -0
- package/dist/swarm/complete-cohort.js +59 -11
- package/dist/swarm/finalize-cohort.js +33 -2
- package/dist/swarm/launch.d.ts +22 -0
- package/dist/swarm/launch.js +155 -0
- package/dist/swarm/routing-set-cli.d.ts +11 -1
- package/dist/swarm/routing-set-cli.js +13 -3
- package/dist/swarm/routing-verify.d.ts +12 -0
- package/dist/swarm/routing-verify.js +59 -20
- package/dist/swarm/routing.d.ts +14 -0
- package/dist/swarm/routing.js +53 -3
- package/dist/telemetry-coverage/evaluate.d.ts +42 -0
- package/dist/telemetry-coverage/evaluate.js +148 -0
- package/dist/telemetry-coverage/fake-trial.d.ts +45 -0
- package/dist/telemetry-coverage/fake-trial.js +204 -0
- package/dist/telemetry-coverage/index.d.ts +5 -0
- package/dist/telemetry-coverage/index.js +5 -0
- package/dist/telemetry-coverage/kinds.d.ts +19 -0
- package/dist/telemetry-coverage/kinds.js +54 -0
- package/dist/telemetry-coverage/scan-callers.d.ts +25 -0
- package/dist/telemetry-coverage/scan-callers.js +168 -0
- package/dist/triage/actions/index.js +4 -1
- package/dist/ts-check-lane/index.d.ts +3 -0
- package/dist/ts-check-lane/index.js +2 -0
- package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
- package/dist/ts-check-lane/progress-reporter.js +38 -0
- package/dist/ts-check-lane/progress.d.ts +32 -0
- package/dist/ts-check-lane/progress.js +57 -0
- package/dist/ts-check-lane/run-lane.d.ts +3 -1
- package/dist/ts-check-lane/run-lane.js +8 -6
- package/dist/validate-content/validate-links.d.ts +8 -0
- package/dist/validate-content/validate-links.js +9 -15
- package/dist/value/feedback-file.js +12 -2
- package/dist/vbrief-build/index.d.ts +1 -0
- package/dist/vbrief-build/index.js +1 -0
- package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
- package/dist/vbrief-build/project-invariants-io.js +41 -0
- package/dist/vbrief-reconcile/graph.js +18 -1
- package/dist/vbrief-reconcile/index.d.ts +3 -1
- package/dist/vbrief-reconcile/index.js +2 -1
- package/dist/vbrief-reconcile/origin-freshness.d.ts +44 -0
- package/dist/vbrief-reconcile/origin-freshness.js +221 -0
- package/dist/vbrief-reconcile/types.d.ts +13 -0
- package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
- package/dist/vbrief-reconcile/umbrellas.js +261 -26
- package/dist/vbrief-validate/index.d.ts +1 -1
- package/dist/vbrief-validate/index.js +1 -1
- package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +68 -0
- package/dist/verify-ac/evaluate.d.ts +7 -1
- package/dist/verify-ac/evaluate.js +34 -26
- package/dist/verify-ac/flag.d.ts +4 -0
- package/dist/verify-ac/flag.js +16 -0
- package/dist/verify-ac/index.d.ts +2 -2
- package/dist/verify-ac/index.js +2 -2
- package/dist/verify-source/contained-writes.js +2 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/verify-stubs.d.ts +15 -1
- package/dist/verify-source/verify-stubs.js +20 -16
- package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
- package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
- package/dist/xbrief-migrate/migrate-project.js +2 -5
- package/package.json +3 -3
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xBRIEF intended-placement field + preflight check (#3424).
|
|
3
|
+
*
|
|
4
|
+
* Preflight keys on declared files vs FILE_SIZE_REVIEW_TRIGGER_LINES.
|
|
5
|
+
* Size alone is not a hard cap (#1488).
|
|
6
|
+
*
|
|
7
|
+
* ## Error policy
|
|
8
|
+
* Every anomaly on a declared file fails closed (exit 1 + remediation hint).
|
|
9
|
+
* Never throw. Never silent-skip. The only skip is first-touch `lstat` ENOENT
|
|
10
|
+
* (planned-new file — nothing to measure). If `lstat` succeeded and a later
|
|
11
|
+
* operation fails, that is an inconsistency → exit 1.
|
|
12
|
+
*
|
|
13
|
+
* ## Threat model
|
|
14
|
+
* Preflight is a quality gate the agent runs against its own declared plan
|
|
15
|
+
* in its own worktree. A concurrent local process that can swap symlinks
|
|
16
|
+
* mid-check can trivially edit the vBRIEF itself. Adversarial-local-attacker
|
|
17
|
+
* TOCTOU is a **non-goal** beyond the fd discipline below. Windows symlink
|
|
18
|
+
* creation requires elevation.
|
|
19
|
+
*
|
|
20
|
+
* ## fd discipline
|
|
21
|
+
* `lstat` rejects symlink entries outright. Resolve + containment-check once.
|
|
22
|
+
* `openSync(abs, O_RDONLY | O_NOFOLLOW)` (plain `O_RDONLY` where `O_NOFOLLOW`
|
|
23
|
+
* is undefined, i.e. Windows). `fstatSync(fd)` confirms a regular file; read
|
|
24
|
+
* from that same fd.
|
|
25
|
+
*/
|
|
26
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync } from "node:fs";
|
|
27
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
28
|
+
import { FILE_SIZE_REVIEW_TRIGGER_LINES } from "../policy/file-size-thresholds.js";
|
|
29
|
+
import { findLifecycleRootFromArtifact } from "../scope/parent-lineage.js";
|
|
30
|
+
export const INTENDED_PLACEMENT_SCHEMA = "deft.scope.intended_placement.v1";
|
|
31
|
+
export const INTENDED_PLACEMENT_MISSING_HINT = "Record plan.metadata.intended_placement with files[] and module_boundary before implementation.";
|
|
32
|
+
export const INTENDED_PLACEMENT_OVER_TRIGGER_HINT = "Record plan.metadata.intended_placement.split_plan or cohesion_exemption. Size alone is not a fail-closed cap (#1488 / #3424).";
|
|
33
|
+
export const INTENDED_PLACEMENT_GRANDFATHER_HINT = "Pre-#3424 brief: intended_placement is missing (warning). Ingest now stamps the field; record files[] before new work.";
|
|
34
|
+
function asRecord(value) {
|
|
35
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function isNonEmptyString(value) {
|
|
41
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
42
|
+
}
|
|
43
|
+
export function emptyIntendedPlacement() {
|
|
44
|
+
return {
|
|
45
|
+
schema: INTENDED_PLACEMENT_SCHEMA,
|
|
46
|
+
files: [],
|
|
47
|
+
module_boundary: "",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Stamp a skeleton intended_placement onto plan.metadata when absent (#3424). */
|
|
51
|
+
export function stampIntendedPlacement(plan) {
|
|
52
|
+
const existing = asRecord(plan.metadata) ?? {};
|
|
53
|
+
if (asRecord(existing.intended_placement) !== null) {
|
|
54
|
+
plan.metadata = existing;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
plan.metadata = {
|
|
58
|
+
...existing,
|
|
59
|
+
intended_placement: emptyIntendedPlacement(),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function parseIntendedPlacement(plan) {
|
|
63
|
+
const metadata = asRecord(plan.metadata);
|
|
64
|
+
if (metadata === null)
|
|
65
|
+
return { kind: "missing" };
|
|
66
|
+
if (!("intended_placement" in metadata) || metadata.intended_placement === undefined) {
|
|
67
|
+
return { kind: "missing" };
|
|
68
|
+
}
|
|
69
|
+
const raw = asRecord(metadata.intended_placement);
|
|
70
|
+
if (raw === null) {
|
|
71
|
+
return {
|
|
72
|
+
kind: "malformed",
|
|
73
|
+
message: `intended_placement is not an object. ${INTENDED_PLACEMENT_MISSING_HINT}`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (!Array.isArray(raw.files)) {
|
|
77
|
+
return {
|
|
78
|
+
kind: "malformed",
|
|
79
|
+
message: `intended_placement.files is not an array. ${INTENDED_PLACEMENT_MISSING_HINT}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const files = [];
|
|
83
|
+
for (let i = 0; i < raw.files.length; i++) {
|
|
84
|
+
const entry = raw.files[i];
|
|
85
|
+
if (typeof entry !== "string" || entry.trim().length === 0) {
|
|
86
|
+
return {
|
|
87
|
+
kind: "malformed",
|
|
88
|
+
message: `intended_placement.files[${i}] is not a non-empty string. ${INTENDED_PLACEMENT_MISSING_HINT}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
files.push(entry.trim());
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
kind: "ok",
|
|
95
|
+
placement: {
|
|
96
|
+
schema: typeof raw.schema === "string" ? raw.schema : undefined,
|
|
97
|
+
files,
|
|
98
|
+
module_boundary: isNonEmptyString(raw.module_boundary)
|
|
99
|
+
? raw.module_boundary.trim()
|
|
100
|
+
: undefined,
|
|
101
|
+
split_plan: isNonEmptyString(raw.split_plan) ? raw.split_plan.trim() : undefined,
|
|
102
|
+
cohesion_exemption: isNonEmptyString(raw.cohesion_exemption)
|
|
103
|
+
? raw.cohesion_exemption.trim()
|
|
104
|
+
: undefined,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export function readIntendedPlacement(plan) {
|
|
109
|
+
const parsed = parseIntendedPlacement(plan);
|
|
110
|
+
return parsed.kind === "ok" ? parsed.placement : null;
|
|
111
|
+
}
|
|
112
|
+
export function countFileLines(text) {
|
|
113
|
+
if (text.length === 0)
|
|
114
|
+
return 0;
|
|
115
|
+
const parts = text.split(/\r\n|\n|\r/);
|
|
116
|
+
return parts[parts.length - 1] === "" ? parts.length - 1 : parts.length;
|
|
117
|
+
}
|
|
118
|
+
export function resolveProjectRootFromBrief(briefPath, explicitRoot) {
|
|
119
|
+
if (explicitRoot !== undefined && explicitRoot.length > 0) {
|
|
120
|
+
return resolve(explicitRoot);
|
|
121
|
+
}
|
|
122
|
+
const lifecycle = findLifecycleRootFromArtifact(briefPath);
|
|
123
|
+
if (lifecycle !== null) {
|
|
124
|
+
return resolve(lifecycle, "..");
|
|
125
|
+
}
|
|
126
|
+
return resolve(briefPath, "..", "..");
|
|
127
|
+
}
|
|
128
|
+
function resolveDeclaredFile(projectRoot, declared) {
|
|
129
|
+
const trimmed = declared.trim();
|
|
130
|
+
if (trimmed.length === 0)
|
|
131
|
+
return null;
|
|
132
|
+
const abs = isAbsolute(trimmed) ? resolve(trimmed) : resolve(projectRoot, trimmed);
|
|
133
|
+
const rel = relative(projectRoot, abs);
|
|
134
|
+
if (rel.startsWith("..") || rel === "" || rel.split(sep).includes("..")) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
return abs;
|
|
138
|
+
}
|
|
139
|
+
function rejectInspect(declared, reason) {
|
|
140
|
+
return {
|
|
141
|
+
kind: "reject",
|
|
142
|
+
message: `Could not inspect intended file '${declared}': ${reason}. ${INTENDED_PLACEMENT_MISSING_HINT}`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function errnoCode(err) {
|
|
146
|
+
return err.code;
|
|
147
|
+
}
|
|
148
|
+
function errReason(err) {
|
|
149
|
+
return err instanceof Error ? err.message : String(err);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* lstat (first-touch ENOENT = planned-new skip) → reject symlink →
|
|
153
|
+
* open+fstat+read the same fd. Later ENOENT is inconsistency, not skip.
|
|
154
|
+
*/
|
|
155
|
+
function inspectDeclaredFile(declared, abs) {
|
|
156
|
+
let info;
|
|
157
|
+
try {
|
|
158
|
+
info = lstatSync(abs);
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
if (errnoCode(err) === "ENOENT") {
|
|
162
|
+
return { kind: "missing" };
|
|
163
|
+
}
|
|
164
|
+
return rejectInspect(declared, errReason(err));
|
|
165
|
+
}
|
|
166
|
+
if (info.isSymbolicLink()) {
|
|
167
|
+
return {
|
|
168
|
+
kind: "reject",
|
|
169
|
+
message: `Intended path '${declared}' is a symlink. ${INTENDED_PLACEMENT_MISSING_HINT}`,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (!info.isFile()) {
|
|
173
|
+
return {
|
|
174
|
+
kind: "reject",
|
|
175
|
+
message: `Intended path '${declared}' is not a regular file. ${INTENDED_PLACEMENT_MISSING_HINT}`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const nofollow = constants.O_NOFOLLOW;
|
|
179
|
+
const flags = constants.O_RDONLY | (typeof nofollow === "number" ? nofollow : 0);
|
|
180
|
+
let fd;
|
|
181
|
+
try {
|
|
182
|
+
fd = openSync(abs, flags);
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
const code = errnoCode(err);
|
|
186
|
+
if (code === "ELOOP" || code === "EMLINK") {
|
|
187
|
+
return {
|
|
188
|
+
kind: "reject",
|
|
189
|
+
message: `Intended path '${declared}' is a symlink. ${INTENDED_PLACEMENT_MISSING_HINT}`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return rejectInspect(declared, errReason(err));
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const st = fstatSync(fd);
|
|
196
|
+
if (!st.isFile()) {
|
|
197
|
+
return {
|
|
198
|
+
kind: "reject",
|
|
199
|
+
message: `Intended path '${declared}' is not a regular file. ${INTENDED_PLACEMENT_MISSING_HINT}`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return { kind: "file", text: readFileSync(fd, "utf8") };
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
return rejectInspect(declared, errReason(err));
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
try {
|
|
209
|
+
closeSync(fd);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// already closed or invalid
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function hasRemediation(placement) {
|
|
217
|
+
return ((placement.split_plan !== undefined && placement.split_plan.length > 0) ||
|
|
218
|
+
(placement.cohesion_exemption !== undefined && placement.cohesion_exemption.length > 0));
|
|
219
|
+
}
|
|
220
|
+
export function evaluateIntendedPlacement(plan, options) {
|
|
221
|
+
const parsed = parseIntendedPlacement(plan);
|
|
222
|
+
if (parsed.kind === "missing") {
|
|
223
|
+
return {
|
|
224
|
+
ok: true,
|
|
225
|
+
warning: true,
|
|
226
|
+
message: INTENDED_PLACEMENT_GRANDFATHER_HINT,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (parsed.kind === "malformed") {
|
|
230
|
+
return { ok: false, message: parsed.message };
|
|
231
|
+
}
|
|
232
|
+
const placement = parsed.placement;
|
|
233
|
+
if (placement.files.length === 0) {
|
|
234
|
+
return {
|
|
235
|
+
ok: true,
|
|
236
|
+
message: "intended placement pending (ingest scaffold; no declared files)",
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
if (!isNonEmptyString(placement.module_boundary)) {
|
|
240
|
+
return {
|
|
241
|
+
ok: false,
|
|
242
|
+
message: `xBRIEF intended_placement lacks module_boundary. ${INTENDED_PLACEMENT_MISSING_HINT}`,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const root = resolve(options.projectRoot);
|
|
246
|
+
const over = [];
|
|
247
|
+
for (const declared of placement.files) {
|
|
248
|
+
const abs = resolveDeclaredFile(root, declared);
|
|
249
|
+
if (abs === null) {
|
|
250
|
+
return {
|
|
251
|
+
ok: false,
|
|
252
|
+
message: `Intended file '${declared}' escapes the project root. ${INTENDED_PLACEMENT_MISSING_HINT}`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const inspected = inspectDeclaredFile(declared, abs);
|
|
256
|
+
if (inspected.kind === "missing") {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (inspected.kind === "reject") {
|
|
260
|
+
return { ok: false, message: inspected.message };
|
|
261
|
+
}
|
|
262
|
+
const lines = countFileLines(inspected.text);
|
|
263
|
+
if (lines >= FILE_SIZE_REVIEW_TRIGGER_LINES) {
|
|
264
|
+
over.push(`${declared} (${lines} lines >= ${FILE_SIZE_REVIEW_TRIGGER_LINES})`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (over.length > 0 && !hasRemediation(placement)) {
|
|
268
|
+
return {
|
|
269
|
+
ok: false,
|
|
270
|
+
message: `Declared file already over the review-trigger threshold: ${over.join("; ")}. ${INTENDED_PLACEMENT_OVER_TRIGGER_HINT}`,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return { ok: true, message: "intended placement OK" };
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=intended-placement.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preflight / story-ready fail-closed check for applicable project invariants (#3425 Story B).
|
|
3
|
+
*
|
|
4
|
+
* Evaluates the PROJECT-DEFINITION list as of check time (list-drift).
|
|
5
|
+
* Completeness only — does not prove a declared `covered` is true.
|
|
6
|
+
*/
|
|
7
|
+
export declare const PROJECT_INVARIANT_REMEDIATION: string;
|
|
8
|
+
export interface ProjectInvariantsGateResult {
|
|
9
|
+
readonly ok: boolean;
|
|
10
|
+
readonly message: string;
|
|
11
|
+
readonly applicableIds: readonly string[];
|
|
12
|
+
readonly missingIds: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export interface ProjectInvariantsGateOptions {
|
|
15
|
+
readonly projectRoot?: string;
|
|
16
|
+
readonly skip?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Prefer an explicit project root. Otherwise walk up from the story path to
|
|
20
|
+
* the xbrief/vbrief lifecycle folder and take its parent (the repo root).
|
|
21
|
+
* Lets `xbrief:preflight` fire without a CLI `--project-root`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveProjectRootForInvariants(artifactPath: string, explicit?: string): string | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Fail closed when an applicable project invariant ID has no coverage disposition.
|
|
26
|
+
* Empty or absent list is a no-op. Slice- and worktree-scoped stories share this check.
|
|
27
|
+
*/
|
|
28
|
+
export declare function evaluateProjectInvariantsGate(story: unknown, options?: ProjectInvariantsGateOptions): ProjectInvariantsGateResult;
|
|
29
|
+
//# sourceMappingURL=project-invariants-gate.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preflight / story-ready fail-closed check for applicable project invariants (#3425 Story B).
|
|
3
|
+
*
|
|
4
|
+
* Evaluates the PROJECT-DEFINITION list as of check time (list-drift).
|
|
5
|
+
* Completeness only — does not prove a declared `covered` is true.
|
|
6
|
+
*/
|
|
7
|
+
import { dirname } from "node:path";
|
|
8
|
+
import { extractModulePathGlobs, resolveProjectInvariants } from "../policy/project-invariants.js";
|
|
9
|
+
import { loadProjectDefinition } from "../policy/resolve.js";
|
|
10
|
+
import { extractStoryFileScope } from "../policy/write-fence.js";
|
|
11
|
+
import { extractChildCoverageDraft, findLifecycleRootFromArtifact, } from "../scope/parent-lineage.js";
|
|
12
|
+
import { applicableProjectInvariants, PROJECT_INVARIANT_DISPOSITIONS, validateProjectInvariantCoverage, } from "../scope/project-invariant-coverage.js";
|
|
13
|
+
export const PROJECT_INVARIANT_REMEDIATION = "Declare coverage_map[<id>].disposition as " +
|
|
14
|
+
`${PROJECT_INVARIANT_DISPOSITIONS.join("|")} ` +
|
|
15
|
+
"(split is excluded at project level).";
|
|
16
|
+
/**
|
|
17
|
+
* Prefer an explicit project root. Otherwise walk up from the story path to
|
|
18
|
+
* the xbrief/vbrief lifecycle folder and take its parent (the repo root).
|
|
19
|
+
* Lets `xbrief:preflight` fire without a CLI `--project-root`.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveProjectRootForInvariants(artifactPath, explicit) {
|
|
22
|
+
if (explicit !== undefined && explicit.length > 0)
|
|
23
|
+
return explicit;
|
|
24
|
+
const lifecycle = findLifecycleRootFromArtifact(artifactPath);
|
|
25
|
+
return lifecycle === null ? undefined : dirname(lifecycle);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Fail closed when an applicable project invariant ID has no coverage disposition.
|
|
29
|
+
* Empty or absent list is a no-op. Slice- and worktree-scoped stories share this check.
|
|
30
|
+
*/
|
|
31
|
+
export function evaluateProjectInvariantsGate(story, options = {}) {
|
|
32
|
+
if (options.skip === true) {
|
|
33
|
+
return { ok: true, message: "project invariants skipped", applicableIds: [], missingIds: [] };
|
|
34
|
+
}
|
|
35
|
+
const projectRoot = options.projectRoot;
|
|
36
|
+
if (projectRoot === undefined || projectRoot.length === 0) {
|
|
37
|
+
return {
|
|
38
|
+
ok: true,
|
|
39
|
+
message: "project invariants N/A (no project root)",
|
|
40
|
+
applicableIds: [],
|
|
41
|
+
missingIds: [],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const resolved = resolveProjectInvariants(projectRoot);
|
|
45
|
+
if (resolved.invariants.length === 0) {
|
|
46
|
+
return {
|
|
47
|
+
ok: true,
|
|
48
|
+
message: "project invariants empty (no-op)",
|
|
49
|
+
applicableIds: [],
|
|
50
|
+
missingIds: [],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const [data] = loadProjectDefinition(projectRoot);
|
|
54
|
+
const modulePathGlobs = extractModulePathGlobs(data);
|
|
55
|
+
const { fileScope } = extractStoryFileScope(story);
|
|
56
|
+
const applicable = applicableProjectInvariants(resolved.invariants, fileScope, modulePathGlobs);
|
|
57
|
+
const applicableIds = applicable.map((a) => a.id);
|
|
58
|
+
const extracted = extractChildCoverageDraft(story);
|
|
59
|
+
const coverage = validateProjectInvariantCoverage({
|
|
60
|
+
applicableIds,
|
|
61
|
+
draft: extracted.draft ?? {},
|
|
62
|
+
});
|
|
63
|
+
if (coverage.ok) {
|
|
64
|
+
return {
|
|
65
|
+
ok: true,
|
|
66
|
+
message: applicableIds.length === 0
|
|
67
|
+
? "project invariants: none applicable"
|
|
68
|
+
: `project invariants OK (${applicableIds.length} applicable)`,
|
|
69
|
+
applicableIds,
|
|
70
|
+
missingIds: [],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const omitted = coverage.missingIds.length > 0 ? coverage.missingIds.join(", ") : "(see errors)";
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
applicableIds,
|
|
77
|
+
missingIds: coverage.missingIds,
|
|
78
|
+
message: `applicable project invariant ID omitted: ${omitted}. ${PROJECT_INVARIANT_REMEDIATION} ` +
|
|
79
|
+
coverage.errors.join("; "),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=project-invariants-gate.js.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One resolver for "does this artifact carry executable acceptance that passed?" (#3497).
|
|
3
|
+
*
|
|
4
|
+
* Before this module `verify:ac` and `scope:complete` read the same artifact through
|
|
5
|
+
* two different lenses and reported two different things:
|
|
6
|
+
*
|
|
7
|
+
* - verify:ac composed a message from the sub-gates (`verify:ac passed`, `Literal
|
|
8
|
+
* acceptance-command gate passed`) and then flipped `ok` in a later stage
|
|
9
|
+
* (clause walk / no-op ledger / oracle integrity) without relabelling the message.
|
|
10
|
+
* - scope:complete took that `ok=false` and asserted "empty or failing acceptance"
|
|
11
|
+
* even when acceptance was neither empty nor failing.
|
|
12
|
+
*
|
|
13
|
+
* Both now read the verdict here. One predicate name, one observed value, printed
|
|
14
|
+
* identically by both consumers.
|
|
15
|
+
*
|
|
16
|
+
* This module is intentionally structural (no import of `VerifyAcResult`) so the
|
|
17
|
+
* done-gate evaluator and the scope gate can both depend on it without a cycle.
|
|
18
|
+
*/
|
|
19
|
+
import type { VerifyAcResolution } from "./empty-resolution.js";
|
|
20
|
+
import type { AcSourceRung } from "./types.js";
|
|
21
|
+
/**
|
|
22
|
+
* Which check decided the acceptance verdict. Named in operator output so the
|
|
23
|
+
* message can never again assert a predicate that did not fire (#3497).
|
|
24
|
+
*/
|
|
25
|
+
export type AcceptancePredicate =
|
|
26
|
+
/** Executable acceptance commands ran and every one exited as expected. */
|
|
27
|
+
"executable-pass"
|
|
28
|
+
/** Nothing executable to run and the project floor allows it. */
|
|
29
|
+
| "empty-pass"
|
|
30
|
+
/** plan.acceptance schema / brief could not be read. */
|
|
31
|
+
| "config-error"
|
|
32
|
+
/** A stated command cannot fail (#3396 no-op denylist). */
|
|
33
|
+
| "noop-refused"
|
|
34
|
+
/** Safety-rejected shell-shaped stated command(s) block completion (#3267). */
|
|
35
|
+
| "safety-rejected"
|
|
36
|
+
/** At least one executable command exited other than expected. */
|
|
37
|
+
| "commands-failed"
|
|
38
|
+
/** No executable acceptance stamped and no suite floor to stand on (#3334). */
|
|
39
|
+
| "empty-acceptance"
|
|
40
|
+
/** Derived clause walk contradicted the shipped artifact (#3323). */
|
|
41
|
+
| "clause-walk-failed"
|
|
42
|
+
/** Unresolved pass-after-fail-with-method-change (#3322). */
|
|
43
|
+
| "integrity-discrepancy"
|
|
44
|
+
/** Fell through every named predicate — carries the message lead verbatim. */
|
|
45
|
+
| "unclassified";
|
|
46
|
+
export interface AcceptanceVerdict {
|
|
47
|
+
readonly ok: boolean;
|
|
48
|
+
readonly predicate: AcceptancePredicate;
|
|
49
|
+
/** The value the deciding check actually read. Never a restatement of the rule. */
|
|
50
|
+
readonly observed: string;
|
|
51
|
+
/** What to change so the predicate stops firing. */
|
|
52
|
+
readonly remedy: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Structural view of a verify:ac result. `VerifyAcResult` satisfies this shape;
|
|
56
|
+
* keeping it structural avoids an import cycle with `evaluate.ts`.
|
|
57
|
+
*/
|
|
58
|
+
export interface AcceptanceReading {
|
|
59
|
+
readonly ok: boolean;
|
|
60
|
+
readonly code: number;
|
|
61
|
+
readonly message: string;
|
|
62
|
+
readonly resolution: VerifyAcResolution;
|
|
63
|
+
readonly sourceRung: AcSourceRung;
|
|
64
|
+
readonly runs: readonly {
|
|
65
|
+
readonly ok: boolean;
|
|
66
|
+
readonly command: string;
|
|
67
|
+
}[];
|
|
68
|
+
readonly commands: readonly unknown[];
|
|
69
|
+
readonly rejected?: readonly {
|
|
70
|
+
readonly command: string;
|
|
71
|
+
readonly reason: string;
|
|
72
|
+
}[];
|
|
73
|
+
readonly advisoryRejected?: readonly {
|
|
74
|
+
readonly command: string;
|
|
75
|
+
}[];
|
|
76
|
+
readonly clauseOutcomes?: readonly {
|
|
77
|
+
readonly id: number;
|
|
78
|
+
readonly outcome: string;
|
|
79
|
+
}[];
|
|
80
|
+
readonly acceptance: {
|
|
81
|
+
readonly commands: readonly unknown[];
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Executable command count this reading resolved (stamped or ledger). */
|
|
85
|
+
export declare function resolvedAcceptanceCommandCount(reading: AcceptanceReading): number;
|
|
86
|
+
/**
|
|
87
|
+
* Name the predicate that decided this verify:ac result, and the value it read.
|
|
88
|
+
*
|
|
89
|
+
* Order matters: the earliest fail-closed stage wins so the operator is pointed at
|
|
90
|
+
* the check that actually refused rather than a downstream symptom.
|
|
91
|
+
*/
|
|
92
|
+
export declare function resolveAcceptanceVerdict(reading: AcceptanceReading): AcceptanceVerdict;
|
|
93
|
+
/** One line naming the predicate and the value it read (#3497). */
|
|
94
|
+
export declare function formatAcceptanceVerdict(verdictResult: AcceptanceVerdict): string;
|
|
95
|
+
/**
|
|
96
|
+
* Clause-walk composition (#3323 / #3497).
|
|
97
|
+
*
|
|
98
|
+
* A clause the shipped artifact contradicts (`failed`) always blocks. A clause the
|
|
99
|
+
* static walk cannot decide (`unverifiable`) is evidence of nothing — it blocks only
|
|
100
|
+
* when nothing else verified the product. A green executable acceptance run IS that
|
|
101
|
+
* something: the product-first oracle already ran. Before #3497 an all-unverifiable
|
|
102
|
+
* clause set refused an artifact whose stated command had just exited 0.
|
|
103
|
+
*/
|
|
104
|
+
export declare function clauseWalkBlocks(input: {
|
|
105
|
+
readonly failed: number;
|
|
106
|
+
readonly verified: number;
|
|
107
|
+
readonly walked: number;
|
|
108
|
+
readonly hasGreenExecutableRun: boolean;
|
|
109
|
+
}): boolean;
|
|
110
|
+
/** Which consumer is reading acceptance. Selects one shared option profile (#3497). */
|
|
111
|
+
export type AcceptanceReaderProfile =
|
|
112
|
+
/** `task verify:ac -- <xbrief>` standalone done-gate run. */
|
|
113
|
+
"standalone"
|
|
114
|
+
/** `task check` composition (`--soft-missing-xbrief`). */
|
|
115
|
+
| "check"
|
|
116
|
+
/** `scope:complete` hard precondition walk (#3357). */
|
|
117
|
+
| "complete";
|
|
118
|
+
export interface AcceptanceGateProfileOptions {
|
|
119
|
+
readonly captureFromNarratives: boolean | undefined;
|
|
120
|
+
readonly checkIntegrated: boolean;
|
|
121
|
+
readonly reuseMode: "auto" | "bank" | "never";
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The single option set each reader uses. Previously verify:ac and scope:complete
|
|
125
|
+
* hand-assembled divergent option objects at their call sites (#3497).
|
|
126
|
+
*/
|
|
127
|
+
export declare function resolveAcceptanceGateProfile(profile: AcceptanceReaderProfile): AcceptanceGateProfileOptions;
|
|
128
|
+
//# sourceMappingURL=acceptance-resolver.d.ts.map
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One resolver for "does this artifact carry executable acceptance that passed?" (#3497).
|
|
3
|
+
*
|
|
4
|
+
* Before this module `verify:ac` and `scope:complete` read the same artifact through
|
|
5
|
+
* two different lenses and reported two different things:
|
|
6
|
+
*
|
|
7
|
+
* - verify:ac composed a message from the sub-gates (`verify:ac passed`, `Literal
|
|
8
|
+
* acceptance-command gate passed`) and then flipped `ok` in a later stage
|
|
9
|
+
* (clause walk / no-op ledger / oracle integrity) without relabelling the message.
|
|
10
|
+
* - scope:complete took that `ok=false` and asserted "empty or failing acceptance"
|
|
11
|
+
* even when acceptance was neither empty nor failing.
|
|
12
|
+
*
|
|
13
|
+
* Both now read the verdict here. One predicate name, one observed value, printed
|
|
14
|
+
* identically by both consumers.
|
|
15
|
+
*
|
|
16
|
+
* This module is intentionally structural (no import of `VerifyAcResult`) so the
|
|
17
|
+
* done-gate evaluator and the scope gate can both depend on it without a cycle.
|
|
18
|
+
*/
|
|
19
|
+
import { NOOP_ACCEPTANCE_REMEDIATION } from "../literal-acceptance/index.js";
|
|
20
|
+
const REMEDY = {
|
|
21
|
+
"executable-pass": "",
|
|
22
|
+
"empty-pass": "",
|
|
23
|
+
"config-error": "fix plan.acceptance so it matches the #3284 schema, then re-run task verify:ac",
|
|
24
|
+
"noop-refused": NOOP_ACCEPTANCE_REMEDIATION,
|
|
25
|
+
"safety-rejected": "promote a safe alternative into plan.metadata.swarm.verify_commands or remove the stated command from the task statement (#3267)",
|
|
26
|
+
"commands-failed": "fix the product until the stated acceptance command exits as expected — the command, not the gate, is the oracle (#3284)",
|
|
27
|
+
"empty-acceptance": "stamp executable commands on plan.acceptance.commands (or plan.metadata.swarm.verify_commands) with source_rung derived|project_floor (#3334)",
|
|
28
|
+
"clause-walk-failed": "ship the artifact each failed clause names, or bind the clause to the path it actually landed at (#3323)",
|
|
29
|
+
"integrity-discrepancy": "resolve by a product change under the same method, or independently re-derive both sides and record independent_rederivation=true (#3322)",
|
|
30
|
+
unclassified: "read the verify:ac message below; the deciding check did not name itself",
|
|
31
|
+
};
|
|
32
|
+
function countClauses(reading, outcome) {
|
|
33
|
+
return (reading.clauseOutcomes ?? []).filter((row) => row.outcome === outcome).length;
|
|
34
|
+
}
|
|
35
|
+
function firstLine(message) {
|
|
36
|
+
const line = message.split("\n").find((row) => row.trim().length > 0);
|
|
37
|
+
return line === undefined ? "(no detail)" : line.trim();
|
|
38
|
+
}
|
|
39
|
+
function verdict(ok, predicate, observed) {
|
|
40
|
+
return { ok, predicate, observed, remedy: REMEDY[predicate] };
|
|
41
|
+
}
|
|
42
|
+
/** Executable command count this reading resolved (stamped or ledger). */
|
|
43
|
+
export function resolvedAcceptanceCommandCount(reading) {
|
|
44
|
+
return Math.max(reading.commands.length, reading.acceptance.commands.length);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Name the predicate that decided this verify:ac result, and the value it read.
|
|
48
|
+
*
|
|
49
|
+
* Order matters: the earliest fail-closed stage wins so the operator is pointed at
|
|
50
|
+
* the check that actually refused rather than a downstream symptom.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveAcceptanceVerdict(reading) {
|
|
53
|
+
const commandCount = resolvedAcceptanceCommandCount(reading);
|
|
54
|
+
const greenRuns = reading.runs.filter((run) => run.ok).length;
|
|
55
|
+
const failedRuns = reading.runs.filter((run) => !run.ok);
|
|
56
|
+
if (reading.ok) {
|
|
57
|
+
if (reading.runs.length > 0) {
|
|
58
|
+
return verdict(true, "executable-pass", `${greenRuns}/${reading.runs.length} command(s) exited as expected [rung=${reading.sourceRung}]`);
|
|
59
|
+
}
|
|
60
|
+
return verdict(true, "empty-pass", `no executable command resolved; project floor allows it [rung=${reading.sourceRung}]`);
|
|
61
|
+
}
|
|
62
|
+
if (reading.resolution === "config" || reading.code === 2) {
|
|
63
|
+
return verdict(false, "config-error", firstLine(reading.message));
|
|
64
|
+
}
|
|
65
|
+
if (reading.resolution === "rejected-noop") {
|
|
66
|
+
const noop = (reading.rejected ?? []).map((row) => row.command);
|
|
67
|
+
return verdict(false, "noop-refused", noop.length > 0
|
|
68
|
+
? `blocking ledger command(s) that cannot fail: ${noop.join(", ")}`
|
|
69
|
+
: firstLine(reading.message));
|
|
70
|
+
}
|
|
71
|
+
const rejected = reading.rejected ?? [];
|
|
72
|
+
if (rejected.length > 0) {
|
|
73
|
+
return verdict(false, "safety-rejected", `${rejected.length} blocking safety-rejected command(s): ${rejected
|
|
74
|
+
.map((row) => row.command)
|
|
75
|
+
.join(", ")}`);
|
|
76
|
+
}
|
|
77
|
+
if (failedRuns.length > 0) {
|
|
78
|
+
return verdict(false, "commands-failed", `${failedRuns.length}/${reading.runs.length} command(s) did not exit as expected: ${failedRuns
|
|
79
|
+
.map((row) => row.command)
|
|
80
|
+
.join(", ")}`);
|
|
81
|
+
}
|
|
82
|
+
if (reading.resolution === "soft_empty") {
|
|
83
|
+
return verdict(false, "empty-acceptance", `plan.acceptance.commands=${commandCount}, runs=0 [rung=${reading.sourceRung}]`);
|
|
84
|
+
}
|
|
85
|
+
if ((reading.clauseOutcomes?.length ?? 0) > 0) {
|
|
86
|
+
const failed = countClauses(reading, "failed");
|
|
87
|
+
const unverifiable = countClauses(reading, "unverifiable");
|
|
88
|
+
const verified = countClauses(reading, "verified");
|
|
89
|
+
return verdict(false, "clause-walk-failed", `clause walk read ${verified} verified, ${unverifiable} unverifiable, ${failed} failed ` +
|
|
90
|
+
`against ${commandCount} stamped command(s) and ${reading.runs.length} run(s)`);
|
|
91
|
+
}
|
|
92
|
+
if (/#3322/.test(reading.message)) {
|
|
93
|
+
return verdict(false, "integrity-discrepancy", firstLine(reading.message));
|
|
94
|
+
}
|
|
95
|
+
if (commandCount === 0) {
|
|
96
|
+
return verdict(false, "empty-acceptance", `plan.acceptance.commands=0, runs=0 [rung=${reading.sourceRung}]`);
|
|
97
|
+
}
|
|
98
|
+
return verdict(false, "unclassified", firstLine(reading.message));
|
|
99
|
+
}
|
|
100
|
+
/** One line naming the predicate and the value it read (#3497). */
|
|
101
|
+
export function formatAcceptanceVerdict(verdictResult) {
|
|
102
|
+
const lead = `acceptance verdict (#3497): ${verdictResult.predicate} — ${verdictResult.observed}`;
|
|
103
|
+
if (verdictResult.ok || verdictResult.remedy.length === 0) {
|
|
104
|
+
return lead;
|
|
105
|
+
}
|
|
106
|
+
return `${lead}\n remedy: ${verdictResult.remedy}`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Clause-walk composition (#3323 / #3497).
|
|
110
|
+
*
|
|
111
|
+
* A clause the shipped artifact contradicts (`failed`) always blocks. A clause the
|
|
112
|
+
* static walk cannot decide (`unverifiable`) is evidence of nothing — it blocks only
|
|
113
|
+
* when nothing else verified the product. A green executable acceptance run IS that
|
|
114
|
+
* something: the product-first oracle already ran. Before #3497 an all-unverifiable
|
|
115
|
+
* clause set refused an artifact whose stated command had just exited 0.
|
|
116
|
+
*/
|
|
117
|
+
export function clauseWalkBlocks(input) {
|
|
118
|
+
if (input.failed > 0) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
if (input.walked === 0 || input.verified > 0) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
return !input.hasGreenExecutableRun;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The single option set each reader uses. Previously verify:ac and scope:complete
|
|
128
|
+
* hand-assembled divergent option objects at their call sites (#3497).
|
|
129
|
+
*/
|
|
130
|
+
export function resolveAcceptanceGateProfile(profile) {
|
|
131
|
+
switch (profile) {
|
|
132
|
+
case "check":
|
|
133
|
+
// Re-scanning issue prose during `task check` re-captures backtick lines
|
|
134
|
+
// as rejected and deadlocks the graph (#3323).
|
|
135
|
+
return { captureFromNarratives: false, checkIntegrated: true, reuseMode: "auto" };
|
|
136
|
+
case "complete":
|
|
137
|
+
// Completion re-scans narratives so a narrative-only stated command fails
|
|
138
|
+
// closed rather than being skipped, and never serves from a same-session cache.
|
|
139
|
+
return { captureFromNarratives: true, checkIntegrated: false, reuseMode: "bank" };
|
|
140
|
+
default:
|
|
141
|
+
return { captureFromNarratives: undefined, checkIntegrated: false, reuseMode: "auto" };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=acceptance-resolver.js.map
|