@deftai/directive-core 0.99.0 → 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 +157 -23
- 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/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/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 +1 -0
- package/dist/index.js +1 -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/policy/ceremony-dial.d.ts +67 -11
- package/dist/policy/ceremony-dial.js +174 -23
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +1 -0
- 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/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/test-gh-fixtures.helpers.js +4 -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/index.d.ts +3 -0
- package/dist/scope/index.js +3 -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 +61 -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 +6 -0
- package/dist/session/session-start.js +53 -2
- 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/triage/actions/candidates-log.d.ts +1 -2
- package/dist/triage/actions/candidates-log.js +37 -6
- package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
- package/dist/xbrief-migrate/migrate-project.js +92 -3
- package/package.json +7 -3
package/dist/authz/actions.d.ts
CHANGED
|
@@ -39,6 +39,12 @@ export interface MintGrantInput {
|
|
|
39
39
|
readonly now?: Date;
|
|
40
40
|
/** When true, pin grant id into state.activeGrantIds. */
|
|
41
41
|
readonly pinActive?: boolean;
|
|
42
|
+
/** SHA-256 hex of exact draft bytes for scope.decompose.apply.structural (#3239). */
|
|
43
|
+
readonly contentDigest?: string | null;
|
|
44
|
+
/** Project-relative parent artifact path (#3239). */
|
|
45
|
+
readonly parentPath?: string | null;
|
|
46
|
+
/** Project-relative draft/target path (#3239). */
|
|
47
|
+
readonly targetPath?: string | null;
|
|
42
48
|
}
|
|
43
49
|
export declare function mintHumanOriginGrant(input: MintGrantInput): HumanOriginGrant;
|
|
44
50
|
export interface RevokeGrantInput {
|
package/dist/authz/actions.js
CHANGED
|
@@ -78,6 +78,9 @@ export function mintHumanOriginGrant(input) {
|
|
|
78
78
|
storyIds: input.storyIds ?? [],
|
|
79
79
|
issueIds: input.issueIds ?? [],
|
|
80
80
|
cohortId: input.cohortId ?? null,
|
|
81
|
+
contentDigest: input.contentDigest ?? null,
|
|
82
|
+
parentPath: input.parentPath ?? null,
|
|
83
|
+
targetPath: input.targetPath ?? null,
|
|
81
84
|
},
|
|
82
85
|
semantics: {
|
|
83
86
|
expiresAt: input.expiresAt ?? null,
|
package/dist/authz/classify.js
CHANGED
|
@@ -180,7 +180,7 @@ const POLICY_AUTHORITY_MUTATORS = new Set([
|
|
|
180
180
|
const KILL_SWITCH_BASENAMES = [".deft-directive-disable", ".no-deft-directive"];
|
|
181
181
|
/**
|
|
182
182
|
* Downloaders / decoders / remote-copy tools that can plant files without shell
|
|
183
|
-
* redirects (#3206 / #3213). Not in INDIRECT_WRITE_BINS: those feed hasWriteShape
|
|
183
|
+
* redirects (#3206 / #3213 / #3245). Not in INDIRECT_WRITE_BINS: those feed hasWriteShape
|
|
184
184
|
* and would classify bare `curl $URL` as a store write via opaque-expansion heuristics.
|
|
185
185
|
*/
|
|
186
186
|
const DOWNLOADER_DECODER_BINS = new Set([
|
|
@@ -192,11 +192,42 @@ const DOWNLOADER_DECODER_BINS = new Set([
|
|
|
192
192
|
"scp",
|
|
193
193
|
"aria2c",
|
|
194
194
|
"certutil",
|
|
195
|
+
// #3245 residual after #3213: archive extractors + further alt downloaders.
|
|
196
|
+
"tar",
|
|
197
|
+
"bsdtar",
|
|
198
|
+
"unzip",
|
|
199
|
+
"7z",
|
|
200
|
+
"7za",
|
|
201
|
+
"7zr",
|
|
202
|
+
"rclone",
|
|
203
|
+
"axel",
|
|
204
|
+
"fetch",
|
|
205
|
+
"socat",
|
|
206
|
+
"lftp",
|
|
207
|
+
]);
|
|
208
|
+
/**
|
|
209
|
+
* Archive extractors / alt writers that can plant via pathish operands without
|
|
210
|
+
* shell redirects (#3245). Used for pathish authz/kill scans (prefer deny) and
|
|
211
|
+
* write-shape residual under UAT — not bare curl-class URL fetches.
|
|
212
|
+
*/
|
|
213
|
+
const ARCHIVE_ALT_WRITE_BINS = new Set([
|
|
214
|
+
"tar",
|
|
215
|
+
"bsdtar",
|
|
216
|
+
"unzip",
|
|
217
|
+
"7z",
|
|
218
|
+
"7za",
|
|
219
|
+
"7zr",
|
|
220
|
+
"rclone",
|
|
221
|
+
"axel",
|
|
222
|
+
"fetch",
|
|
223
|
+
"socat",
|
|
224
|
+
"lftp",
|
|
195
225
|
]);
|
|
196
226
|
/**
|
|
197
227
|
* File destination flags for downloaders/decoders (#3206).
|
|
198
228
|
* normalizeToken lowercases, so wget `-O` and curl `-o` share `-o`.
|
|
199
229
|
* scp: `-o` is an SSH option — excluded in isDownloaderDestFlag.
|
|
230
|
+
* 7z: `-oDIR` is attached-only (no separate value token).
|
|
200
231
|
*/
|
|
201
232
|
const DOWNLOADER_FILE_DEST_FLAGS = new Set([
|
|
202
233
|
"-o",
|
|
@@ -209,11 +240,28 @@ const DOWNLOADER_FILE_DEST_FLAGS = new Set([
|
|
|
209
240
|
const CURL_DIR_DEST_FLAGS = new Set(["--output-dir"]);
|
|
210
241
|
const WGET_DIR_DEST_FLAGS = new Set(["-p", "--directory-prefix"]);
|
|
211
242
|
const ARIA2C_DIR_DEST_FLAGS = new Set(["-d", "--dir"]);
|
|
243
|
+
/**
|
|
244
|
+
* tar/bsdtar extract directory (#3245).
|
|
245
|
+
* Case-sensitive: POSIX tar uses capital `-C` for chdir; lower `-c` is create and is NOT dest.
|
|
246
|
+
*/
|
|
247
|
+
const TAR_DIR_DEST_FLAGS_EXACT = new Set(["-C"]);
|
|
248
|
+
const TAR_DIR_DEST_FLAGS_LOWER = new Set(["--directory"]);
|
|
249
|
+
/** unzip extract directory (#3245). */
|
|
250
|
+
const UNZIP_DIR_DEST_FLAGS = new Set(["-d"]);
|
|
212
251
|
/**
|
|
213
252
|
* Symlink / hard-link plant bins (#3213). Absent from prior killWriteBins →
|
|
214
253
|
* `ln -sf … .deft-directive-disable` classified empty → UAT fail-open.
|
|
215
254
|
*/
|
|
216
255
|
const SYMLINK_PLANT_BINS = new Set(["ln", "link", "mklink"]);
|
|
256
|
+
/** socat address prefixes that open a write target (#3245). */
|
|
257
|
+
const SOCAT_WRITE_ADDR_PREFIXES = [
|
|
258
|
+
"open:",
|
|
259
|
+
"create:",
|
|
260
|
+
"creat:",
|
|
261
|
+
"append:",
|
|
262
|
+
"owronly:",
|
|
263
|
+
"oappend:",
|
|
264
|
+
];
|
|
217
265
|
/** Final path segment of a token (path-qualified bins / .exe). */
|
|
218
266
|
function binBareName(token) {
|
|
219
267
|
const pathish = token.replace(/['"]/g, "").toLowerCase().replace(/\\/g, "/");
|
|
@@ -227,10 +275,19 @@ function isDownloaderDecoderBin(token) {
|
|
|
227
275
|
return false;
|
|
228
276
|
return DOWNLOADER_DECODER_BINS.has(binBareName(token));
|
|
229
277
|
}
|
|
230
|
-
|
|
278
|
+
/**
|
|
279
|
+
* True when `flag` (normalized lower) or `rawFlag` (original token) is a dest flag for `bin`.
|
|
280
|
+
* tar `-C` must use raw case: lowercased `-c` is create-archive, not chdir (#3245).
|
|
281
|
+
* 7z uses attached `-oDIR` only — separate `-o PATH` is not a 7z dest form (handled elsewhere).
|
|
282
|
+
*/
|
|
283
|
+
function isDownloaderDestFlag(flag, bin, rawFlag) {
|
|
231
284
|
// scp: `-o` is OpenSSH option (ProxyCommand, …), not a file dest flag.
|
|
232
285
|
if (bin === "scp")
|
|
233
286
|
return false;
|
|
287
|
+
// 7z family: only attached `-oDIR` (parsed in attached-short branch), not separate `-o PATH`.
|
|
288
|
+
if (bin === "7z" || bin === "7za" || bin === "7zr") {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
234
291
|
if (DOWNLOADER_FILE_DEST_FLAGS.has(flag))
|
|
235
292
|
return true;
|
|
236
293
|
if (bin === "curl" && CURL_DIR_DEST_FLAGS.has(flag))
|
|
@@ -239,6 +296,18 @@ function isDownloaderDestFlag(flag, bin) {
|
|
|
239
296
|
return true;
|
|
240
297
|
if (bin === "aria2c" && ARIA2C_DIR_DEST_FLAGS.has(flag))
|
|
241
298
|
return true;
|
|
299
|
+
if (bin === "tar" || bin === "bsdtar") {
|
|
300
|
+
if (rawFlag !== undefined && TAR_DIR_DEST_FLAGS_EXACT.has(rawFlag))
|
|
301
|
+
return true;
|
|
302
|
+
if (TAR_DIR_DEST_FLAGS_LOWER.has(flag))
|
|
303
|
+
return true;
|
|
304
|
+
// Exact match on raw when normalize collapsed case for long flags only.
|
|
305
|
+
if (rawFlag !== undefined && TAR_DIR_DEST_FLAGS_LOWER.has(normalizeToken(rawFlag)))
|
|
306
|
+
return true;
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
if (bin === "unzip" && UNZIP_DIR_DEST_FLAGS.has(flag))
|
|
310
|
+
return true;
|
|
242
311
|
return false;
|
|
243
312
|
}
|
|
244
313
|
/**
|
|
@@ -292,7 +361,8 @@ function firstUnquotedShellOpIndex(raw) {
|
|
|
292
361
|
/**
|
|
293
362
|
* Destinations from curl/wget/xxd/openssl/scp/aria2c/certutil via -o/--output/-O/-out/
|
|
294
363
|
* --output-dir/-P/-d/--dir (separate, =value, or attached short form), xxd -r path-like
|
|
295
|
-
* write positionals (#3206),
|
|
364
|
+
* write positionals (#3206), positional dests for scp/certutil (#3213), and #3245 residual
|
|
365
|
+
* archive/alt bins (tar -C, unzip -d, 7z -oDIR, rclone positionals, socat OPEN:, axel/fetch -o).
|
|
296
366
|
* openssl uses flags only (no positional dests — avoids treating -in paths as writes).
|
|
297
367
|
* Segment stops at shell operators so compound `scp …authz…; echo` cannot overwrite dests.
|
|
298
368
|
* O(n) token walk — no nested-quantifier regex on untrusted input.
|
|
@@ -309,13 +379,24 @@ function downloaderDecoderDestinations(tokens) {
|
|
|
309
379
|
i++;
|
|
310
380
|
// xxd reverse mode writes; without -r, path positionals are dump inputs (read).
|
|
311
381
|
let xxdReverse = false;
|
|
312
|
-
// scp / certutil: collect pathish operands in this segment
|
|
382
|
+
// scp / certutil / rclone / archive extractors: collect pathish operands in this segment.
|
|
313
383
|
// Fail-closed under UAT: any pathish mentioning protected store/kill paths is a dest
|
|
314
384
|
// candidate (scp source-or-dest of `.deft/authz` is treated as settings — prefer deny
|
|
315
385
|
// over source/dest thrash). Last pathish remains the ordinary write dest.
|
|
316
386
|
// Segment breaks (`;`/`\n`/glued ops) prevent following-command overwrite.
|
|
317
387
|
let lastPositionalPath = null;
|
|
318
388
|
const protectedPathish = [];
|
|
389
|
+
const collectsProtectedPositionals = bin === "scp" ||
|
|
390
|
+
bin === "certutil" ||
|
|
391
|
+
bin === "rclone" ||
|
|
392
|
+
bin === "tar" ||
|
|
393
|
+
bin === "bsdtar" ||
|
|
394
|
+
bin === "unzip" ||
|
|
395
|
+
bin === "7z" ||
|
|
396
|
+
bin === "7za" ||
|
|
397
|
+
bin === "7zr" ||
|
|
398
|
+
bin === "socat" ||
|
|
399
|
+
bin === "lftp";
|
|
319
400
|
while (i < tokens.length) {
|
|
320
401
|
const raw = tokens[i];
|
|
321
402
|
const n = normalizeToken(raw);
|
|
@@ -361,10 +442,12 @@ function downloaderDecoderDestinations(tokens) {
|
|
|
361
442
|
continue;
|
|
362
443
|
}
|
|
363
444
|
// --flag=value (and rare -out=value); scp -oOption=Value attached form also lands here.
|
|
445
|
+
// tar --directory=PATH also lands here.
|
|
364
446
|
if (n.includes("=") && (n.startsWith("-") || n.startsWith("--"))) {
|
|
365
447
|
const eq = raw.indexOf("=");
|
|
366
|
-
const
|
|
367
|
-
|
|
448
|
+
const flagRaw = raw.slice(0, eq);
|
|
449
|
+
const flag = normalizeToken(flagRaw);
|
|
450
|
+
if (isDownloaderDestFlag(flag, bin, flagRaw)) {
|
|
368
451
|
dests.push(pathishToken(raw.slice(eq + 1)));
|
|
369
452
|
}
|
|
370
453
|
// scp attached -oProxyCommand=… is not a file dest — skip without recording.
|
|
@@ -373,6 +456,7 @@ function downloaderDecoderDestinations(tokens) {
|
|
|
373
456
|
}
|
|
374
457
|
// Attached short: -oPATH / -OPATH (after lowercasing both are -opath…)
|
|
375
458
|
// Skip for scp (OpenSSH -oOption=Value attached forms are not file dests).
|
|
459
|
+
// 7z requires attached -oDIR (#3245).
|
|
376
460
|
if (bin !== "scp" && n.startsWith("-") && !n.startsWith("--") && n.length > 2) {
|
|
377
461
|
if (n.startsWith("-out") && n.length > 4 && !n.startsWith("-output")) {
|
|
378
462
|
dests.push(pathishToken(raw.slice(4)));
|
|
@@ -396,9 +480,21 @@ function downloaderDecoderDestinations(tokens) {
|
|
|
396
480
|
i++;
|
|
397
481
|
continue;
|
|
398
482
|
}
|
|
483
|
+
// unzip attached -dDIR (#3245)
|
|
484
|
+
if (bin === "unzip" && n.startsWith("-d") && n.length > 2) {
|
|
485
|
+
dests.push(pathishToken(raw.slice(2)));
|
|
486
|
+
i++;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
// tar attached -CDIR (rare; capital C required)
|
|
490
|
+
if ((bin === "tar" || bin === "bsdtar") && raw.startsWith("-C") && raw.length > 2) {
|
|
491
|
+
dests.push(pathishToken(raw.slice(2)));
|
|
492
|
+
i++;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
399
495
|
}
|
|
400
|
-
// Separate value: -o PATH / --output PATH / -out PATH / --output-dir / -P / -d
|
|
401
|
-
if (isDownloaderDestFlag(n, bin)) {
|
|
496
|
+
// Separate value: -o PATH / --output PATH / -out PATH / --output-dir / -P / -d / -C DIR
|
|
497
|
+
if (isDownloaderDestFlag(n, bin, raw)) {
|
|
402
498
|
const next = tokens[i + 1];
|
|
403
499
|
if (next !== undefined && !String(next).startsWith("-") && !isShellSegmentBreak(next)) {
|
|
404
500
|
dests.push(pathishToken(next));
|
|
@@ -418,16 +514,32 @@ function downloaderDecoderDestinations(tokens) {
|
|
|
418
514
|
dests.push(p);
|
|
419
515
|
}
|
|
420
516
|
}
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
517
|
+
// socat write address forms: OPEN:path, CREATE:path, APPEND:path (#3245).
|
|
518
|
+
if (bin === "socat" && !n.startsWith("-")) {
|
|
519
|
+
const p = pathishToken(raw);
|
|
520
|
+
for (const prefix of SOCAT_WRITE_ADDR_PREFIXES) {
|
|
521
|
+
if (p.startsWith(prefix) && p.length > prefix.length) {
|
|
522
|
+
const dest = p.slice(prefix.length);
|
|
523
|
+
dests.push(dest);
|
|
524
|
+
if (dest.includes(".deft/authz") ||
|
|
525
|
+
dest.includes(".deft-directive-disable") ||
|
|
526
|
+
dest.includes(".no-deft-directive")) {
|
|
527
|
+
protectedPathish.push(dest);
|
|
528
|
+
}
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// scp / certutil / rclone / archive extractors: pathish operands (quote-aware glued-op cut).
|
|
534
|
+
// Under UAT: any `.deft/authz` / kill-switch pathish is fail-closed settings
|
|
535
|
+
// (read vs write thrash deferred — prefer deny over dest-parser perfection; #3213 / #3245).
|
|
536
|
+
if (collectsProtectedPositionals && !n.startsWith("-")) {
|
|
425
537
|
const cut = firstUnquotedShellOpIndex(raw);
|
|
426
538
|
const cleaned = cut >= 0 ? raw.slice(0, cut) : raw;
|
|
427
539
|
const p = pathishToken(cleaned);
|
|
428
540
|
if (p.length > 0) {
|
|
429
541
|
lastPositionalPath = p;
|
|
430
|
-
// Fail-closed: protected store/kill basenames anywhere in
|
|
542
|
+
// Fail-closed: protected store/kill basenames anywhere in pathish.
|
|
431
543
|
if (p.includes(".deft/authz") ||
|
|
432
544
|
p.includes(".deft-directive-disable") ||
|
|
433
545
|
p.includes(".no-deft-directive")) {
|
|
@@ -589,10 +701,12 @@ function hasKillSwitchShellWrite(command, tokens) {
|
|
|
589
701
|
if (pathishMentionsKillSwitch(dest))
|
|
590
702
|
return true;
|
|
591
703
|
}
|
|
592
|
-
// Write/destructive + symlink plant bins with a kill-switch path
|
|
704
|
+
// Write/destructive + symlink plant + archive/alt-write bins with a kill-switch path
|
|
705
|
+
// argument (#3213 ln/link/mklink; #3245 tar/axel/rclone/socat residual).
|
|
593
706
|
const killWriteBins = new Set([
|
|
594
707
|
...INDIRECT_WRITE_BINS,
|
|
595
708
|
...SYMLINK_PLANT_BINS,
|
|
709
|
+
...ARCHIVE_ALT_WRITE_BINS,
|
|
596
710
|
"touch",
|
|
597
711
|
"new-item",
|
|
598
712
|
"ni",
|
|
@@ -606,8 +720,15 @@ function hasKillSwitchShellWrite(command, tokens) {
|
|
|
606
720
|
if (!killWriteBins.has(binTok) && !killWriteBins.has(bare))
|
|
607
721
|
continue;
|
|
608
722
|
for (let tj = ti + 1; tj < tokens.length; tj++) {
|
|
609
|
-
|
|
723
|
+
const p = pathishToken(tokens[tj]);
|
|
724
|
+
if (pathishMentionsKillSwitch(p))
|
|
610
725
|
return true;
|
|
726
|
+
// socat OPEN:.deft-directive-disable — path after address prefix (#3245).
|
|
727
|
+
for (const prefix of SOCAT_WRITE_ADDR_PREFIXES) {
|
|
728
|
+
if (p.startsWith(prefix) && pathishMentionsKillSwitch(p.slice(prefix.length))) {
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
611
732
|
}
|
|
612
733
|
}
|
|
613
734
|
// Bare `touch .deft-directive-disable` / `ln -sf x .deft-directive-disable` — path later.
|
|
@@ -833,24 +954,32 @@ function hasAuthzDirShellWrite(command, tokens) {
|
|
|
833
954
|
return true;
|
|
834
955
|
}
|
|
835
956
|
}
|
|
836
|
-
// Write/destructive + symlink plant bins with an authz path argument
|
|
837
|
-
// quote-strip resistant). Always run — do not gate on contiguous `.deft/authz`
|
|
838
|
-
// in the raw command (#3213). SYMLINK_PLANT_BINS: `ln -s forged .deft/authz/grants/x`
|
|
839
|
-
// must not fail-open as unclassifiable (SLizard residual).
|
|
957
|
+
// Write/destructive + symlink plant + archive/alt-write bins with an authz path argument
|
|
958
|
+
// (pathish = quote-strip resistant). Always run — do not gate on contiguous `.deft/authz`
|
|
959
|
+
// in the raw command (#3213 / #3245). SYMLINK_PLANT_BINS: `ln -s forged .deft/authz/grants/x`
|
|
960
|
+
// must not fail-open as unclassifiable (SLizard residual). ARCHIVE_ALT_WRITE_BINS: tar/rclone
|
|
961
|
+
// residual pathish without dest-flag perfection thrash.
|
|
840
962
|
for (let ti = 0; ti < tokens.length; ti++) {
|
|
841
963
|
const bare = binBareName(tokens[ti]);
|
|
842
964
|
const n = normalizeToken(tokens[ti]);
|
|
843
965
|
if (!INDIRECT_WRITE_BINS.has(n) &&
|
|
844
966
|
!INDIRECT_WRITE_BINS.has(bare) &&
|
|
845
|
-
!SYMLINK_PLANT_BINS.has(bare)
|
|
967
|
+
!SYMLINK_PLANT_BINS.has(bare) &&
|
|
968
|
+
!ARCHIVE_ALT_WRITE_BINS.has(bare)) {
|
|
846
969
|
continue;
|
|
847
970
|
}
|
|
848
971
|
for (let tj = ti + 1; tj < tokens.length; tj++) {
|
|
849
|
-
|
|
972
|
+
const p = pathishToken(tokens[tj]);
|
|
973
|
+
if (pathishIsAuthzDir(p))
|
|
850
974
|
return true;
|
|
975
|
+
for (const prefix of SOCAT_WRITE_ADDR_PREFIXES) {
|
|
976
|
+
if (p.startsWith(prefix) && pathishIsAuthzDir(p.slice(prefix.length))) {
|
|
977
|
+
return true;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
851
980
|
}
|
|
852
981
|
}
|
|
853
|
-
// Downloader/decoder destinations under .deft/authz (#3206 / #3213
|
|
982
|
+
// Downloader/decoder destinations under .deft/authz (#3206 / #3213 / #3245 archive+alt).
|
|
854
983
|
for (const dest of downloaderDecoderDestinations(tokens)) {
|
|
855
984
|
if (pathishIsAuthzDir(dest))
|
|
856
985
|
return true;
|
|
@@ -932,7 +1061,12 @@ function hasWriteShape(command, tokens) {
|
|
|
932
1061
|
if (command.includes(">"))
|
|
933
1062
|
return true;
|
|
934
1063
|
for (const t of tokens) {
|
|
935
|
-
|
|
1064
|
+
const bare = binBareName(t);
|
|
1065
|
+
if (INDIRECT_WRITE_BINS.has(normalizeToken(t)) || INDIRECT_WRITE_BINS.has(bare))
|
|
1066
|
+
return true;
|
|
1067
|
+
// #3245: archive extractors / alt downloaders are write-shaped (defense-in-depth for
|
|
1068
|
+
// unclassifiable plant paths under UAT — not bare curl $URL which stays non-write-shaped).
|
|
1069
|
+
if (ARCHIVE_ALT_WRITE_BINS.has(bare))
|
|
936
1070
|
return true;
|
|
937
1071
|
}
|
|
938
1072
|
return false;
|
|
@@ -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
|