@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,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator-facing authz actions: UAT start/suspend, grant mint/revoke (#2944).
|
|
3
|
+
* All mint paths stamp human-origin `operator-cli` provenance.
|
|
4
|
+
*/
|
|
5
|
+
import type { AuthzOperation, AuthzState, HumanOriginGrant, UatLease } from "./types.js";
|
|
6
|
+
export interface StartUatInput {
|
|
7
|
+
readonly projectRoot: string;
|
|
8
|
+
readonly campaignId: string;
|
|
9
|
+
readonly actor?: string;
|
|
10
|
+
readonly note?: string | null;
|
|
11
|
+
readonly now?: Date;
|
|
12
|
+
}
|
|
13
|
+
export declare function startUatLease(input: StartUatInput): {
|
|
14
|
+
state: AuthzState;
|
|
15
|
+
lease: UatLease;
|
|
16
|
+
};
|
|
17
|
+
export interface SuspendUatInput {
|
|
18
|
+
readonly projectRoot: string;
|
|
19
|
+
readonly actor?: string;
|
|
20
|
+
readonly now?: Date;
|
|
21
|
+
}
|
|
22
|
+
export declare function suspendUatLease(input: SuspendUatInput): AuthzState;
|
|
23
|
+
export interface MintGrantInput {
|
|
24
|
+
readonly projectRoot: string;
|
|
25
|
+
readonly actor?: string;
|
|
26
|
+
readonly operations: readonly AuthzOperation[];
|
|
27
|
+
readonly surfaces?: readonly string[];
|
|
28
|
+
readonly cohortId?: string | null;
|
|
29
|
+
readonly planRef?: string | null;
|
|
30
|
+
readonly repo?: string | null;
|
|
31
|
+
readonly branch?: string | null;
|
|
32
|
+
readonly worktree?: string | null;
|
|
33
|
+
readonly storyIds?: readonly string[];
|
|
34
|
+
readonly issueIds?: readonly number[];
|
|
35
|
+
readonly expiresAt?: string | null;
|
|
36
|
+
readonly singleUse?: boolean;
|
|
37
|
+
readonly eventRef?: string | null;
|
|
38
|
+
readonly grantId?: string;
|
|
39
|
+
readonly now?: Date;
|
|
40
|
+
/** When true, pin grant id into state.activeGrantIds. */
|
|
41
|
+
readonly pinActive?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare function mintHumanOriginGrant(input: MintGrantInput): HumanOriginGrant;
|
|
44
|
+
export interface RevokeGrantInput {
|
|
45
|
+
readonly projectRoot: string;
|
|
46
|
+
readonly grantId: string;
|
|
47
|
+
readonly now?: Date;
|
|
48
|
+
}
|
|
49
|
+
export declare function revokeGrant(input: RevokeGrantInput): HumanOriginGrant | null;
|
|
50
|
+
export declare function showAuthzSnapshot(projectRoot: string): {
|
|
51
|
+
state: AuthzState;
|
|
52
|
+
activeGrants: HumanOriginGrant[];
|
|
53
|
+
allGrants: HumanOriginGrant[];
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=actions.d.ts.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator-facing authz actions: UAT start/suspend, grant mint/revoke (#2944).
|
|
3
|
+
* All mint paths stamp human-origin `operator-cli` provenance.
|
|
4
|
+
*/
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { listActiveHumanGrants, listGrants, loadAuthzState, mintOperatorOrigin, saveAuthzState, saveGrant, utcIso, } from "./store.js";
|
|
7
|
+
import { AUTHZ_OPERATIONS } from "./types.js";
|
|
8
|
+
function newGrantId(now) {
|
|
9
|
+
const ts = (now ?? new Date())
|
|
10
|
+
.toISOString()
|
|
11
|
+
.replace(/[-:TZ.]/g, "")
|
|
12
|
+
.slice(0, 14);
|
|
13
|
+
const suffix = randomBytes(3).toString("hex");
|
|
14
|
+
return `grant-${ts}-${suffix}`;
|
|
15
|
+
}
|
|
16
|
+
export function startUatLease(input) {
|
|
17
|
+
const actor = input.actor ?? "operator";
|
|
18
|
+
const origin = mintOperatorOrigin(actor, "deft authz:uat-start", input.now);
|
|
19
|
+
const lease = {
|
|
20
|
+
active: true,
|
|
21
|
+
campaignId: input.campaignId.trim(),
|
|
22
|
+
startedAt: origin.mintedAt,
|
|
23
|
+
startedBy: origin,
|
|
24
|
+
suspendedAt: null,
|
|
25
|
+
note: input.note ?? null,
|
|
26
|
+
};
|
|
27
|
+
if (lease.campaignId.length === 0) {
|
|
28
|
+
throw new Error("campaignId must be non-empty");
|
|
29
|
+
}
|
|
30
|
+
const prev = loadAuthzState(input.projectRoot);
|
|
31
|
+
const state = {
|
|
32
|
+
schemaVersion: 1,
|
|
33
|
+
uat: lease,
|
|
34
|
+
activeGrantIds: prev.activeGrantIds,
|
|
35
|
+
};
|
|
36
|
+
saveAuthzState(input.projectRoot, state);
|
|
37
|
+
return { state, lease };
|
|
38
|
+
}
|
|
39
|
+
export function suspendUatLease(input) {
|
|
40
|
+
const prev = loadAuthzState(input.projectRoot);
|
|
41
|
+
if (prev.uat === null || !prev.uat.active) {
|
|
42
|
+
return prev;
|
|
43
|
+
}
|
|
44
|
+
const state = {
|
|
45
|
+
schemaVersion: 1,
|
|
46
|
+
uat: {
|
|
47
|
+
...prev.uat,
|
|
48
|
+
active: false,
|
|
49
|
+
suspendedAt: utcIso(input.now),
|
|
50
|
+
},
|
|
51
|
+
activeGrantIds: prev.activeGrantIds,
|
|
52
|
+
};
|
|
53
|
+
saveAuthzState(input.projectRoot, state);
|
|
54
|
+
return state;
|
|
55
|
+
}
|
|
56
|
+
export function mintHumanOriginGrant(input) {
|
|
57
|
+
if (input.operations.length === 0) {
|
|
58
|
+
throw new Error("operations must include at least one AuthzOperation");
|
|
59
|
+
}
|
|
60
|
+
const allowed = new Set(AUTHZ_OPERATIONS);
|
|
61
|
+
for (const op of input.operations) {
|
|
62
|
+
if (!allowed.has(op)) {
|
|
63
|
+
throw new Error(`unknown operation: ${op}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const origin = mintOperatorOrigin(input.actor ?? "operator", "deft authz:grant", input.now, input.eventRef ?? null);
|
|
67
|
+
const grant = {
|
|
68
|
+
schemaVersion: 1,
|
|
69
|
+
id: input.grantId ?? newGrantId(input.now),
|
|
70
|
+
origin,
|
|
71
|
+
scope: {
|
|
72
|
+
planRef: input.planRef ?? null,
|
|
73
|
+
repo: input.repo ?? null,
|
|
74
|
+
branch: input.branch ?? null,
|
|
75
|
+
worktree: input.worktree ?? null,
|
|
76
|
+
surfaces: input.surfaces ?? [],
|
|
77
|
+
operations: [...input.operations],
|
|
78
|
+
storyIds: input.storyIds ?? [],
|
|
79
|
+
issueIds: input.issueIds ?? [],
|
|
80
|
+
cohortId: input.cohortId ?? null,
|
|
81
|
+
},
|
|
82
|
+
semantics: {
|
|
83
|
+
expiresAt: input.expiresAt ?? null,
|
|
84
|
+
singleUse: input.singleUse === true,
|
|
85
|
+
usedAt: null,
|
|
86
|
+
revokedAt: null,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
saveGrant(input.projectRoot, grant);
|
|
90
|
+
if (input.pinActive) {
|
|
91
|
+
const prev = loadAuthzState(input.projectRoot);
|
|
92
|
+
const ids = new Set(prev.activeGrantIds);
|
|
93
|
+
ids.add(grant.id);
|
|
94
|
+
saveAuthzState(input.projectRoot, {
|
|
95
|
+
schemaVersion: 1,
|
|
96
|
+
uat: prev.uat,
|
|
97
|
+
activeGrantIds: [...ids],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return grant;
|
|
101
|
+
}
|
|
102
|
+
export function revokeGrant(input) {
|
|
103
|
+
const all = listGrants(input.projectRoot);
|
|
104
|
+
const found = all.find((g) => g.id === input.grantId);
|
|
105
|
+
if (found === undefined)
|
|
106
|
+
return null;
|
|
107
|
+
const revoked = {
|
|
108
|
+
...found,
|
|
109
|
+
semantics: {
|
|
110
|
+
...found.semantics,
|
|
111
|
+
revokedAt: utcIso(input.now),
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
saveGrant(input.projectRoot, revoked);
|
|
115
|
+
return revoked;
|
|
116
|
+
}
|
|
117
|
+
export function showAuthzSnapshot(projectRoot) {
|
|
118
|
+
const state = loadAuthzState(projectRoot);
|
|
119
|
+
return {
|
|
120
|
+
state,
|
|
121
|
+
activeGrants: listActiveHumanGrants(projectRoot, state),
|
|
122
|
+
allGrants: listGrants(projectRoot),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=actions.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map hook tool invocations to authz operation classes (#2944).
|
|
3
|
+
* Reuses #2711 push/merge shell classification; adds PR create/advance detection
|
|
4
|
+
* for UAT denials without re-owning runtimeAuthority Shell matchers.
|
|
5
|
+
*
|
|
6
|
+
* Token walks are O(n) — no nested-quantifier regex on untrusted shell input
|
|
7
|
+
* (CodeQL js/polynomial-redos).
|
|
8
|
+
*/
|
|
9
|
+
import { classifyMcpTool, classifyShellCommand, listShellOps } from "../policy/runtime-authority.js";
|
|
10
|
+
import type { AuthzOperation } from "./types.js";
|
|
11
|
+
export type AuthzClassifiedOp = AuthzOperation | "test" | "evidence" | "unknown";
|
|
12
|
+
/** Best-effort shell classification for UAT-sensitive ops beyond push/merge. */
|
|
13
|
+
export declare function classifyShellAuthzOps(command: string): AuthzClassifiedOp[];
|
|
14
|
+
/** Map a PreToolUse tool name + optional shell command to authz ops. */
|
|
15
|
+
export declare function classifyHookAuthzOps(input: {
|
|
16
|
+
readonly toolName: string;
|
|
17
|
+
readonly shellCommand: string | null;
|
|
18
|
+
readonly isDirectWrite: boolean;
|
|
19
|
+
readonly mcpArgsText?: string | null;
|
|
20
|
+
}): AuthzClassifiedOp[];
|
|
21
|
+
/** Re-export #2711 classifiers for composition docs/tests. */
|
|
22
|
+
export { classifyMcpTool, classifyShellCommand, listShellOps };
|
|
23
|
+
//# sourceMappingURL=classify.d.ts.map
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map hook tool invocations to authz operation classes (#2944).
|
|
3
|
+
* Reuses #2711 push/merge shell classification; adds PR create/advance detection
|
|
4
|
+
* for UAT denials without re-owning runtimeAuthority Shell matchers.
|
|
5
|
+
*
|
|
6
|
+
* Token walks are O(n) — no nested-quantifier regex on untrusted shell input
|
|
7
|
+
* (CodeQL js/polynomial-redos).
|
|
8
|
+
*/
|
|
9
|
+
import { classifyMcpTool, classifyShellCommand, listShellOps, } from "../policy/runtime-authority.js";
|
|
10
|
+
/** Split on whitespace without nested quantifiers (O(n)). */
|
|
11
|
+
function shellTokens(command) {
|
|
12
|
+
const out = [];
|
|
13
|
+
let cur = "";
|
|
14
|
+
for (let i = 0; i < command.length; i++) {
|
|
15
|
+
const c = command[i];
|
|
16
|
+
if (c === undefined)
|
|
17
|
+
break;
|
|
18
|
+
if (c === " " || c === "\t" || c === "\n" || c === "\r") {
|
|
19
|
+
if (cur.length > 0) {
|
|
20
|
+
out.push(cur);
|
|
21
|
+
cur = "";
|
|
22
|
+
}
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
cur += c;
|
|
26
|
+
}
|
|
27
|
+
if (cur.length > 0)
|
|
28
|
+
out.push(cur);
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
function normalizeToken(token) {
|
|
32
|
+
return token.replace(/['"\\]/g, "").toLowerCase();
|
|
33
|
+
}
|
|
34
|
+
function isEnvAssign(token) {
|
|
35
|
+
const eq = token.indexOf("=");
|
|
36
|
+
if (eq <= 0)
|
|
37
|
+
return false;
|
|
38
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(token.slice(0, eq));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Walk tokens looking for `gh [opts] <resource> <verb>` patterns (O(n)).
|
|
42
|
+
* Returns the resource+verb pair when found.
|
|
43
|
+
*/
|
|
44
|
+
/** gh global flags that take a separate value token. */
|
|
45
|
+
const GH_VALUE_FLAGS = new Set(["-R", "--repo", "-a", "--app", "-h", "--hostname", "-p", "--jq"]);
|
|
46
|
+
function findGhResourceVerb(tokens) {
|
|
47
|
+
let i = 0;
|
|
48
|
+
while (i < tokens.length && isEnvAssign(tokens[i]))
|
|
49
|
+
i++;
|
|
50
|
+
const wrap = tokens[i] !== undefined ? normalizeToken(tokens[i]) : "";
|
|
51
|
+
if (wrap === "sudo" || wrap === "env" || wrap === "command") {
|
|
52
|
+
i++;
|
|
53
|
+
while (i < tokens.length && isEnvAssign(tokens[i]))
|
|
54
|
+
i++;
|
|
55
|
+
}
|
|
56
|
+
const bin = tokens[i] !== undefined ? normalizeToken(tokens[i]) : "";
|
|
57
|
+
if (bin !== "gh" && bin !== "gh.exe")
|
|
58
|
+
return null;
|
|
59
|
+
i++;
|
|
60
|
+
while (i < tokens.length) {
|
|
61
|
+
const t = tokens[i];
|
|
62
|
+
if (t === undefined)
|
|
63
|
+
break;
|
|
64
|
+
const n = normalizeToken(t);
|
|
65
|
+
if (!n.startsWith("-"))
|
|
66
|
+
break;
|
|
67
|
+
// --flag=value form consumes one token.
|
|
68
|
+
if (n.startsWith("--") && n.includes("=")) {
|
|
69
|
+
i++;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// Value-taking short/long flags: -R owner/repo, --repo owner/repo
|
|
73
|
+
if (GH_VALUE_FLAGS.has(t) || GH_VALUE_FLAGS.has(n)) {
|
|
74
|
+
i += 2;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
const resource = tokens[i] !== undefined ? normalizeToken(tokens[i]) : "";
|
|
80
|
+
const verb = tokens[i + 1] !== undefined ? normalizeToken(tokens[i + 1]) : "";
|
|
81
|
+
if (resource.length === 0 || verb.length === 0)
|
|
82
|
+
return null;
|
|
83
|
+
return { resource, verb };
|
|
84
|
+
}
|
|
85
|
+
const TEST_BINS = new Set(["vitest", "pytest", "cargo", "npm", "pnpm", "yarn", "task"]);
|
|
86
|
+
const TEST_SECOND = new Set(["test"]);
|
|
87
|
+
const DEPLOY_PAIRS = [
|
|
88
|
+
["terraform", "apply"],
|
|
89
|
+
["helm", "upgrade"],
|
|
90
|
+
["kubectl", "apply"],
|
|
91
|
+
["fly", "deploy"],
|
|
92
|
+
["vercel", "deploy"],
|
|
93
|
+
];
|
|
94
|
+
function hasGoTest(tokens) {
|
|
95
|
+
for (let i = 0; i < tokens.length - 1; i++) {
|
|
96
|
+
if (normalizeToken(tokens[i]) === "go" &&
|
|
97
|
+
normalizeToken(tokens[i + 1]) === "test") {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
function hasTestRunner(tokens) {
|
|
104
|
+
if (hasGoTest(tokens))
|
|
105
|
+
return true;
|
|
106
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
107
|
+
const t = normalizeToken(tokens[i]);
|
|
108
|
+
if (TEST_BINS.has(t)) {
|
|
109
|
+
// vitest/pytest alone, or npm/pnpm/yarn/task/cargo + test
|
|
110
|
+
if (t === "vitest" || t === "pytest")
|
|
111
|
+
return true;
|
|
112
|
+
const next = tokens[i + 1] !== undefined ? normalizeToken(tokens[i + 1]) : "";
|
|
113
|
+
if (TEST_SECOND.has(next))
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
function hasDeploy(tokens) {
|
|
120
|
+
for (let i = 0; i < tokens.length - 1; i++) {
|
|
121
|
+
const a = normalizeToken(tokens[i]);
|
|
122
|
+
const b = normalizeToken(tokens[i + 1]);
|
|
123
|
+
for (const [bin, verb] of DEPLOY_PAIRS) {
|
|
124
|
+
if (a === bin && b === verb)
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
function hasGhApiPath(tokens, needle) {
|
|
131
|
+
let sawGh = false;
|
|
132
|
+
let sawApi = false;
|
|
133
|
+
for (const raw of tokens) {
|
|
134
|
+
const t = normalizeToken(raw);
|
|
135
|
+
if (t === "gh" || t === "gh.exe") {
|
|
136
|
+
sawGh = true;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (sawGh && t === "api") {
|
|
140
|
+
sawApi = true;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (sawApi && t.includes(needle))
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
/** Best-effort shell classification for UAT-sensitive ops beyond push/merge. */
|
|
149
|
+
export function classifyShellAuthzOps(command) {
|
|
150
|
+
const cmd = command.trim();
|
|
151
|
+
if (cmd.length === 0)
|
|
152
|
+
return [];
|
|
153
|
+
const found = new Set();
|
|
154
|
+
for (const op of listShellOps(cmd)) {
|
|
155
|
+
found.add(op);
|
|
156
|
+
}
|
|
157
|
+
const tokens = shellTokens(cmd);
|
|
158
|
+
const gh = findGhResourceVerb(tokens);
|
|
159
|
+
if (gh !== null) {
|
|
160
|
+
if (gh.resource === "pr" &&
|
|
161
|
+
(gh.verb === "create" || gh.verb === "edit" || gh.verb === "ready" || gh.verb === "reopen")) {
|
|
162
|
+
found.add("pr");
|
|
163
|
+
}
|
|
164
|
+
// Surface merge when listShellOps misses global flags like --repo (#2711 compose).
|
|
165
|
+
if (gh.resource === "pr" && gh.verb === "merge") {
|
|
166
|
+
found.add("merge");
|
|
167
|
+
}
|
|
168
|
+
if (gh.resource === "issue" && gh.verb === "create") {
|
|
169
|
+
found.add("issue_mutation");
|
|
170
|
+
}
|
|
171
|
+
if (gh.resource === "repo" && gh.verb === "edit") {
|
|
172
|
+
found.add("settings");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (hasGhApiPath(tokens, "/issues"))
|
|
176
|
+
found.add("issue_mutation");
|
|
177
|
+
if (hasGhApiPath(tokens, "/settings"))
|
|
178
|
+
found.add("settings");
|
|
179
|
+
if (hasTestRunner(tokens))
|
|
180
|
+
found.add("test");
|
|
181
|
+
if (hasDeploy(tokens))
|
|
182
|
+
found.add("deployment");
|
|
183
|
+
return [...found];
|
|
184
|
+
}
|
|
185
|
+
/** Map a PreToolUse tool name + optional shell command to authz ops. */
|
|
186
|
+
export function classifyHookAuthzOps(input) {
|
|
187
|
+
const { toolName, shellCommand, isDirectWrite } = input;
|
|
188
|
+
if (isDirectWrite)
|
|
189
|
+
return ["edit"];
|
|
190
|
+
const lower = toolName.toLowerCase();
|
|
191
|
+
if (lower.includes("bash") || lower.includes("shell") || lower === "run_terminal_cmd") {
|
|
192
|
+
// Missing command string: fail open (host gap) — same posture as #2711.
|
|
193
|
+
if (shellCommand === null)
|
|
194
|
+
return [];
|
|
195
|
+
// Empty classification (git status, tests without product verbs, …) is not gated.
|
|
196
|
+
return classifyShellAuthzOps(shellCommand);
|
|
197
|
+
}
|
|
198
|
+
// MCP / bare names via #2711 classifier + PR heuristics (token-ish name checks).
|
|
199
|
+
const mcpOp = classifyMcpTool(toolName, input.mcpArgsText ?? null);
|
|
200
|
+
if (mcpOp !== null)
|
|
201
|
+
return [mcpOp];
|
|
202
|
+
const name = lower.replace(/[^a-z0-9_]/g, "_");
|
|
203
|
+
if (name.includes("create_pull_request") ||
|
|
204
|
+
name.includes("pull_request_create") ||
|
|
205
|
+
name.includes("pr_create") ||
|
|
206
|
+
name === "create_pull_request") {
|
|
207
|
+
return ["pr"];
|
|
208
|
+
}
|
|
209
|
+
if (name.includes("create_issue") || name.includes("issue_create")) {
|
|
210
|
+
return ["issue_mutation"];
|
|
211
|
+
}
|
|
212
|
+
// Unrelated tools — not gated by Wave 1 authz (prefer fail-open over false deny).
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
/** Re-export #2711 classifiers for composition docs/tests. */
|
|
216
|
+
export { classifyMcpTool, classifyShellCommand, listShellOps };
|
|
217
|
+
//# sourceMappingURL=classify.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Closed-verb evaluator for release-class high-blast verbs (#1095 Wave 4 / #2948).
|
|
3
|
+
*
|
|
4
|
+
* Allow only when:
|
|
5
|
+
* (a) DEFT_ALLOW_<VERB>=1 ephemeral env bypass, OR
|
|
6
|
+
* (b) a matching human-origin grant whose operations cover the verb and
|
|
7
|
+
* surfaces cover the target (empty surfaces = any target).
|
|
8
|
+
*
|
|
9
|
+
* Agent-authored / rejected-origin grants never satisfy. Does not invent a
|
|
10
|
+
* second mint path — grants come from Wave 1 mintHumanOriginGrant / authz:grant.
|
|
11
|
+
*/
|
|
12
|
+
import type { ClosedVerbDecision, HumanOriginGrant } from "./types.js";
|
|
13
|
+
import { type VerbClassificationTable } from "./verb-classification.js";
|
|
14
|
+
export type EnvMap = Readonly<Record<string, string | undefined>>;
|
|
15
|
+
export interface EvaluateClosedVerbInput {
|
|
16
|
+
readonly verb: string;
|
|
17
|
+
/** Release version / tag / target id (e.g. 0.30.0 or v0.30.0). */
|
|
18
|
+
readonly target: string | null;
|
|
19
|
+
readonly grants: readonly HumanOriginGrant[];
|
|
20
|
+
/** Env map; defaults to process.env when omitted. */
|
|
21
|
+
readonly env?: EnvMap;
|
|
22
|
+
readonly now?: Date;
|
|
23
|
+
/** Injected table for unit tests; otherwise load from projectRoot or builtin. */
|
|
24
|
+
readonly classification?: VerbClassificationTable;
|
|
25
|
+
/** Project root — loads conventions/verb-classification.json when set. */
|
|
26
|
+
readonly projectRoot?: string | null;
|
|
27
|
+
/** Optional structural context — when grant pins repo/branch, must match. */
|
|
28
|
+
readonly repo?: string | null;
|
|
29
|
+
readonly branch?: string | null;
|
|
30
|
+
}
|
|
31
|
+
/** Normalise version/target for surface matching (strip leading v, lower-case). */
|
|
32
|
+
export declare function normaliseClosedVerbTarget(target: string | null | undefined): string | null;
|
|
33
|
+
/** Surface forms that match a target (version, v-version, raw). */
|
|
34
|
+
export declare function targetSurfaceCandidates(target: string | null): string[];
|
|
35
|
+
/**
|
|
36
|
+
* Closed-verb gate. Fail closed for unknown verbs and missing grants.
|
|
37
|
+
* Loads conventions/verb-classification.json when projectRoot is provided.
|
|
38
|
+
*/
|
|
39
|
+
export declare function evaluateClosedVerb(input: EvaluateClosedVerbInput): ClosedVerbDecision;
|
|
40
|
+
/** Map verb name → DEFT_ALLOW_* key (from loaded/builtin table). */
|
|
41
|
+
export declare function closedVerbEnvBypassKey(verb: string, projectRoot?: string | null): string | null;
|
|
42
|
+
//# sourceMappingURL=closed-verb.d.ts.map
|