@deftai/directive-core 0.87.0 → 0.89.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authz/actions.d.ts +55 -0
- package/dist/authz/actions.js +125 -0
- package/dist/authz/classify.d.ts +23 -0
- package/dist/authz/classify.js +217 -0
- package/dist/authz/closed-verb.d.ts +42 -0
- package/dist/authz/closed-verb.js +279 -0
- package/dist/authz/evaluate.d.ts +41 -0
- package/dist/authz/evaluate.js +298 -0
- package/dist/authz/index.d.ts +15 -0
- package/dist/authz/index.js +15 -0
- package/dist/authz/origin.d.ts +28 -0
- package/dist/authz/origin.js +64 -0
- package/dist/authz/paths.d.ts +6 -0
- package/dist/authz/paths.js +19 -0
- package/dist/authz/store.d.ts +40 -0
- package/dist/authz/store.js +317 -0
- package/dist/authz/templates.d.ts +139 -0
- package/dist/authz/templates.js +241 -0
- package/dist/authz/types.d.ts +111 -0
- package/dist/authz/types.js +41 -0
- package/dist/authz/verb-classification.d.ts +44 -0
- package/dist/authz/verb-classification.js +237 -0
- package/dist/branch/evaluate.js +6 -1
- package/dist/cache/fetch.js +10 -5
- package/dist/cache/io.d.ts +9 -2
- package/dist/cache/io.js +30 -10
- package/dist/cache/scanner.d.ts +11 -1
- package/dist/cache/scanner.js +29 -4
- package/dist/cache/task-cache/store.js +11 -7
- package/dist/capacity/backfill.js +10 -4
- package/dist/category-b-namespace/index.js +10 -4
- package/dist/check/gate-lists.js +1 -0
- package/dist/codebase/default-extractor.js +3 -0
- package/dist/codebase/map.js +11 -4
- package/dist/content-contracts/skills/helpers.d.ts +10 -0
- package/dist/content-contracts/skills/helpers.js +35 -0
- package/dist/deposit/copy-tree.d.ts +19 -1
- package/dist/deposit/copy-tree.js +134 -5
- package/dist/doctor/doctor-state.js +28 -4
- package/dist/doctor/main.d.ts +10 -0
- package/dist/doctor/main.js +208 -0
- package/dist/doctor/types.d.ts +11 -0
- package/dist/escalation/actions.d.ts +63 -0
- package/dist/escalation/actions.js +137 -0
- package/dist/escalation/index.d.ts +8 -0
- package/dist/escalation/index.js +8 -0
- package/dist/escalation/paths.d.ts +3 -0
- package/dist/escalation/paths.js +10 -0
- package/dist/escalation/store.d.ts +17 -0
- package/dist/escalation/store.js +172 -0
- package/dist/escalation/types.d.ts +73 -0
- package/dist/escalation/types.js +43 -0
- package/dist/eval/crud-telemetry.js +30 -10
- package/dist/eval/health.js +22 -7
- package/dist/eval/readback.js +11 -10
- package/dist/eval/run.js +32 -16
- package/dist/events/attribution-enrichment.js +10 -4
- package/dist/finish-loop/directive-finish-loop.d.ts +37 -0
- package/dist/finish-loop/directive-finish-loop.js +239 -0
- package/dist/finish-loop/grant-gate.d.ts +31 -0
- package/dist/finish-loop/grant-gate.js +169 -0
- package/dist/finish-loop/index.d.ts +11 -0
- package/dist/finish-loop/index.js +11 -0
- package/dist/finish-loop/main.d.ts +35 -0
- package/dist/finish-loop/main.js +365 -0
- package/dist/finish-loop/pr-finish-loop.d.ts +34 -0
- package/dist/finish-loop/pr-finish-loop.js +217 -0
- package/dist/finish-loop/progress.d.ts +17 -0
- package/dist/finish-loop/progress.js +45 -0
- package/dist/finish-loop/queue.d.ts +16 -0
- package/dist/finish-loop/queue.js +65 -0
- package/dist/finish-loop/types.d.ts +52 -0
- package/dist/finish-loop/types.js +10 -0
- package/dist/fs/contained-write.d.ts +101 -0
- package/dist/fs/contained-write.js +281 -0
- package/dist/fs/projection-containment.d.ts +18 -0
- package/dist/fs/projection-containment.js +40 -0
- package/dist/hooks/classify/classify.d.ts +10 -0
- package/dist/hooks/classify/classify.js +37 -0
- package/dist/hooks/classify/index.d.ts +14 -0
- package/dist/hooks/classify/index.js +13 -0
- package/dist/hooks/classify/paths.d.ts +22 -0
- package/dist/hooks/classify/paths.js +72 -0
- package/dist/hooks/classify/payload.d.ts +16 -0
- package/dist/hooks/classify/payload.js +45 -0
- package/dist/hooks/classify/stdin.d.ts +12 -0
- package/dist/hooks/classify/stdin.js +63 -0
- package/dist/hooks/classify/tool-name.d.ts +18 -0
- package/dist/hooks/classify/tool-name.js +85 -0
- package/dist/hooks/classify/types.d.ts +41 -0
- package/dist/hooks/classify/types.js +7 -0
- package/dist/hooks/dispatcher.d.ts +30 -15
- package/dist/hooks/dispatcher.js +326 -134
- package/dist/hooks/fixtures/cases.d.ts +44 -0
- package/dist/hooks/fixtures/cases.js +595 -0
- package/dist/hooks/fixtures/index.d.ts +2 -0
- package/dist/hooks/fixtures/index.js +2 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/tools.d.ts +31 -0
- package/dist/hooks/tools.js +74 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +65 -10
- package/dist/init-deposit/gitignore.js +11 -3
- package/dist/init-deposit/headless-manifest.js +12 -4
- package/dist/init-deposit/hygiene.d.ts +16 -0
- package/dist/init-deposit/hygiene.js +26 -0
- package/dist/init-deposit/init-dispatch.js +28 -0
- package/dist/init-deposit/migrate.d.ts +1 -1
- package/dist/init-deposit/migrate.js +13 -3
- package/dist/init-deposit/prettierignore.js +12 -5
- package/dist/init-deposit/refresh.js +38 -7
- package/dist/init-deposit/scaffold.js +42 -29
- package/dist/init-deposit/xbrief-projections.js +17 -10
- package/dist/intake/candidates-log.js +46 -35
- package/dist/intake/github-body-cli.d.ts +6 -0
- package/dist/intake/github-body-cli.js +17 -0
- package/dist/intake/github-body.d.ts +46 -3
- package/dist/intake/github-body.js +191 -20
- package/dist/intake/issue-emit.d.ts +1 -0
- package/dist/intake/issue-emit.js +41 -23
- package/dist/intake/issue-ingest.js +11 -2
- package/dist/lifecycle/event-detect.js +12 -4
- package/dist/lifecycle/events.js +29 -20
- package/dist/lifecycle/lifecycle-hygiene.js +9 -3
- package/dist/packs/pack-render.d.ts +33 -0
- package/dist/packs/pack-render.js +172 -13
- package/dist/packs/quarantine-ext.d.ts +10 -0
- package/dist/packs/quarantine-ext.js +26 -2
- package/dist/plan-sequence/store.js +11 -4
- package/dist/policy/hotfix-criteria.d.ts +79 -0
- package/dist/policy/hotfix-criteria.js +197 -0
- package/dist/policy/index.d.ts +5 -0
- package/dist/policy/index.js +32 -1
- package/dist/policy/intent-ceiling.d.ts +79 -0
- package/dist/policy/intent-ceiling.js +181 -0
- package/dist/policy/no-deft-directive.d.ts +59 -0
- package/dist/policy/no-deft-directive.js +110 -0
- package/dist/policy/org-force-on-migration.d.ts +52 -0
- package/dist/policy/org-force-on-migration.js +269 -27
- package/dist/policy/require-human-merge.d.ts +75 -0
- package/dist/policy/require-human-merge.js +324 -0
- package/dist/policy/resolve.js +17 -6
- package/dist/policy/runtime-authority.d.ts +56 -2
- package/dist/policy/runtime-authority.js +297 -3
- package/dist/policy/write-fence.d.ts +78 -0
- package/dist/policy/write-fence.js +164 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +10 -0
- package/dist/pr-wait-mergeable/cascade.js +28 -0
- package/dist/preflight/evaluate.js +10 -0
- package/dist/product-signal/consent.js +21 -5
- package/dist/product-signal/submit.js +22 -12
- package/dist/release/build-dist-runner.js +5 -2
- package/dist/release/build-dist.d.ts +19 -1
- package/dist/release/build-dist.js +50 -2
- package/dist/release/native-steps.js +7 -2
- package/dist/release-publish/pipeline.d.ts +13 -0
- package/dist/release-publish/pipeline.js +46 -1
- package/dist/scope/audit-log.js +19 -24
- package/dist/scope/decompose.js +10 -5
- package/dist/scope/decomposed-refs.js +18 -3
- package/dist/scope/demote.js +9 -2
- package/dist/scope/undo.js +9 -2
- package/dist/session/release-availability.js +26 -6
- package/dist/session/ritual-sentinel.js +19 -12
- package/dist/session/session-start-hook.d.ts +3 -0
- package/dist/session/session-start-hook.js +15 -0
- package/dist/session/session-start.js +37 -0
- package/dist/staleness-tickler/state.js +26 -6
- package/dist/swarm/launch.js +13 -3
- package/dist/swarm/routing.js +9 -3
- package/dist/task-surface/index.js +24 -9
- package/dist/triage/bootstrap/gitignore.js +53 -10
- package/dist/triage/cache-path.js +10 -3
- package/dist/triage/welcome/summary.js +11 -4
- package/dist/triage/welcome/writers.js +45 -16
- package/dist/validate-content/validate-links.js +5 -0
- package/dist/value/readback.js +11 -6
- package/dist/vbrief-activate/activate.js +12 -4
- package/dist/vbrief-build/project-definition-io.js +14 -6
- package/dist/verify-source/contained-writes.d.ts +59 -0
- package/dist/verify-source/contained-writes.js +272 -0
- package/dist/verify-source/cursor-tier1.js +7 -2
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/openclaw-tier1.js +19 -1
- package/dist/verify-source/verify-stubs.js +3 -0
- package/dist/xbrief-migrate/migrate-project.js +26 -12
- package/dist/xbrief-migrate/transforms.d.ts +4 -2
- package/dist/xbrief-migrate/transforms.js +37 -14
- package/package.json +16 -3
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AFK grant templates (#1095 Wave 4 + #871 Wave 5).
|
|
3
|
+
*
|
|
4
|
+
* Templates are **presets** on the Wave 1 mint path only:
|
|
5
|
+
* they call mintHumanOriginGrant / operator-cli origin.
|
|
6
|
+
*
|
|
7
|
+
* ⊗ Do not invent a second authorization SoT under ~/.deft/session-auth
|
|
8
|
+
* that agents can self-mint as alternate permission.
|
|
9
|
+
*/
|
|
10
|
+
import { mintHumanOriginGrant } from "./actions.js";
|
|
11
|
+
import { normaliseClosedVerbTarget, targetSurfaceCandidates } from "./closed-verb.js";
|
|
12
|
+
import { builtinReleaseVerbClassification, getVerbRow, } from "./verb-classification.js";
|
|
13
|
+
/** Canonical AFK template names for Wave 4 release-class closed verbs. */
|
|
14
|
+
export const CLOSED_VERB_TEMPLATE_NAMES = [
|
|
15
|
+
"release-cut",
|
|
16
|
+
"release-publish",
|
|
17
|
+
"release-rollback",
|
|
18
|
+
];
|
|
19
|
+
/** Walk-away finish-loop template (#871 Wave 5) — not a release closed-verb. */
|
|
20
|
+
export const FINISH_LOOP_TEMPLATE_NAME = "finish-loop";
|
|
21
|
+
/** All AFK templates accepted by `authz:grant --template`. */
|
|
22
|
+
export const AFK_TEMPLATE_NAMES = [
|
|
23
|
+
...CLOSED_VERB_TEMPLATE_NAMES,
|
|
24
|
+
FINISH_LOOP_TEMPLATE_NAME,
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* AuthzOperation set covered by the finish-loop template.
|
|
28
|
+
* Release-class ops are explicitly carved out (operator must mint a release-* grant).
|
|
29
|
+
*/
|
|
30
|
+
export const FINISH_LOOP_OPERATIONS = [
|
|
31
|
+
"edit",
|
|
32
|
+
"push",
|
|
33
|
+
"pr",
|
|
34
|
+
"merge",
|
|
35
|
+
];
|
|
36
|
+
/** Default AFK window for finish-loop (matches issue #871 walk-away notes). */
|
|
37
|
+
export const FINISH_LOOP_DEFAULT_EXPIRY_HOURS = 8;
|
|
38
|
+
export function isClosedVerbTemplateName(name) {
|
|
39
|
+
return CLOSED_VERB_TEMPLATE_NAMES.includes(name.trim().toLowerCase());
|
|
40
|
+
}
|
|
41
|
+
export function isFinishLoopTemplateName(name) {
|
|
42
|
+
return name.trim().toLowerCase() === FINISH_LOOP_TEMPLATE_NAME;
|
|
43
|
+
}
|
|
44
|
+
export function isAfkTemplateName(name) {
|
|
45
|
+
return AFK_TEMPLATE_NAMES.includes(name.trim().toLowerCase());
|
|
46
|
+
}
|
|
47
|
+
function parseExpiryHours(defaultExpiry) {
|
|
48
|
+
const m = /^(\d+)\s*h$/i.exec(defaultExpiry.trim());
|
|
49
|
+
if (m !== null)
|
|
50
|
+
return Number(m[1]);
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
function isoExpiry(now, hours) {
|
|
54
|
+
return new Date(now.getTime() + hours * 3600 * 1000).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve template → mint input (operations + surfaces). Pure; no I/O.
|
|
58
|
+
* Always routes operations through Wave 1 AuthzOperation names.
|
|
59
|
+
*/
|
|
60
|
+
export function resolveClosedVerbTemplate(input) {
|
|
61
|
+
const name = input.template.trim().toLowerCase();
|
|
62
|
+
if (!isClosedVerbTemplateName(name)) {
|
|
63
|
+
throw new Error(`unknown closed-verb template '${input.template}'; expected one of: ${CLOSED_VERB_TEMPLATE_NAMES.join(", ")}` +
|
|
64
|
+
(isFinishLoopTemplateName(input.template)
|
|
65
|
+
? ` (use resolveFinishLoopTemplate / mintFinishLoopTemplateGrant for finish-loop)`
|
|
66
|
+
: ""));
|
|
67
|
+
}
|
|
68
|
+
const target = input.target.trim();
|
|
69
|
+
if (target.length === 0) {
|
|
70
|
+
throw new Error(`template ${name} requires a non-empty --target`);
|
|
71
|
+
}
|
|
72
|
+
const table = input.classification ?? builtinReleaseVerbClassification();
|
|
73
|
+
const row = getVerbRow(table, name);
|
|
74
|
+
if (row === null) {
|
|
75
|
+
throw new Error(`verb classification missing row for template ${name}`);
|
|
76
|
+
}
|
|
77
|
+
// Prefer the precise closed-verb operation (least privilege); deployment is
|
|
78
|
+
// accepted at evaluate time as a broader alternative but not minted by default.
|
|
79
|
+
const op = name;
|
|
80
|
+
const surfaces = targetSurfaceCandidates(target);
|
|
81
|
+
// Prefer canonical version form without forcing only-v prefix ambiguity.
|
|
82
|
+
const norm = normaliseClosedVerbTarget(target);
|
|
83
|
+
const surfaceList = surfaces.length > 0 ? surfaces : norm !== null ? [norm, `v${norm}`] : [target];
|
|
84
|
+
let expiresAt = input.expiresAt ?? null;
|
|
85
|
+
if (expiresAt === null || expiresAt === undefined) {
|
|
86
|
+
const hours = parseExpiryHours(row.default_expiry);
|
|
87
|
+
const now = input.now ?? new Date();
|
|
88
|
+
expiresAt = isoExpiry(now, hours);
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
operations: [op],
|
|
92
|
+
surfaces: surfaceList,
|
|
93
|
+
expiresAt,
|
|
94
|
+
template: name,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Resolve finish-loop AFK template → mint ops (edit/push/pr/merge). Pure; no I/O.
|
|
99
|
+
* Does not mint release-class operations.
|
|
100
|
+
*/
|
|
101
|
+
export function resolveFinishLoopTemplate(input) {
|
|
102
|
+
let expiresAt = input.expiresAt ?? null;
|
|
103
|
+
if (expiresAt === null || expiresAt === undefined) {
|
|
104
|
+
const hours = input.durationHours !== undefined && Number.isFinite(input.durationHours)
|
|
105
|
+
? Math.max(1, Math.floor(input.durationHours))
|
|
106
|
+
: FINISH_LOOP_DEFAULT_EXPIRY_HOURS;
|
|
107
|
+
expiresAt = isoExpiry(input.now ?? new Date(), hours);
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
operations: [...FINISH_LOOP_OPERATIONS],
|
|
111
|
+
surfaces: input.surfaces !== undefined ? [...input.surfaces] : [],
|
|
112
|
+
expiresAt,
|
|
113
|
+
template: FINISH_LOOP_TEMPLATE_NAME,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Mint a human-origin grant for an AFK closed-verb template.
|
|
118
|
+
* **Sole mint path:** mintHumanOriginGrant (operator-cli). No session-auth SoT.
|
|
119
|
+
*/
|
|
120
|
+
export function mintClosedVerbTemplateGrant(input) {
|
|
121
|
+
// Production dual-mint guard: templates never open an independent session-auth mint.
|
|
122
|
+
const dualMint = assertNoIndependentSessionAuthMint();
|
|
123
|
+
if (dualMint.sessionAuthIsAuthority || dualMint.mintPath !== "mintHumanOriginGrant") {
|
|
124
|
+
throw new Error("closed-verb template mint refused: dual authorization SoT is forbidden (#1095)");
|
|
125
|
+
}
|
|
126
|
+
const resolved = resolveClosedVerbTemplate({
|
|
127
|
+
template: input.template,
|
|
128
|
+
target: input.target,
|
|
129
|
+
now: input.now,
|
|
130
|
+
expiresAt: input.expiresAt,
|
|
131
|
+
classification: input.classification,
|
|
132
|
+
});
|
|
133
|
+
const mintInput = {
|
|
134
|
+
projectRoot: input.projectRoot,
|
|
135
|
+
actor: input.actor ?? "operator",
|
|
136
|
+
operations: resolved.operations,
|
|
137
|
+
surfaces: resolved.surfaces,
|
|
138
|
+
expiresAt: resolved.expiresAt,
|
|
139
|
+
singleUse: input.singleUse === true,
|
|
140
|
+
planRef: input.planRef ?? null,
|
|
141
|
+
repo: input.repo ?? null,
|
|
142
|
+
branch: input.branch ?? null,
|
|
143
|
+
pinActive: input.pinActive,
|
|
144
|
+
eventRef: input.eventRef ?? `template:${resolved.template}`,
|
|
145
|
+
now: input.now,
|
|
146
|
+
};
|
|
147
|
+
return mintHumanOriginGrant(mintInput);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Mint a human-origin grant for the walk-away finish-loop template (#871).
|
|
151
|
+
* Covers edit/push/pr/merge only — never release-cut / release-publish / release-rollback.
|
|
152
|
+
* **Sole mint path:** mintHumanOriginGrant (operator-cli).
|
|
153
|
+
*/
|
|
154
|
+
export function mintFinishLoopTemplateGrant(input) {
|
|
155
|
+
const dualMint = assertNoIndependentSessionAuthMint();
|
|
156
|
+
if (dualMint.sessionAuthIsAuthority || dualMint.mintPath !== "mintHumanOriginGrant") {
|
|
157
|
+
throw new Error("finish-loop template mint refused: dual authorization SoT is forbidden (#871 / #1095)");
|
|
158
|
+
}
|
|
159
|
+
const resolved = resolveFinishLoopTemplate({
|
|
160
|
+
now: input.now,
|
|
161
|
+
expiresAt: input.expiresAt,
|
|
162
|
+
durationHours: input.durationHours,
|
|
163
|
+
surfaces: input.surfaces,
|
|
164
|
+
});
|
|
165
|
+
const mintInput = {
|
|
166
|
+
projectRoot: input.projectRoot,
|
|
167
|
+
actor: input.actor ?? "operator",
|
|
168
|
+
operations: resolved.operations,
|
|
169
|
+
surfaces: resolved.surfaces,
|
|
170
|
+
expiresAt: resolved.expiresAt,
|
|
171
|
+
singleUse: input.singleUse === true,
|
|
172
|
+
planRef: input.planRef ?? null,
|
|
173
|
+
repo: input.repo ?? null,
|
|
174
|
+
branch: input.branch ?? null,
|
|
175
|
+
storyIds: input.storyIds,
|
|
176
|
+
issueIds: input.issueIds,
|
|
177
|
+
cohortId: input.cohortId ?? null,
|
|
178
|
+
pinActive: input.pinActive,
|
|
179
|
+
eventRef: input.eventRef ?? `template:${FINISH_LOOP_TEMPLATE_NAME}`,
|
|
180
|
+
now: input.now,
|
|
181
|
+
};
|
|
182
|
+
return mintHumanOriginGrant(mintInput);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Dispatch AFK template mint by name. Closed-verb templates require target;
|
|
186
|
+
* finish-loop does not.
|
|
187
|
+
*/
|
|
188
|
+
export function mintAfkTemplateGrant(input) {
|
|
189
|
+
const name = input.template.trim().toLowerCase();
|
|
190
|
+
if (isFinishLoopTemplateName(name)) {
|
|
191
|
+
return mintFinishLoopTemplateGrant({
|
|
192
|
+
projectRoot: input.projectRoot,
|
|
193
|
+
actor: input.actor,
|
|
194
|
+
expiresAt: input.expiresAt,
|
|
195
|
+
singleUse: input.singleUse,
|
|
196
|
+
planRef: input.planRef,
|
|
197
|
+
repo: input.repo,
|
|
198
|
+
branch: input.branch,
|
|
199
|
+
surfaces: input.surfaces,
|
|
200
|
+
storyIds: input.storyIds,
|
|
201
|
+
issueIds: input.issueIds,
|
|
202
|
+
cohortId: input.cohortId,
|
|
203
|
+
now: input.now,
|
|
204
|
+
pinActive: input.pinActive,
|
|
205
|
+
eventRef: input.eventRef,
|
|
206
|
+
durationHours: input.durationHours,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (!isClosedVerbTemplateName(name)) {
|
|
210
|
+
throw new Error(`unknown AFK template '${input.template}'; expected one of: ${AFK_TEMPLATE_NAMES.join(", ")}`);
|
|
211
|
+
}
|
|
212
|
+
if (input.target === null || input.target === undefined || input.target.trim().length === 0) {
|
|
213
|
+
throw new Error(`template ${name} requires a non-empty --target`);
|
|
214
|
+
}
|
|
215
|
+
return mintClosedVerbTemplateGrant({
|
|
216
|
+
projectRoot: input.projectRoot,
|
|
217
|
+
template: name,
|
|
218
|
+
target: input.target,
|
|
219
|
+
actor: input.actor,
|
|
220
|
+
expiresAt: input.expiresAt,
|
|
221
|
+
singleUse: input.singleUse,
|
|
222
|
+
planRef: input.planRef,
|
|
223
|
+
repo: input.repo,
|
|
224
|
+
branch: input.branch,
|
|
225
|
+
now: input.now,
|
|
226
|
+
classification: input.classification,
|
|
227
|
+
pinActive: input.pinActive,
|
|
228
|
+
eventRef: input.eventRef,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Assert that authorization for closed verbs must not use an independent
|
|
233
|
+
* session-auth mint engine. Used by unit tests as a dual-mint guard.
|
|
234
|
+
*/
|
|
235
|
+
export function assertNoIndependentSessionAuthMint() {
|
|
236
|
+
return {
|
|
237
|
+
mintPath: "mintHumanOriginGrant",
|
|
238
|
+
sessionAuthIsAuthority: false,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-origin approval grants + UAT mutation lease (#2944 Wave 1 / #2948 L1–L2).
|
|
3
|
+
*
|
|
4
|
+
* Threat model (aligned agent, not malice): lifecycle/xBRIEF/dispatch tokens the
|
|
5
|
+
* agent authors must not independently satisfy implementation-approval gates.
|
|
6
|
+
* Local-file forgery by a credential-compromised agent remains #983-class OOS.
|
|
7
|
+
*/
|
|
8
|
+
/** Operation classes bound on a grant (Wave 1 structural binding + Wave 4 closed verbs). */
|
|
9
|
+
export declare const AUTHZ_OPERATIONS: readonly ["edit", "push", "pr", "merge", "settings", "deployment", "issue_mutation", "release-cut", "release-publish", "release-rollback"];
|
|
10
|
+
export type AuthzOperation = (typeof AUTHZ_OPERATIONS)[number];
|
|
11
|
+
/**
|
|
12
|
+
* Human-origin kinds accepted as approval provenance.
|
|
13
|
+
* Agent-minted / self-asserted kinds are rejected by the gate.
|
|
14
|
+
*/
|
|
15
|
+
export declare const HUMAN_ORIGIN_KINDS: readonly ["operator-cli", "operator-session", "human-event"];
|
|
16
|
+
export type HumanOriginKind = (typeof HUMAN_ORIGIN_KINDS)[number];
|
|
17
|
+
/** Origin kinds that NEVER satisfy an implementation-approval gate. */
|
|
18
|
+
export declare const REJECTED_ORIGIN_KINDS: readonly ["agent-lifecycle", "xbrief-status", "dispatch-envelope", "allocation-context", "self-asserted", "agent-authored"];
|
|
19
|
+
export type RejectedOriginKind = (typeof REJECTED_ORIGIN_KINDS)[number];
|
|
20
|
+
export interface GrantOrigin {
|
|
21
|
+
readonly kind: string;
|
|
22
|
+
readonly actor: string;
|
|
23
|
+
readonly mintedAt: string;
|
|
24
|
+
readonly mintedVia: string;
|
|
25
|
+
/** Optional external human event ref (issue comment URL, plan:approved id, …). */
|
|
26
|
+
readonly eventRef: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface GrantScope {
|
|
29
|
+
/** Immutable plan id or content hash string (structural; not crypto HMAC). */
|
|
30
|
+
readonly planRef: string | null;
|
|
31
|
+
readonly repo: string | null;
|
|
32
|
+
readonly branch: string | null;
|
|
33
|
+
readonly worktree: string | null;
|
|
34
|
+
/** Path globs for permitted file/product surfaces (incl. user-visible UI). */
|
|
35
|
+
readonly surfaces: readonly string[];
|
|
36
|
+
readonly operations: readonly AuthzOperation[];
|
|
37
|
+
readonly storyIds: readonly string[];
|
|
38
|
+
readonly issueIds: readonly number[];
|
|
39
|
+
/**
|
|
40
|
+
* Named fix cohort under an active UAT lease. Required when UAT is active
|
|
41
|
+
* for product mutations; approving one cohort does not clear UAT.
|
|
42
|
+
*/
|
|
43
|
+
readonly cohortId: string | null;
|
|
44
|
+
}
|
|
45
|
+
export interface GrantSemantics {
|
|
46
|
+
readonly expiresAt: string | null;
|
|
47
|
+
readonly singleUse: boolean;
|
|
48
|
+
readonly usedAt: string | null;
|
|
49
|
+
readonly revokedAt: string | null;
|
|
50
|
+
}
|
|
51
|
+
export interface HumanOriginGrant {
|
|
52
|
+
readonly schemaVersion: 1;
|
|
53
|
+
readonly id: string;
|
|
54
|
+
readonly origin: GrantOrigin;
|
|
55
|
+
readonly scope: GrantScope;
|
|
56
|
+
readonly semantics: GrantSemantics;
|
|
57
|
+
}
|
|
58
|
+
export interface UatLease {
|
|
59
|
+
readonly active: boolean;
|
|
60
|
+
readonly campaignId: string;
|
|
61
|
+
readonly startedAt: string;
|
|
62
|
+
readonly startedBy: GrantOrigin;
|
|
63
|
+
readonly suspendedAt: string | null;
|
|
64
|
+
readonly note: string | null;
|
|
65
|
+
}
|
|
66
|
+
export interface AuthzState {
|
|
67
|
+
readonly schemaVersion: 1;
|
|
68
|
+
readonly uat: UatLease | null;
|
|
69
|
+
/** Optional pin of grant ids considered active (empty = all non-revoked on disk). */
|
|
70
|
+
readonly activeGrantIds: readonly string[];
|
|
71
|
+
}
|
|
72
|
+
export type AuthzDecisionCode = "authz-allow" | "authz-inactive" | "authz-uat-deny" | "authz-grant-missing" | "authz-grant-origin-reject" | "authz-grant-scope-deny" | "authz-grant-expired" | "authz-grant-revoked" | "authz-grant-single-use-spent";
|
|
73
|
+
/** Structured codes for release-class closed-verb gates (#1095 Wave 4). */
|
|
74
|
+
export type ClosedVerbDecisionCode = "closed-verb-allow" | "closed-verb-env-bypass" | "closed-verb-deny-missing" | "closed-verb-deny-origin" | "closed-verb-deny-scope" | "closed-verb-deny-expired" | "closed-verb-deny-revoked" | "closed-verb-deny-spent" | "closed-verb-unknown";
|
|
75
|
+
export interface ClosedVerbDecision {
|
|
76
|
+
readonly allowed: boolean;
|
|
77
|
+
readonly code: ClosedVerbDecisionCode;
|
|
78
|
+
readonly reason: string;
|
|
79
|
+
readonly verb: string;
|
|
80
|
+
readonly target: string | null;
|
|
81
|
+
readonly humanApprovalRef: string | null;
|
|
82
|
+
readonly envBypassKey: string | null;
|
|
83
|
+
readonly skillPointer: string | null;
|
|
84
|
+
}
|
|
85
|
+
export interface AuthzDecision {
|
|
86
|
+
readonly allowed: boolean;
|
|
87
|
+
readonly code: AuthzDecisionCode;
|
|
88
|
+
readonly reason: string;
|
|
89
|
+
readonly humanApprovalRef: string | null;
|
|
90
|
+
readonly approvedScope: GrantScope | null;
|
|
91
|
+
readonly attemptedOp: AuthzOperation | "test" | "evidence" | "unknown";
|
|
92
|
+
readonly path: string | null;
|
|
93
|
+
}
|
|
94
|
+
export interface AuthzAuditRecord {
|
|
95
|
+
readonly schemaVersion: 1;
|
|
96
|
+
readonly ts: string;
|
|
97
|
+
readonly humanApprovalRef: string | null;
|
|
98
|
+
readonly approvedScope: GrantScope | null;
|
|
99
|
+
readonly attemptedOp: string;
|
|
100
|
+
readonly path: string | null;
|
|
101
|
+
readonly result: "allow" | "deny";
|
|
102
|
+
readonly code: AuthzDecisionCode;
|
|
103
|
+
readonly message: string;
|
|
104
|
+
readonly campaignId: string | null;
|
|
105
|
+
}
|
|
106
|
+
export declare const AUTHZ_SCHEMA_VERSION: 1;
|
|
107
|
+
export declare const AUTHZ_DIR = ".deft/authz";
|
|
108
|
+
export declare const AUTHZ_STATE_FILE = "state.json";
|
|
109
|
+
export declare const AUTHZ_GRANTS_DIR = "grants";
|
|
110
|
+
export declare const AUTHZ_AUDIT_FILE = "audit.jsonl";
|
|
111
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-origin approval grants + UAT mutation lease (#2944 Wave 1 / #2948 L1–L2).
|
|
3
|
+
*
|
|
4
|
+
* Threat model (aligned agent, not malice): lifecycle/xBRIEF/dispatch tokens the
|
|
5
|
+
* agent authors must not independently satisfy implementation-approval gates.
|
|
6
|
+
* Local-file forgery by a credential-compromised agent remains #983-class OOS.
|
|
7
|
+
*/
|
|
8
|
+
/** Operation classes bound on a grant (Wave 1 structural binding + Wave 4 closed verbs). */
|
|
9
|
+
export const AUTHZ_OPERATIONS = [
|
|
10
|
+
"edit",
|
|
11
|
+
"push",
|
|
12
|
+
"pr",
|
|
13
|
+
"merge",
|
|
14
|
+
"settings",
|
|
15
|
+
"deployment",
|
|
16
|
+
"issue_mutation",
|
|
17
|
+
/** Release-class closed verbs (#1095 Wave 4) — also satisfied by `deployment`. */
|
|
18
|
+
"release-cut",
|
|
19
|
+
"release-publish",
|
|
20
|
+
"release-rollback",
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Human-origin kinds accepted as approval provenance.
|
|
24
|
+
* Agent-minted / self-asserted kinds are rejected by the gate.
|
|
25
|
+
*/
|
|
26
|
+
export const HUMAN_ORIGIN_KINDS = ["operator-cli", "operator-session", "human-event"];
|
|
27
|
+
/** Origin kinds that NEVER satisfy an implementation-approval gate. */
|
|
28
|
+
export const REJECTED_ORIGIN_KINDS = [
|
|
29
|
+
"agent-lifecycle",
|
|
30
|
+
"xbrief-status",
|
|
31
|
+
"dispatch-envelope",
|
|
32
|
+
"allocation-context",
|
|
33
|
+
"self-asserted",
|
|
34
|
+
"agent-authored",
|
|
35
|
+
];
|
|
36
|
+
export const AUTHZ_SCHEMA_VERSION = 1;
|
|
37
|
+
export const AUTHZ_DIR = ".deft/authz";
|
|
38
|
+
export const AUTHZ_STATE_FILE = "state.json";
|
|
39
|
+
export const AUTHZ_GRANTS_DIR = "grants";
|
|
40
|
+
export const AUTHZ_AUDIT_FILE = "audit.jsonl";
|
|
41
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verb classification table loader + schema validation (#1095 Wave 4).
|
|
3
|
+
* SoT file: conventions/verb-classification.json (repo root).
|
|
4
|
+
*/
|
|
5
|
+
export declare const IRREVERSIBILITY_VALUES: readonly ["reversible-via-git-revert", "reversible-via-release-rollback", "destructive", "reversible"];
|
|
6
|
+
export type Irreversibility = (typeof IRREVERSIBILITY_VALUES)[number];
|
|
7
|
+
export interface VerbClassificationRow {
|
|
8
|
+
readonly closure_set: readonly string[];
|
|
9
|
+
readonly explicit_required: readonly string[];
|
|
10
|
+
readonly irreversibility: string;
|
|
11
|
+
readonly wildcard_allowed: boolean;
|
|
12
|
+
readonly recurring_allowed: boolean;
|
|
13
|
+
readonly default_expiry: string;
|
|
14
|
+
readonly skill: string;
|
|
15
|
+
readonly phase: string;
|
|
16
|
+
/** AuthzOperation names that satisfy this closed verb (Wave 1 grant ops). */
|
|
17
|
+
readonly authz_operations: readonly string[];
|
|
18
|
+
/** Ephemeral env bypass key, e.g. DEFT_ALLOW_RELEASE_PUBLISH. */
|
|
19
|
+
readonly env_bypass: string;
|
|
20
|
+
}
|
|
21
|
+
export interface VerbClassificationTable {
|
|
22
|
+
readonly schemaVersion: number;
|
|
23
|
+
readonly description?: string;
|
|
24
|
+
readonly verbs: Readonly<Record<string, VerbClassificationRow>>;
|
|
25
|
+
}
|
|
26
|
+
export declare class VerbClassificationError extends Error {
|
|
27
|
+
readonly code: string;
|
|
28
|
+
constructor(code: string, message: string);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validate and normalise a raw JSON payload into a VerbClassificationTable.
|
|
32
|
+
* Throws VerbClassificationError on schema violations.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseVerbClassification(raw: unknown): VerbClassificationTable;
|
|
35
|
+
/** Built-in Wave 4/5 rows used when the file is unavailable (tests / install). */
|
|
36
|
+
export declare function builtinReleaseVerbClassification(): VerbClassificationTable;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve conventions/verb-classification.json relative to a project root, then
|
|
39
|
+
* walk up from this module for framework source layouts.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveVerbClassificationPath(projectRoot?: string | null): string | null;
|
|
42
|
+
export declare function loadVerbClassification(projectRoot?: string | null): VerbClassificationTable;
|
|
43
|
+
export declare function getVerbRow(table: VerbClassificationTable, verb: string): VerbClassificationRow | null;
|
|
44
|
+
//# sourceMappingURL=verb-classification.d.ts.map
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verb classification table loader + schema validation (#1095 Wave 4).
|
|
3
|
+
* SoT file: conventions/verb-classification.json (repo root).
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
export const IRREVERSIBILITY_VALUES = [
|
|
9
|
+
"reversible-via-git-revert",
|
|
10
|
+
"reversible-via-release-rollback",
|
|
11
|
+
"destructive",
|
|
12
|
+
"reversible",
|
|
13
|
+
];
|
|
14
|
+
export class VerbClassificationError extends Error {
|
|
15
|
+
code;
|
|
16
|
+
constructor(code, message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "VerbClassificationError";
|
|
19
|
+
this.code = code;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function isRecord(value) {
|
|
23
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function asStringArray(raw, field) {
|
|
26
|
+
if (!Array.isArray(raw)) {
|
|
27
|
+
throw new VerbClassificationError("verb-class-schema", `${field} must be an array of strings`);
|
|
28
|
+
}
|
|
29
|
+
const out = [];
|
|
30
|
+
for (const item of raw) {
|
|
31
|
+
if (typeof item !== "string" || item.trim().length === 0) {
|
|
32
|
+
throw new VerbClassificationError("verb-class-schema", `${field} entries must be non-empty strings`);
|
|
33
|
+
}
|
|
34
|
+
out.push(item.trim());
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
function parseRow(name, raw) {
|
|
39
|
+
if (!isRecord(raw)) {
|
|
40
|
+
throw new VerbClassificationError("verb-class-schema", `verb '${name}' must be an object`);
|
|
41
|
+
}
|
|
42
|
+
const irreversibility = raw.irreversibility;
|
|
43
|
+
if (typeof irreversibility !== "string" || irreversibility.trim().length === 0) {
|
|
44
|
+
throw new VerbClassificationError("verb-class-schema", `verb '${name}' requires irreversibility string`);
|
|
45
|
+
}
|
|
46
|
+
if (typeof raw.wildcard_allowed !== "boolean") {
|
|
47
|
+
throw new VerbClassificationError("verb-class-schema", `verb '${name}' requires wildcard_allowed boolean`);
|
|
48
|
+
}
|
|
49
|
+
if (typeof raw.recurring_allowed !== "boolean") {
|
|
50
|
+
throw new VerbClassificationError("verb-class-schema", `verb '${name}' requires recurring_allowed boolean`);
|
|
51
|
+
}
|
|
52
|
+
if (typeof raw.default_expiry !== "string" || raw.default_expiry.trim().length === 0) {
|
|
53
|
+
throw new VerbClassificationError("verb-class-schema", `verb '${name}' requires default_expiry string`);
|
|
54
|
+
}
|
|
55
|
+
if (typeof raw.skill !== "string" || raw.skill.trim().length === 0) {
|
|
56
|
+
throw new VerbClassificationError("verb-class-schema", `verb '${name}' requires skill string`);
|
|
57
|
+
}
|
|
58
|
+
if (typeof raw.phase !== "string" || raw.phase.trim().length === 0) {
|
|
59
|
+
throw new VerbClassificationError("verb-class-schema", `verb '${name}' requires phase string`);
|
|
60
|
+
}
|
|
61
|
+
if (typeof raw.env_bypass !== "string" || !raw.env_bypass.startsWith("DEFT_ALLOW_")) {
|
|
62
|
+
throw new VerbClassificationError("verb-class-schema", `verb '${name}' requires env_bypass starting with DEFT_ALLOW_`);
|
|
63
|
+
}
|
|
64
|
+
const ops = asStringArray(raw.authz_operations, `${name}.authz_operations`);
|
|
65
|
+
if (ops.length === 0) {
|
|
66
|
+
throw new VerbClassificationError("verb-class-schema", `verb '${name}' authz_operations must be non-empty`);
|
|
67
|
+
}
|
|
68
|
+
if (raw.wildcard_allowed === true) {
|
|
69
|
+
throw new VerbClassificationError("verb-class-policy", `verb '${name}' must set wildcard_allowed=false for Wave 4 release-class closed verbs`);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
closure_set: asStringArray(raw.closure_set, `${name}.closure_set`),
|
|
73
|
+
explicit_required: asStringArray(raw.explicit_required, `${name}.explicit_required`),
|
|
74
|
+
irreversibility: irreversibility.trim(),
|
|
75
|
+
wildcard_allowed: false,
|
|
76
|
+
recurring_allowed: raw.recurring_allowed,
|
|
77
|
+
default_expiry: raw.default_expiry.trim(),
|
|
78
|
+
skill: raw.skill.trim(),
|
|
79
|
+
phase: raw.phase.trim(),
|
|
80
|
+
authz_operations: ops,
|
|
81
|
+
env_bypass: raw.env_bypass.trim(),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Validate and normalise a raw JSON payload into a VerbClassificationTable.
|
|
86
|
+
* Throws VerbClassificationError on schema violations.
|
|
87
|
+
*/
|
|
88
|
+
export function parseVerbClassification(raw) {
|
|
89
|
+
if (!isRecord(raw)) {
|
|
90
|
+
throw new VerbClassificationError("verb-class-schema", "root must be an object");
|
|
91
|
+
}
|
|
92
|
+
const schemaVersion = raw.schemaVersion;
|
|
93
|
+
if (typeof schemaVersion !== "number" || !Number.isInteger(schemaVersion) || schemaVersion < 1) {
|
|
94
|
+
throw new VerbClassificationError("verb-class-schema", "schemaVersion must be a positive integer");
|
|
95
|
+
}
|
|
96
|
+
if (!isRecord(raw.verbs)) {
|
|
97
|
+
throw new VerbClassificationError("verb-class-schema", "verbs must be an object");
|
|
98
|
+
}
|
|
99
|
+
const verbs = {};
|
|
100
|
+
for (const [name, row] of Object.entries(raw.verbs)) {
|
|
101
|
+
if (name.trim().length === 0) {
|
|
102
|
+
throw new VerbClassificationError("verb-class-schema", "verb name must be non-empty");
|
|
103
|
+
}
|
|
104
|
+
verbs[name.trim()] = parseRow(name.trim(), row);
|
|
105
|
+
}
|
|
106
|
+
if (Object.keys(verbs).length === 0) {
|
|
107
|
+
throw new VerbClassificationError("verb-class-schema", "verbs must include at least one row");
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
schemaVersion,
|
|
111
|
+
description: typeof raw.description === "string" ? raw.description : undefined,
|
|
112
|
+
verbs,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** Built-in Wave 4/5 rows used when the file is unavailable (tests / install). */
|
|
116
|
+
export function builtinReleaseVerbClassification() {
|
|
117
|
+
return parseVerbClassification({
|
|
118
|
+
schemaVersion: 1,
|
|
119
|
+
description: "builtin release-class + finish-loop closed verbs (#1095 / #871)",
|
|
120
|
+
verbs: {
|
|
121
|
+
"release-cut": {
|
|
122
|
+
closure_set: [
|
|
123
|
+
"changelog-promote",
|
|
124
|
+
"roadmap-render",
|
|
125
|
+
"tag-create",
|
|
126
|
+
"tag-push",
|
|
127
|
+
"gh-release-create-draft",
|
|
128
|
+
],
|
|
129
|
+
explicit_required: ["release-publish", "release-rollback"],
|
|
130
|
+
irreversibility: "reversible-via-release-rollback",
|
|
131
|
+
wildcard_allowed: false,
|
|
132
|
+
recurring_allowed: false,
|
|
133
|
+
default_expiry: "4h",
|
|
134
|
+
skill: "content/skills/deft-directive-release/SKILL.md",
|
|
135
|
+
phase: "Phase 4",
|
|
136
|
+
authz_operations: ["release-cut", "deployment"],
|
|
137
|
+
env_bypass: "DEFT_ALLOW_RELEASE_CUT",
|
|
138
|
+
},
|
|
139
|
+
"release-publish": {
|
|
140
|
+
closure_set: [],
|
|
141
|
+
explicit_required: [],
|
|
142
|
+
irreversibility: "destructive",
|
|
143
|
+
wildcard_allowed: false,
|
|
144
|
+
recurring_allowed: false,
|
|
145
|
+
default_expiry: "1h",
|
|
146
|
+
skill: "content/skills/deft-directive-release/SKILL.md",
|
|
147
|
+
phase: "Phase 5",
|
|
148
|
+
authz_operations: ["release-publish", "deployment"],
|
|
149
|
+
env_bypass: "DEFT_ALLOW_RELEASE_PUBLISH",
|
|
150
|
+
},
|
|
151
|
+
"release-rollback": {
|
|
152
|
+
closure_set: [],
|
|
153
|
+
explicit_required: [],
|
|
154
|
+
irreversibility: "destructive",
|
|
155
|
+
wildcard_allowed: false,
|
|
156
|
+
recurring_allowed: false,
|
|
157
|
+
default_expiry: "1h",
|
|
158
|
+
skill: "content/skills/deft-directive-release/SKILL.md",
|
|
159
|
+
phase: "Phase 7",
|
|
160
|
+
authz_operations: ["release-rollback", "deployment"],
|
|
161
|
+
env_bypass: "DEFT_ALLOW_RELEASE_ROLLBACK",
|
|
162
|
+
},
|
|
163
|
+
"finish-loop": {
|
|
164
|
+
closure_set: [
|
|
165
|
+
"edit",
|
|
166
|
+
"push",
|
|
167
|
+
"pr",
|
|
168
|
+
"merge",
|
|
169
|
+
"pr-watch",
|
|
170
|
+
"pr-finish-loop",
|
|
171
|
+
"directive-finish-loop",
|
|
172
|
+
],
|
|
173
|
+
explicit_required: ["release-cut", "release-publish", "release-rollback"],
|
|
174
|
+
irreversibility: "reversible-via-git-revert",
|
|
175
|
+
wildcard_allowed: false,
|
|
176
|
+
recurring_allowed: true,
|
|
177
|
+
default_expiry: "8h",
|
|
178
|
+
skill: "content/contracts/finish-loop.md",
|
|
179
|
+
phase: "walk-away",
|
|
180
|
+
authz_operations: ["edit", "push", "pr", "merge"],
|
|
181
|
+
env_bypass: "DEFT_ALLOW_FINISH_LOOP",
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Resolve conventions/verb-classification.json relative to a project root, then
|
|
188
|
+
* walk up from this module for framework source layouts.
|
|
189
|
+
*/
|
|
190
|
+
export function resolveVerbClassificationPath(projectRoot) {
|
|
191
|
+
const candidates = [];
|
|
192
|
+
if (projectRoot !== null && projectRoot !== undefined && projectRoot.trim().length > 0) {
|
|
193
|
+
candidates.push(resolve(projectRoot, "conventions", "verb-classification.json"));
|
|
194
|
+
}
|
|
195
|
+
// packages/core/src/authz → repo root: ../../../../conventions
|
|
196
|
+
try {
|
|
197
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
198
|
+
candidates.push(resolve(here, "..", "..", "..", "..", "conventions", "verb-classification.json"));
|
|
199
|
+
candidates.push(resolve(here, "..", "..", "..", "conventions", "verb-classification.json"));
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// ignore import.meta failures in non-ESM test shims
|
|
203
|
+
}
|
|
204
|
+
candidates.push(join(process.cwd(), "conventions", "verb-classification.json"));
|
|
205
|
+
for (const p of candidates) {
|
|
206
|
+
if (existsSync(p))
|
|
207
|
+
return p;
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
export function loadVerbClassification(projectRoot) {
|
|
212
|
+
const path = resolveVerbClassificationPath(projectRoot);
|
|
213
|
+
if (path === null) {
|
|
214
|
+
return builtinReleaseVerbClassification();
|
|
215
|
+
}
|
|
216
|
+
const text = readFileSync(path, "utf8");
|
|
217
|
+
let raw;
|
|
218
|
+
try {
|
|
219
|
+
raw = JSON.parse(text);
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
throw new VerbClassificationError("verb-class-parse", `failed to parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
223
|
+
}
|
|
224
|
+
return parseVerbClassification(raw);
|
|
225
|
+
}
|
|
226
|
+
export function getVerbRow(table, verb) {
|
|
227
|
+
const key = verb.trim().toLowerCase();
|
|
228
|
+
const direct = table.verbs[key];
|
|
229
|
+
if (direct !== undefined)
|
|
230
|
+
return direct;
|
|
231
|
+
for (const [name, row] of Object.entries(table.verbs)) {
|
|
232
|
+
if (name.toLowerCase() === key)
|
|
233
|
+
return row;
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=verb-classification.js.map
|