@deftai/directive-core 0.98.1 → 0.100.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 +6 -0
- package/dist/authz/actions.js +3 -0
- package/dist/authz/classify.js +401 -75
- package/dist/authz/decompose-apply.d.ts +67 -0
- package/dist/authz/decompose-apply.js +302 -0
- package/dist/authz/index.d.ts +3 -1
- package/dist/authz/index.js +3 -1
- package/dist/authz/store.d.ts +53 -0
- package/dist/authz/store.js +219 -1
- package/dist/authz/types.d.ts +19 -2
- package/dist/authz/types.js +5 -1
- package/dist/consumer-check-contract/evaluate.d.ts +40 -0
- package/dist/consumer-check-contract/evaluate.js +188 -3
- package/dist/consumer-check-contract/index.d.ts +1 -1
- package/dist/consumer-check-contract/index.js +1 -1
- package/dist/content-contracts/skills/greptile-detector.d.ts +42 -0
- package/dist/content-contracts/skills/greptile-detector.js +202 -4
- package/dist/decision/index.d.ts +17 -0
- package/dist/decision/index.js +35 -0
- package/dist/decision/list.d.ts +47 -0
- package/dist/decision/list.js +250 -0
- package/dist/decision/schema.d.ts +88 -0
- package/dist/decision/schema.js +293 -0
- package/dist/decision/write.d.ts +82 -0
- package/dist/decision/write.js +427 -0
- package/dist/doctor/checks.d.ts +44 -0
- package/dist/doctor/checks.js +428 -2
- package/dist/doctor/main.d.ts +6 -7
- package/dist/doctor/main.js +72 -79
- package/dist/doctor/which.d.ts +20 -1
- package/dist/doctor/which.js +67 -1
- package/dist/eval/report.d.ts +29 -0
- package/dist/eval/report.js +69 -0
- package/dist/eval/run.d.ts +9 -0
- package/dist/eval/run.js +40 -4
- package/dist/eval/version-pin.d.ts +99 -0
- package/dist/eval/version-pin.js +181 -0
- package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
- package/dist/finish-loop/pr-finish-loop.js +116 -5
- package/dist/finish-loop/types.d.ts +1 -1
- package/dist/hooks/dispatcher.d.ts +9 -5
- package/dist/hooks/dispatcher.js +23 -15
- package/dist/hooks/readonly.d.ts +6 -3
- package/dist/hooks/readonly.js +44 -25
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/agent-hooks.d.ts +20 -0
- package/dist/init-deposit/agent-hooks.js +51 -31
- package/dist/intake/issue-ingest.js +29 -0
- package/dist/lifecycle/completed-consistency.d.ts +60 -0
- package/dist/lifecycle/completed-consistency.js +443 -0
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.js +2 -0
- package/dist/literal-acceptance/capture.d.ts +52 -0
- package/dist/literal-acceptance/capture.js +658 -0
- package/dist/literal-acceptance/evaluate.d.ts +47 -0
- package/dist/literal-acceptance/evaluate.js +193 -0
- package/dist/literal-acceptance/index.d.ts +12 -0
- package/dist/literal-acceptance/index.js +12 -0
- package/dist/literal-acceptance/run.d.ts +38 -0
- package/dist/literal-acceptance/run.js +216 -0
- package/dist/literal-acceptance/safety.d.ts +24 -0
- package/dist/literal-acceptance/safety.js +251 -0
- package/dist/literal-acceptance/types.d.ts +82 -0
- package/dist/literal-acceptance/types.js +26 -0
- package/dist/platform/host-content-surface.d.ts +74 -0
- package/dist/platform/host-content-surface.js +214 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/policy/ceremony-dial.d.ts +289 -0
- package/dist/policy/ceremony-dial.js +980 -0
- package/dist/policy/deft-directive-disable.js +12 -2
- package/dist/policy/index.d.ts +2 -0
- package/dist/policy/index.js +16 -1
- package/dist/policy/merge-approval-head.d.ts +129 -0
- package/dist/policy/merge-approval-head.js +461 -0
- package/dist/policy/require-human-merge.d.ts +5 -0
- package/dist/policy/require-human-merge.js +5 -0
- package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
- package/dist/pr-merge-readiness/ci-gate.js +65 -5
- package/dist/pr-merge-readiness/compute.d.ts +13 -0
- package/dist/pr-merge-readiness/compute.js +99 -14
- package/dist/pr-merge-readiness/evaluate.js +10 -0
- package/dist/pr-merge-readiness/gh.d.ts +49 -0
- package/dist/pr-merge-readiness/gh.js +268 -0
- package/dist/pr-merge-readiness/index.d.ts +2 -2
- package/dist/pr-merge-readiness/index.js +2 -2
- package/dist/pr-merge-readiness/mergeability.js +5 -0
- package/dist/pr-merge-readiness/output.js +2 -0
- package/dist/pr-merge-readiness/parse.js +4 -0
- package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
- package/dist/pr-merge-readiness/types.d.ts +6 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
- package/dist/pr-wait-mergeable/cascade.js +115 -1
- package/dist/pr-wait-mergeable/main.d.ts +4 -0
- package/dist/pr-wait-mergeable/main.js +4 -0
- package/dist/pr-wait-mergeable/types.d.ts +3 -1
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
- package/dist/pr-wait-mergeable/wrappers.js +7 -1
- package/dist/preflight/evaluate.d.ts +15 -1
- package/dist/preflight/evaluate.js +33 -2
- package/dist/render/framework-commands.js +4 -0
- package/dist/scope/acceptance-evidence.d.ts +76 -0
- package/dist/scope/acceptance-evidence.js +348 -0
- package/dist/scope/coverage-map.d.ts +98 -0
- package/dist/scope/coverage-map.js +558 -0
- package/dist/scope/decompose.js +125 -11
- package/dist/scope/effort-activate-gate.d.ts +28 -0
- package/dist/scope/effort-activate-gate.js +64 -0
- package/dist/scope/index.d.ts +4 -0
- package/dist/scope/index.js +4 -0
- package/dist/scope/parent-lineage.d.ts +125 -0
- package/dist/scope/parent-lineage.js +626 -0
- package/dist/scope/transition.d.ts +8 -0
- package/dist/scope/transition.js +69 -2
- package/dist/session/active-cli.d.ts +79 -0
- package/dist/session/active-cli.js +382 -0
- package/dist/session/effort-budget.d.ts +130 -0
- package/dist/session/effort-budget.js +334 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/session-start.d.ts +30 -1
- package/dist/session/session-start.js +234 -26
- package/dist/session/verify-session-ritual.d.ts +14 -0
- package/dist/session/verify-session-ritual.js +33 -0
- package/dist/story-ready/evaluate.d.ts +11 -0
- package/dist/story-ready/evaluate.js +41 -3
- package/dist/swarm/index.d.ts +2 -0
- package/dist/swarm/index.js +2 -0
- package/dist/swarm/pre-dispatch-cli.d.ts +19 -0
- package/dist/swarm/pre-dispatch-cli.js +143 -0
- package/dist/swarm/pre-dispatch.d.ts +87 -0
- package/dist/swarm/pre-dispatch.js +373 -0
- package/dist/triage/actions/candidates-log.d.ts +1 -2
- package/dist/triage/actions/candidates-log.js +37 -6
- package/dist/vbrief-activate/activate.js +6 -0
- package/dist/vbrief-validate/constants.d.ts +2 -0
- package/dist/vbrief-validate/constants.js +2 -0
- package/dist/vbrief-validate/schema.js +4 -1
- package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
- package/dist/xbrief-migrate/migrate-project.js +92 -3
- package/package.json +19 -3
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-origin structural apply gate for scope:decompose (#3239 / epic #3237 Q2).
|
|
3
|
+
*
|
|
4
|
+
* Apply requires a human-origin grant bound to:
|
|
5
|
+
* project root identity (worktree and/or repo), parent path, draft/target path,
|
|
6
|
+
* SHA-256 of exact draft bytes, and op scope.decompose.apply.structural.
|
|
7
|
+
*
|
|
8
|
+
* --check stays ungated (caller responsibility). Missing / expired / mismatched /
|
|
9
|
+
* agent-origin grants fail closed before any child story writes.
|
|
10
|
+
*/
|
|
11
|
+
import { type AuthzDecisionCode, type HumanOriginGrant } from "./types.js";
|
|
12
|
+
export type DecomposeApplyDecisionCode = AuthzDecisionCode | "authz-grant-digest-mismatch" | "authz-grant-parent-mismatch" | "authz-grant-target-mismatch" | "authz-grant-project-mismatch" | "authz-grant-binding-incomplete";
|
|
13
|
+
export interface DecomposeApplyDecision {
|
|
14
|
+
readonly allowed: boolean;
|
|
15
|
+
readonly code: DecomposeApplyDecisionCode;
|
|
16
|
+
readonly reason: string;
|
|
17
|
+
readonly humanApprovalRef: string | null;
|
|
18
|
+
readonly draftDigest: string;
|
|
19
|
+
}
|
|
20
|
+
export interface EvaluateDecomposeStructuralApplyInput {
|
|
21
|
+
readonly projectRoot: string;
|
|
22
|
+
/** Absolute or project-relative parent artifact path. */
|
|
23
|
+
readonly parentPath: string;
|
|
24
|
+
/** Absolute or project-relative draft path whose bytes were digested. */
|
|
25
|
+
readonly draftPath: string;
|
|
26
|
+
/** SHA-256 hex of the exact draft file bytes about to be applied. */
|
|
27
|
+
readonly draftDigest: string;
|
|
28
|
+
readonly grants?: readonly HumanOriginGrant[];
|
|
29
|
+
/** Optional GitHub-style repo identity (owner/name); compared when grant pins repo. */
|
|
30
|
+
readonly repo?: string | null;
|
|
31
|
+
readonly now?: Date;
|
|
32
|
+
}
|
|
33
|
+
/** SHA-256 hex of a buffer or utf8 string. */
|
|
34
|
+
export declare function sha256Hex(data: Buffer | string): string;
|
|
35
|
+
/** SHA-256 hex of file bytes at path (exact on-disk digest). */
|
|
36
|
+
export declare function sha256FileHex(path: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Normalize a path to project-relative POSIX form for grant binding compare.
|
|
39
|
+
* Returns null when the path escapes the project root.
|
|
40
|
+
*/
|
|
41
|
+
export declare function toProjectRelativePosix(projectRoot: string, path: string): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Evaluate whether a human-origin grant covers structural decompose apply for
|
|
44
|
+
* the exact draft digest + parent + target + project identity.
|
|
45
|
+
*/
|
|
46
|
+
export declare function evaluateDecomposeStructuralApply(input: EvaluateDecomposeStructuralApplyInput): DecomposeApplyDecision;
|
|
47
|
+
export interface MintDecomposeStructuralApplyGrantInput {
|
|
48
|
+
readonly projectRoot: string;
|
|
49
|
+
readonly parentPath: string;
|
|
50
|
+
readonly draftPath: string;
|
|
51
|
+
/** When omitted, digest is computed from draftPath on-disk bytes. */
|
|
52
|
+
readonly contentDigest?: string;
|
|
53
|
+
readonly actor?: string;
|
|
54
|
+
readonly repo?: string | null;
|
|
55
|
+
readonly expiresAt?: string | null;
|
|
56
|
+
readonly singleUse?: boolean;
|
|
57
|
+
readonly grantId?: string;
|
|
58
|
+
readonly now?: Date;
|
|
59
|
+
readonly pinActive?: boolean;
|
|
60
|
+
readonly eventRef?: string | null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Operator mint helper: human-origin grant bound to exact draft bytes + paths.
|
|
64
|
+
* Sole mint path remains mintHumanOriginGrant (operator-cli origin).
|
|
65
|
+
*/
|
|
66
|
+
export declare function mintDecomposeStructuralApplyGrant(input: MintDecomposeStructuralApplyGrantInput): HumanOriginGrant;
|
|
67
|
+
//# sourceMappingURL=decompose-apply.d.ts.map
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-origin structural apply gate for scope:decompose (#3239 / epic #3237 Q2).
|
|
3
|
+
*
|
|
4
|
+
* Apply requires a human-origin grant bound to:
|
|
5
|
+
* project root identity (worktree and/or repo), parent path, draft/target path,
|
|
6
|
+
* SHA-256 of exact draft bytes, and op scope.decompose.apply.structural.
|
|
7
|
+
*
|
|
8
|
+
* --check stays ungated (caller responsibility). Missing / expired / mismatched /
|
|
9
|
+
* agent-origin grants fail closed before any child story writes.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
14
|
+
import { mintHumanOriginGrant } from "./actions.js";
|
|
15
|
+
import { evidenceSatisfiesImplementationApproval, isHumanOriginGrant, isRejectedOriginKind, } from "./origin.js";
|
|
16
|
+
import { listGrants } from "./store.js";
|
|
17
|
+
import { SCOPE_DECOMPOSE_APPLY_STRUCTURAL, } from "./types.js";
|
|
18
|
+
/** SHA-256 hex of a buffer or utf8 string. */
|
|
19
|
+
export function sha256Hex(data) {
|
|
20
|
+
return createHash("sha256").update(data).digest("hex");
|
|
21
|
+
}
|
|
22
|
+
/** SHA-256 hex of file bytes at path (exact on-disk digest). */
|
|
23
|
+
export function sha256FileHex(path) {
|
|
24
|
+
return sha256Hex(readFileSync(path));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Normalize a path to project-relative POSIX form for grant binding compare.
|
|
28
|
+
* Returns null when the path escapes the project root.
|
|
29
|
+
*/
|
|
30
|
+
export function toProjectRelativePosix(projectRoot, path) {
|
|
31
|
+
const root = resolve(projectRoot);
|
|
32
|
+
const abs = isAbsolute(path) ? resolve(path) : resolve(root, path);
|
|
33
|
+
const rel = relative(root, abs);
|
|
34
|
+
if (rel.startsWith("..") || rel === "..")
|
|
35
|
+
return null;
|
|
36
|
+
// Windows absolute on different drive yields absolute rel — treat as escape.
|
|
37
|
+
if (isAbsolute(rel))
|
|
38
|
+
return null;
|
|
39
|
+
return rel.split(sep).join("/");
|
|
40
|
+
}
|
|
41
|
+
function normalizeDigest(raw) {
|
|
42
|
+
if (raw === null || raw === undefined)
|
|
43
|
+
return null;
|
|
44
|
+
const t = raw.trim().toLowerCase();
|
|
45
|
+
if (t.length === 0)
|
|
46
|
+
return null;
|
|
47
|
+
// Accept optional sha256: prefix.
|
|
48
|
+
return t.startsWith("sha256:") ? t.slice("sha256:".length) : t;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Exact path equality for grant binding (POSIX separators only).
|
|
52
|
+
* Case-sensitive: case-variant paths are distinct on case-sensitive filesystems
|
|
53
|
+
* (#3239 Greptile P1). Windows operators mint with the same relative form
|
|
54
|
+
* produce via toProjectRelativePosix.
|
|
55
|
+
*/
|
|
56
|
+
function pathsEqual(a, b) {
|
|
57
|
+
return a.replace(/\\/g, "/") === b.replace(/\\/g, "/");
|
|
58
|
+
}
|
|
59
|
+
function grantValidity(grant, now) {
|
|
60
|
+
if (!isHumanOriginGrant(grant)) {
|
|
61
|
+
const kind = grant.origin.kind;
|
|
62
|
+
if (isRejectedOriginKind(kind)) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
code: "authz-grant-origin-reject",
|
|
66
|
+
reason: `Directive denied scope:decompose apply: grant ${grant.id} origin.kind=${kind} is ` +
|
|
67
|
+
"agent/self-authored and cannot approve a decomposition draft. " +
|
|
68
|
+
"Human action required: mint via `deft authz:grant` (operator-cli) bound to the draft digest.",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
code: "authz-grant-origin-reject",
|
|
74
|
+
reason: `Directive denied scope:decompose apply: grant ${grant.id} lacks human-origin provenance. ` +
|
|
75
|
+
"Human action required: `deft authz:grant` with operator-cli origin.",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (grant.semantics.revokedAt !== null) {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
code: "authz-grant-revoked",
|
|
82
|
+
reason: `Directive denied scope:decompose apply: grant ${grant.id} was revoked at ${grant.semantics.revokedAt}.`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (grant.semantics.singleUse && grant.semantics.usedAt !== null) {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
code: "authz-grant-single-use-spent",
|
|
89
|
+
reason: `Directive denied scope:decompose apply: single-use grant ${grant.id} already spent at ${grant.semantics.usedAt}.`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (grant.semantics.expiresAt !== null) {
|
|
93
|
+
const exp = Date.parse(grant.semantics.expiresAt);
|
|
94
|
+
if (!Number.isNaN(exp) && exp <= now.getTime()) {
|
|
95
|
+
return {
|
|
96
|
+
ok: false,
|
|
97
|
+
code: "authz-grant-expired",
|
|
98
|
+
reason: `Directive denied scope:decompose apply: grant ${grant.id} expired at ${grant.semantics.expiresAt}. ` +
|
|
99
|
+
"Human action required: re-mint a grant for the current draft digest.",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { ok: true };
|
|
104
|
+
}
|
|
105
|
+
function grantHasStructuralOp(grant) {
|
|
106
|
+
return grant.scope.operations.includes(SCOPE_DECOMPOSE_APPLY_STRUCTURAL);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Evaluate whether a human-origin grant covers structural decompose apply for
|
|
110
|
+
* the exact draft digest + parent + target + project identity.
|
|
111
|
+
*/
|
|
112
|
+
export function evaluateDecomposeStructuralApply(input) {
|
|
113
|
+
const now = input.now ?? new Date();
|
|
114
|
+
const digest = normalizeDigest(input.draftDigest) ?? "";
|
|
115
|
+
const parentRel = toProjectRelativePosix(input.projectRoot, input.parentPath);
|
|
116
|
+
const targetRel = toProjectRelativePosix(input.projectRoot, input.draftPath);
|
|
117
|
+
const projectAbs = resolve(input.projectRoot);
|
|
118
|
+
const grants = input.grants ?? listGrants(input.projectRoot);
|
|
119
|
+
if (parentRel === null || targetRel === null) {
|
|
120
|
+
return {
|
|
121
|
+
allowed: false,
|
|
122
|
+
code: "authz-grant-scope-deny",
|
|
123
|
+
reason: "Directive denied scope:decompose apply: parent or draft path is outside the project root.",
|
|
124
|
+
humanApprovalRef: null,
|
|
125
|
+
draftDigest: digest,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
let lastReject = null;
|
|
129
|
+
for (const grant of grants) {
|
|
130
|
+
if (!evidenceSatisfiesImplementationApproval({ grant })) {
|
|
131
|
+
const validity = grantValidity(grant, now);
|
|
132
|
+
if (!validity.ok) {
|
|
133
|
+
lastReject = { code: validity.code, reason: validity.reason, grantId: grant.id };
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
lastReject = {
|
|
137
|
+
code: "authz-grant-origin-reject",
|
|
138
|
+
reason: `Directive denied scope:decompose apply: grant ${grant.id} does not satisfy ` +
|
|
139
|
+
"human-origin implementation approval. Human action required: `deft authz:grant`.",
|
|
140
|
+
grantId: grant.id,
|
|
141
|
+
};
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const validity = grantValidity(grant, now);
|
|
145
|
+
if (!validity.ok) {
|
|
146
|
+
lastReject = { code: validity.code, reason: validity.reason, grantId: grant.id };
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (!grantHasStructuralOp(grant)) {
|
|
150
|
+
lastReject = {
|
|
151
|
+
code: "authz-grant-scope-deny",
|
|
152
|
+
reason: `Directive denied scope:decompose apply: grant ${grant.id} does not include operation ` +
|
|
153
|
+
`'${SCOPE_DECOMPOSE_APPLY_STRUCTURAL}'. Human action required: mint with that operation.`,
|
|
154
|
+
grantId: grant.id,
|
|
155
|
+
};
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const boundDigest = normalizeDigest(grant.scope.contentDigest ?? null);
|
|
159
|
+
const boundParent = grant.scope.parentPath?.trim() ?? "";
|
|
160
|
+
const boundTarget = grant.scope.targetPath?.trim() ?? "";
|
|
161
|
+
const boundRepo = grant.scope.repo?.trim() ?? "";
|
|
162
|
+
const boundWorktree = grant.scope.worktree?.trim() ?? "";
|
|
163
|
+
if (boundDigest === null || boundParent.length === 0 || boundTarget.length === 0) {
|
|
164
|
+
lastReject = {
|
|
165
|
+
code: "authz-grant-binding-incomplete",
|
|
166
|
+
reason: `Directive denied scope:decompose apply: grant ${grant.id} is missing required ` +
|
|
167
|
+
"structural bindings (contentDigest, parentPath, targetPath). " +
|
|
168
|
+
"Human action required: re-mint bound to the exact draft.",
|
|
169
|
+
grantId: grant.id,
|
|
170
|
+
};
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (boundRepo.length === 0 && boundWorktree.length === 0) {
|
|
174
|
+
lastReject = {
|
|
175
|
+
code: "authz-grant-binding-incomplete",
|
|
176
|
+
reason: `Directive denied scope:decompose apply: grant ${grant.id} does not bind project ` +
|
|
177
|
+
"identity (repo and/or worktree). Human action required: re-mint with --repo or worktree.",
|
|
178
|
+
grantId: grant.id,
|
|
179
|
+
};
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (boundDigest !== digest) {
|
|
183
|
+
lastReject = {
|
|
184
|
+
code: "authz-grant-digest-mismatch",
|
|
185
|
+
reason: `Directive denied scope:decompose apply: grant ${grant.id} is bound to draft digest ` +
|
|
186
|
+
`${boundDigest.slice(0, 12)}… but the draft bytes hash to ${digest.slice(0, 12)}…. ` +
|
|
187
|
+
"Any content change after approval invalidates the grant. Re-mint for the current draft.",
|
|
188
|
+
grantId: grant.id,
|
|
189
|
+
};
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (!pathsEqual(boundParent, parentRel)) {
|
|
193
|
+
lastReject = {
|
|
194
|
+
code: "authz-grant-parent-mismatch",
|
|
195
|
+
reason: `Directive denied scope:decompose apply: grant ${grant.id} is bound to parent ` +
|
|
196
|
+
`'${boundParent}' but apply targets '${parentRel}'.`,
|
|
197
|
+
grantId: grant.id,
|
|
198
|
+
};
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (!pathsEqual(boundTarget, targetRel)) {
|
|
202
|
+
lastReject = {
|
|
203
|
+
code: "authz-grant-target-mismatch",
|
|
204
|
+
reason: `Directive denied scope:decompose apply: grant ${grant.id} is bound to draft path ` +
|
|
205
|
+
`'${boundTarget}' but apply uses '${targetRel}'.`,
|
|
206
|
+
grantId: grant.id,
|
|
207
|
+
};
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
// Project identity: when grant pins repo and/or worktree, attempt must match.
|
|
211
|
+
if (boundRepo.length > 0) {
|
|
212
|
+
const attemptRepo = (input.repo ?? "").trim();
|
|
213
|
+
if (attemptRepo.length === 0 || attemptRepo.toLowerCase() !== boundRepo.toLowerCase()) {
|
|
214
|
+
lastReject = {
|
|
215
|
+
code: "authz-grant-project-mismatch",
|
|
216
|
+
reason: `Directive denied scope:decompose apply: grant ${grant.id} is bound to repo ` +
|
|
217
|
+
`'${boundRepo}' which does not match the apply context.`,
|
|
218
|
+
grantId: grant.id,
|
|
219
|
+
};
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (boundWorktree.length > 0) {
|
|
224
|
+
const grantWt = resolve(boundWorktree);
|
|
225
|
+
// Worktree compare is path-normalize only (resolve); keep exact string match of
|
|
226
|
+
// resolved absolute paths without case-folding (same rule as parent/target paths).
|
|
227
|
+
if (grantWt !== projectAbs) {
|
|
228
|
+
lastReject = {
|
|
229
|
+
code: "authz-grant-project-mismatch",
|
|
230
|
+
reason: `Directive denied scope:decompose apply: grant ${grant.id} is bound to worktree ` +
|
|
231
|
+
`'${boundWorktree}' which does not match project root '${projectAbs}'.`,
|
|
232
|
+
grantId: grant.id,
|
|
233
|
+
};
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
allowed: true,
|
|
239
|
+
code: "authz-allow",
|
|
240
|
+
reason: `Directive allowed scope:decompose apply via human-origin grant ${grant.id} ` +
|
|
241
|
+
`bound to draft digest ${digest.slice(0, 12)}….`,
|
|
242
|
+
humanApprovalRef: grant.id,
|
|
243
|
+
draftDigest: digest,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
if (lastReject !== null) {
|
|
247
|
+
return {
|
|
248
|
+
allowed: false,
|
|
249
|
+
code: lastReject.code,
|
|
250
|
+
reason: lastReject.reason,
|
|
251
|
+
humanApprovalRef: lastReject.grantId,
|
|
252
|
+
draftDigest: digest,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
allowed: false,
|
|
257
|
+
code: "authz-grant-missing",
|
|
258
|
+
reason: "Directive denied scope:decompose apply: no human-origin grant covers " +
|
|
259
|
+
`'${SCOPE_DECOMPOSE_APPLY_STRUCTURAL}' for this draft digest. ` +
|
|
260
|
+
"Human action required: mint a grant bound to the exact draft SHA-256 " +
|
|
261
|
+
"(self-authored lifecycle/dispatch tokens do not count). --check remains ungated.",
|
|
262
|
+
humanApprovalRef: null,
|
|
263
|
+
draftDigest: digest,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Operator mint helper: human-origin grant bound to exact draft bytes + paths.
|
|
268
|
+
* Sole mint path remains mintHumanOriginGrant (operator-cli origin).
|
|
269
|
+
*/
|
|
270
|
+
export function mintDecomposeStructuralApplyGrant(input) {
|
|
271
|
+
const parentRel = toProjectRelativePosix(input.projectRoot, input.parentPath);
|
|
272
|
+
const targetRel = toProjectRelativePosix(input.projectRoot, input.draftPath);
|
|
273
|
+
if (parentRel === null || targetRel === null) {
|
|
274
|
+
throw new Error("mintDecomposeStructuralApplyGrant: parentPath and draftPath must resolve inside projectRoot");
|
|
275
|
+
}
|
|
276
|
+
const digest = input.contentDigest !== undefined
|
|
277
|
+
? (normalizeDigest(input.contentDigest) ?? "")
|
|
278
|
+
: sha256FileHex(isAbsolute(input.draftPath)
|
|
279
|
+
? input.draftPath
|
|
280
|
+
: resolve(input.projectRoot, input.draftPath));
|
|
281
|
+
if (digest.length === 0) {
|
|
282
|
+
throw new Error("mintDecomposeStructuralApplyGrant: contentDigest must be non-empty");
|
|
283
|
+
}
|
|
284
|
+
const mintInput = {
|
|
285
|
+
projectRoot: input.projectRoot,
|
|
286
|
+
actor: input.actor,
|
|
287
|
+
operations: [SCOPE_DECOMPOSE_APPLY_STRUCTURAL],
|
|
288
|
+
contentDigest: digest,
|
|
289
|
+
parentPath: parentRel,
|
|
290
|
+
targetPath: targetRel,
|
|
291
|
+
worktree: resolve(input.projectRoot),
|
|
292
|
+
repo: input.repo ?? null,
|
|
293
|
+
expiresAt: input.expiresAt ?? null,
|
|
294
|
+
singleUse: input.singleUse === true,
|
|
295
|
+
grantId: input.grantId,
|
|
296
|
+
now: input.now,
|
|
297
|
+
pinActive: input.pinActive,
|
|
298
|
+
eventRef: input.eventRef ?? null,
|
|
299
|
+
};
|
|
300
|
+
return mintHumanOriginGrant(mintInput);
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=decompose-apply.js.map
|
package/dist/authz/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Human-origin approval grants + UAT mutation lease (#2944 Wave 1)
|
|
3
|
-
* + closed-verb release gates / AFK templates (#1095 Wave 4)
|
|
3
|
+
* + closed-verb release gates / AFK templates (#1095 Wave 4)
|
|
4
|
+
* + structural scope:decompose apply digest gate (#3239).
|
|
4
5
|
*/
|
|
5
6
|
export * from "./actions.js";
|
|
6
7
|
export * from "./classify.js";
|
|
7
8
|
export * from "./closed-verb.js";
|
|
9
|
+
export * from "./decompose-apply.js";
|
|
8
10
|
export * from "./evaluate.js";
|
|
9
11
|
export * from "./origin.js";
|
|
10
12
|
export * from "./paths.js";
|
package/dist/authz/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Human-origin approval grants + UAT mutation lease (#2944 Wave 1)
|
|
3
|
-
* + closed-verb release gates / AFK templates (#1095 Wave 4)
|
|
3
|
+
* + closed-verb release gates / AFK templates (#1095 Wave 4)
|
|
4
|
+
* + structural scope:decompose apply digest gate (#3239).
|
|
4
5
|
*/
|
|
5
6
|
export * from "./actions.js";
|
|
6
7
|
export * from "./classify.js";
|
|
7
8
|
export * from "./closed-verb.js";
|
|
9
|
+
export * from "./decompose-apply.js";
|
|
8
10
|
export * from "./evaluate.js";
|
|
9
11
|
export * from "./origin.js";
|
|
10
12
|
export * from "./paths.js";
|
package/dist/authz/store.d.ts
CHANGED
|
@@ -25,6 +25,59 @@ export declare function loadAuthzStateResult(projectRoot: string): AuthzStateLoa
|
|
|
25
25
|
export declare function loadAuthzState(projectRoot: string): AuthzState;
|
|
26
26
|
/** Persist single-use grant consumption after an allow decision (#2944). */
|
|
27
27
|
export declare function markGrantUsed(projectRoot: string, grantId: string, now?: Date): HumanOriginGrant | null;
|
|
28
|
+
/** Exclusive claim lock body under `.deft/authz/locks/<id>.lock` (#3239). */
|
|
29
|
+
export interface GrantClaimLockRecord {
|
|
30
|
+
readonly pid: number;
|
|
31
|
+
readonly startedAt: string;
|
|
32
|
+
readonly token: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Whether a claim lock may be reclaimed after a crashed holder (#3239).
|
|
36
|
+
* Live owner PIDs are never reclaimed (no mtime-only steal of a live critical section).
|
|
37
|
+
* Corrupt / unreadable records are reclaimable; PID-reuse residual needs manual delete.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isGrantClaimLockReclaimable(rec: GrantClaimLockRecord | null): boolean;
|
|
40
|
+
export interface ClaimSingleUseGrantOptions {
|
|
41
|
+
readonly now?: Date;
|
|
42
|
+
/**
|
|
43
|
+
* Re-check grant after exclusive lock (revocation/expiry/origin/bindings).
|
|
44
|
+
* Called before protected apply / single-use mark so an invalidated grant cannot authorize writes.
|
|
45
|
+
*/
|
|
46
|
+
readonly revalidate?: (grant: HumanOriginGrant) => {
|
|
47
|
+
ok: true;
|
|
48
|
+
} | {
|
|
49
|
+
ok: false;
|
|
50
|
+
reason: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Protected work under the exclusive claim (#3239 residual).
|
|
54
|
+
* Order: lock → revalidate → mark single-use usedAt → apply → release lock.
|
|
55
|
+
* If apply throws, usedAt is rolled back (grant reusable) and the lock is released.
|
|
56
|
+
* Concurrent claimants fail closed while the lock is held.
|
|
57
|
+
*/
|
|
58
|
+
readonly apply?: (grant: HumanOriginGrant) => void;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Claim a single-use grant for structural apply (#3239).
|
|
62
|
+
*
|
|
63
|
+
* Concurrent-safe + failure-safe order when `apply` is provided:
|
|
64
|
+
* exclusive lock → re-load → revalidate → mark single-use usedAt → apply (writes)
|
|
65
|
+
* → on apply throw: rollback usedAt + release lock (grant reusable for retry).
|
|
66
|
+
* Multi-use grants run apply (if any) without mutating usedAt.
|
|
67
|
+
* Without `apply`, single-use is marked under the lock (claim-only / test path).
|
|
68
|
+
*
|
|
69
|
+
* Crash after mark + before rollback: grant stays spent (no double-apply); operator remints.
|
|
70
|
+
* Dead-PID / corrupt locks reclaim via **rename-away** of the old lock path (only one
|
|
71
|
+
* renamer wins) then exclusive create — never blind rmSync of a live winner's lock.
|
|
72
|
+
* Live PIDs are never reclaimed. PID-reuse residual: operator deletes the lock file.
|
|
73
|
+
*/
|
|
74
|
+
export declare function claimSingleUseGrantForApply(projectRoot: string, grantId: string, options?: ClaimSingleUseGrantOptions | Date): {
|
|
75
|
+
ok: true;
|
|
76
|
+
grant: HumanOriginGrant;
|
|
77
|
+
} | {
|
|
78
|
+
ok: false;
|
|
79
|
+
reason: string;
|
|
80
|
+
};
|
|
28
81
|
export declare function saveAuthzState(projectRoot: string, state: AuthzState): void;
|
|
29
82
|
export declare function loadGrant(projectRoot: string, grantId: string): HumanOriginGrant | null;
|
|
30
83
|
export declare function saveGrant(projectRoot: string, grant: HumanOriginGrant): void;
|