@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,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-origin provenance predicates (#2944).
|
|
3
|
+
* Self-authored lifecycle / dispatch / xBRIEF state never counts as approval.
|
|
4
|
+
*/
|
|
5
|
+
import { HUMAN_ORIGIN_KINDS, REJECTED_ORIGIN_KINDS, } from "./types.js";
|
|
6
|
+
const HUMAN_SET = new Set(HUMAN_ORIGIN_KINDS);
|
|
7
|
+
const REJECTED_SET = new Set(REJECTED_ORIGIN_KINDS);
|
|
8
|
+
/** True when origin.kind is an accepted human-origin kind. */
|
|
9
|
+
export function isHumanOriginKind(kind) {
|
|
10
|
+
if (kind === null || kind === undefined)
|
|
11
|
+
return false;
|
|
12
|
+
return HUMAN_SET.has(kind.trim().toLowerCase());
|
|
13
|
+
}
|
|
14
|
+
/** True when origin.kind is explicitly agent/self-authored (always reject). */
|
|
15
|
+
export function isRejectedOriginKind(kind) {
|
|
16
|
+
if (kind === null || kind === undefined)
|
|
17
|
+
return false;
|
|
18
|
+
return REJECTED_SET.has(kind.trim().toLowerCase());
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Structural human-origin check for a grant record.
|
|
22
|
+
* Rejects missing kind, rejected kinds, empty actor, and agent-like actors.
|
|
23
|
+
*/
|
|
24
|
+
export function isHumanOriginGrant(grant) {
|
|
25
|
+
if (grant === null || grant === undefined)
|
|
26
|
+
return false;
|
|
27
|
+
return isHumanOrigin(grant.origin);
|
|
28
|
+
}
|
|
29
|
+
export function isHumanOrigin(origin) {
|
|
30
|
+
if (origin === null || origin === undefined)
|
|
31
|
+
return false;
|
|
32
|
+
const kind = (origin.kind ?? "").trim().toLowerCase();
|
|
33
|
+
if (kind.length === 0)
|
|
34
|
+
return false;
|
|
35
|
+
if (isRejectedOriginKind(kind))
|
|
36
|
+
return false;
|
|
37
|
+
if (!isHumanOriginKind(kind))
|
|
38
|
+
return false;
|
|
39
|
+
const actor = (origin.actor ?? "").trim().toLowerCase();
|
|
40
|
+
if (actor.length === 0)
|
|
41
|
+
return false;
|
|
42
|
+
// Agent-shaped actor strings are not human provenance even with a human kind stamp.
|
|
43
|
+
if (actor === "agent" || actor.startsWith("agent:") || actor === "self")
|
|
44
|
+
return false;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Reject evidence shapes agents commonly invent as consent:
|
|
49
|
+
* allocation_context free-text, xBRIEF status, lifecycle flags, dispatch envelopes.
|
|
50
|
+
* These never independently satisfy an implementation-approval gate.
|
|
51
|
+
*/
|
|
52
|
+
export function evidenceSatisfiesImplementationApproval(evidence) {
|
|
53
|
+
// Only a validated human-origin grant counts.
|
|
54
|
+
if (evidence.grant !== undefined && evidence.grant !== null) {
|
|
55
|
+
return isHumanOriginGrant(evidence.grant);
|
|
56
|
+
}
|
|
57
|
+
// Explicit non-grant evidence channels always fail closed for implement gates.
|
|
58
|
+
void evidence.xbriefStatus;
|
|
59
|
+
void evidence.allocationContext;
|
|
60
|
+
void evidence.dispatchEnvelope;
|
|
61
|
+
void evidence.lifecycleAdvancedBy;
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=origin.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function authzDir(projectRoot: string): string;
|
|
2
|
+
export declare function authzStatePath(projectRoot: string): string;
|
|
3
|
+
export declare function authzGrantsDir(projectRoot: string): string;
|
|
4
|
+
export declare function authzGrantPath(projectRoot: string, grantId: string): string;
|
|
5
|
+
export declare function authzAuditPath(projectRoot: string): string;
|
|
6
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { AUTHZ_AUDIT_FILE, AUTHZ_DIR, AUTHZ_GRANTS_DIR, AUTHZ_STATE_FILE } from "./types.js";
|
|
3
|
+
export function authzDir(projectRoot) {
|
|
4
|
+
return join(projectRoot, ...AUTHZ_DIR.split("/"));
|
|
5
|
+
}
|
|
6
|
+
export function authzStatePath(projectRoot) {
|
|
7
|
+
return join(authzDir(projectRoot), AUTHZ_STATE_FILE);
|
|
8
|
+
}
|
|
9
|
+
export function authzGrantsDir(projectRoot) {
|
|
10
|
+
return join(authzDir(projectRoot), AUTHZ_GRANTS_DIR);
|
|
11
|
+
}
|
|
12
|
+
export function authzGrantPath(projectRoot, grantId) {
|
|
13
|
+
const safe = grantId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
14
|
+
return join(authzGrantsDir(projectRoot), `${safe}.json`);
|
|
15
|
+
}
|
|
16
|
+
export function authzAuditPath(projectRoot) {
|
|
17
|
+
return join(authzDir(projectRoot), AUTHZ_AUDIT_FILE);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk store for authz state + grants under `.deft/authz/` (#2944).
|
|
3
|
+
*/
|
|
4
|
+
import { type AuthzAuditRecord, type AuthzState, type GrantOrigin, type HumanOriginGrant, type UatLease } from "./types.js";
|
|
5
|
+
declare function utcIso(now?: Date): string;
|
|
6
|
+
/** Parse a grant JSON object; returns null when structurally unusable. */
|
|
7
|
+
export declare function parseGrant(raw: unknown): HumanOriginGrant | null;
|
|
8
|
+
export declare function parseUatLease(raw: unknown): UatLease | null;
|
|
9
|
+
export declare function parseAuthzState(raw: unknown): AuthzState;
|
|
10
|
+
/**
|
|
11
|
+
* Load result distinguishes missing state (inactive) from corrupt state
|
|
12
|
+
* (fail-closed deny-all under UAT posture — #2944 Greptile/SLizard).
|
|
13
|
+
*/
|
|
14
|
+
export type AuthzStateLoad = {
|
|
15
|
+
readonly ok: true;
|
|
16
|
+
readonly state: AuthzState;
|
|
17
|
+
readonly corrupt: false;
|
|
18
|
+
} | {
|
|
19
|
+
readonly ok: false;
|
|
20
|
+
readonly state: AuthzState;
|
|
21
|
+
readonly corrupt: true;
|
|
22
|
+
readonly reason: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function loadAuthzStateResult(projectRoot: string): AuthzStateLoad;
|
|
25
|
+
export declare function loadAuthzState(projectRoot: string): AuthzState;
|
|
26
|
+
/** Persist single-use grant consumption after an allow decision (#2944). */
|
|
27
|
+
export declare function markGrantUsed(projectRoot: string, grantId: string, now?: Date): HumanOriginGrant | null;
|
|
28
|
+
export declare function saveAuthzState(projectRoot: string, state: AuthzState): void;
|
|
29
|
+
export declare function loadGrant(projectRoot: string, grantId: string): HumanOriginGrant | null;
|
|
30
|
+
export declare function saveGrant(projectRoot: string, grant: HumanOriginGrant): void;
|
|
31
|
+
export declare function listGrants(projectRoot: string): HumanOriginGrant[];
|
|
32
|
+
/**
|
|
33
|
+
* Active grants: non-revoked, optionally filtered by state.activeGrantIds,
|
|
34
|
+
* human-origin only (self-authored records stay on disk but do not activate).
|
|
35
|
+
*/
|
|
36
|
+
export declare function listActiveHumanGrants(projectRoot: string, state?: AuthzState, now?: Date): HumanOriginGrant[];
|
|
37
|
+
export declare function appendAuthzAudit(projectRoot: string, record: AuthzAuditRecord): void;
|
|
38
|
+
export declare function mintOperatorOrigin(actor: string, mintedVia: string, now?: Date, eventRef?: string | null): GrantOrigin;
|
|
39
|
+
export { utcIso };
|
|
40
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk store for authz state + grants under `.deft/authz/` (#2944).
|
|
3
|
+
*/
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import { existsSync, readdirSync, readFileSync, renameSync, rmSync } from "node:fs";
|
|
6
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
7
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
8
|
+
import { assertWriteTargetSafe } from "../fs/projection-containment.js";
|
|
9
|
+
import { isHumanOrigin } from "./origin.js";
|
|
10
|
+
import { authzAuditPath, authzGrantPath, authzGrantsDir, authzStatePath } from "./paths.js";
|
|
11
|
+
import { AUTHZ_OPERATIONS, } from "./types.js";
|
|
12
|
+
function utcIso(now) {
|
|
13
|
+
const dt = now ?? new Date();
|
|
14
|
+
return dt.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
15
|
+
}
|
|
16
|
+
function record(value) {
|
|
17
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
18
|
+
? value
|
|
19
|
+
: null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Contained atomic JSON write for authz state/grants (#2980 / Greptile P1).
|
|
23
|
+
* Containment root is projectRoot (not dirname(target)) so parent-symlink escape fails closed.
|
|
24
|
+
* Unique random temp names avoid PID-reuse collisions; rename is the atomic publish step.
|
|
25
|
+
*/
|
|
26
|
+
function writeJsonContained(projectRoot, targetPath, payload) {
|
|
27
|
+
const root = resolve(projectRoot);
|
|
28
|
+
const abs = resolve(targetPath);
|
|
29
|
+
// Refuse leaf/parent symlinks on the final path before temp+rename publish.
|
|
30
|
+
assertWriteTargetSafe(root, abs);
|
|
31
|
+
const dir = dirname(abs);
|
|
32
|
+
const tmpBase = `.${basename(abs)}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
33
|
+
const tmp = join(dir, tmpBase);
|
|
34
|
+
try {
|
|
35
|
+
containedWrite({
|
|
36
|
+
root,
|
|
37
|
+
target: tmp,
|
|
38
|
+
data: `${JSON.stringify(payload, null, 2)}\n`,
|
|
39
|
+
mode: "create",
|
|
40
|
+
});
|
|
41
|
+
renameSync(tmp, abs);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
try {
|
|
45
|
+
rmSync(tmp, { force: true });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
/* best-effort cleanup */
|
|
49
|
+
}
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function readString(rec, key) {
|
|
54
|
+
const v = rec[key];
|
|
55
|
+
return typeof v === "string" && v.trim().length > 0 ? v.trim() : null;
|
|
56
|
+
}
|
|
57
|
+
function readStringArray(raw) {
|
|
58
|
+
if (!Array.isArray(raw))
|
|
59
|
+
return [];
|
|
60
|
+
return raw.filter((x) => typeof x === "string" && x.trim().length > 0);
|
|
61
|
+
}
|
|
62
|
+
function readNumberArray(raw) {
|
|
63
|
+
if (!Array.isArray(raw))
|
|
64
|
+
return [];
|
|
65
|
+
return raw.filter((x) => typeof x === "number" && Number.isFinite(x));
|
|
66
|
+
}
|
|
67
|
+
function parseOrigin(raw) {
|
|
68
|
+
const rec = record(raw);
|
|
69
|
+
if (rec === null)
|
|
70
|
+
return null;
|
|
71
|
+
const kind = readString(rec, "kind");
|
|
72
|
+
const actor = readString(rec, "actor");
|
|
73
|
+
const mintedAt = readString(rec, "mintedAt") ?? readString(rec, "minted_at");
|
|
74
|
+
const mintedVia = readString(rec, "mintedVia") ?? readString(rec, "minted_via") ?? "unknown";
|
|
75
|
+
if (kind === null || actor === null || mintedAt === null)
|
|
76
|
+
return null;
|
|
77
|
+
const eventRef = readString(rec, "eventRef") ?? readString(rec, "event_ref");
|
|
78
|
+
return { kind, actor, mintedAt, mintedVia, eventRef };
|
|
79
|
+
}
|
|
80
|
+
function parseOperations(raw) {
|
|
81
|
+
if (!Array.isArray(raw))
|
|
82
|
+
return [];
|
|
83
|
+
const allowed = new Set(AUTHZ_OPERATIONS);
|
|
84
|
+
const out = [];
|
|
85
|
+
for (const item of raw) {
|
|
86
|
+
if (typeof item !== "string")
|
|
87
|
+
continue;
|
|
88
|
+
const op = item.trim().toLowerCase();
|
|
89
|
+
if (allowed.has(op))
|
|
90
|
+
out.push(op);
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
function parseScope(raw) {
|
|
95
|
+
const rec = record(raw);
|
|
96
|
+
if (rec === null)
|
|
97
|
+
return null;
|
|
98
|
+
return {
|
|
99
|
+
planRef: readString(rec, "planRef") ?? readString(rec, "plan_ref") ?? readString(rec, "planHash"),
|
|
100
|
+
repo: readString(rec, "repo"),
|
|
101
|
+
branch: readString(rec, "branch"),
|
|
102
|
+
worktree: readString(rec, "worktree"),
|
|
103
|
+
surfaces: readStringArray(rec.surfaces),
|
|
104
|
+
operations: parseOperations(rec.operations),
|
|
105
|
+
storyIds: readStringArray(rec.storyIds ?? rec.story_ids),
|
|
106
|
+
issueIds: readNumberArray(rec.issueIds ?? rec.issue_ids),
|
|
107
|
+
cohortId: readString(rec, "cohortId") ?? readString(rec, "cohort_id"),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function parseSemantics(raw) {
|
|
111
|
+
const rec = record(raw);
|
|
112
|
+
if (rec === null) {
|
|
113
|
+
return { expiresAt: null, singleUse: false, usedAt: null, revokedAt: null };
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
expiresAt: readString(rec, "expiresAt") ?? readString(rec, "expires_at"),
|
|
117
|
+
singleUse: rec.singleUse === true || rec.single_use === true,
|
|
118
|
+
usedAt: readString(rec, "usedAt") ?? readString(rec, "used_at"),
|
|
119
|
+
revokedAt: readString(rec, "revokedAt") ?? readString(rec, "revoked_at"),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/** Parse a grant JSON object; returns null when structurally unusable. */
|
|
123
|
+
export function parseGrant(raw) {
|
|
124
|
+
const rec = record(raw);
|
|
125
|
+
if (rec === null)
|
|
126
|
+
return null;
|
|
127
|
+
const id = readString(rec, "id");
|
|
128
|
+
const origin = parseOrigin(rec.origin);
|
|
129
|
+
const scope = parseScope(rec.scope);
|
|
130
|
+
if (id === null || origin === null || scope === null)
|
|
131
|
+
return null;
|
|
132
|
+
return {
|
|
133
|
+
schemaVersion: 1,
|
|
134
|
+
id,
|
|
135
|
+
origin,
|
|
136
|
+
scope,
|
|
137
|
+
semantics: parseSemantics(rec.semantics),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export function parseUatLease(raw) {
|
|
141
|
+
const rec = record(raw);
|
|
142
|
+
if (rec === null)
|
|
143
|
+
return null;
|
|
144
|
+
const campaignId = readString(rec, "campaignId") ?? readString(rec, "campaign_id");
|
|
145
|
+
const startedAt = readString(rec, "startedAt") ?? readString(rec, "started_at");
|
|
146
|
+
const startedBy = parseOrigin(rec.startedBy ?? rec.started_by);
|
|
147
|
+
if (campaignId === null || startedAt === null || startedBy === null)
|
|
148
|
+
return null;
|
|
149
|
+
const active = rec.active === true;
|
|
150
|
+
return {
|
|
151
|
+
active,
|
|
152
|
+
campaignId,
|
|
153
|
+
startedAt,
|
|
154
|
+
startedBy,
|
|
155
|
+
suspendedAt: readString(rec, "suspendedAt") ?? readString(rec, "suspended_at"),
|
|
156
|
+
note: readString(rec, "note"),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
export function parseAuthzState(raw) {
|
|
160
|
+
const rec = record(raw);
|
|
161
|
+
if (rec === null) {
|
|
162
|
+
return { schemaVersion: 1, uat: null, activeGrantIds: [] };
|
|
163
|
+
}
|
|
164
|
+
const uat = "uat" in rec ? parseUatLease(rec.uat) : null;
|
|
165
|
+
return {
|
|
166
|
+
schemaVersion: 1,
|
|
167
|
+
uat,
|
|
168
|
+
activeGrantIds: readStringArray(rec.activeGrantIds ?? rec.active_grant_ids),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/** Synthetic fail-closed state: active UAT with no human origin (evaluate treats corrupt separately). */
|
|
172
|
+
function corruptFailClosedState() {
|
|
173
|
+
return {
|
|
174
|
+
schemaVersion: 1,
|
|
175
|
+
uat: {
|
|
176
|
+
active: true,
|
|
177
|
+
campaignId: "__corrupt_authz_state__",
|
|
178
|
+
startedAt: "1970-01-01T00:00:00Z",
|
|
179
|
+
startedBy: {
|
|
180
|
+
kind: "operator-cli",
|
|
181
|
+
actor: "system",
|
|
182
|
+
mintedAt: "1970-01-01T00:00:00Z",
|
|
183
|
+
mintedVia: "corrupt-state-fail-closed",
|
|
184
|
+
eventRef: null,
|
|
185
|
+
},
|
|
186
|
+
suspendedAt: null,
|
|
187
|
+
note: "authz state unreadable — fail closed",
|
|
188
|
+
},
|
|
189
|
+
activeGrantIds: [],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
export function loadAuthzStateResult(projectRoot) {
|
|
193
|
+
const path = authzStatePath(projectRoot);
|
|
194
|
+
if (!existsSync(path)) {
|
|
195
|
+
return {
|
|
196
|
+
ok: true,
|
|
197
|
+
corrupt: false,
|
|
198
|
+
state: { schemaVersion: 1, uat: null, activeGrantIds: [] },
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
203
|
+
return { ok: true, corrupt: false, state: parseAuthzState(raw) };
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
return {
|
|
207
|
+
ok: false,
|
|
208
|
+
corrupt: true,
|
|
209
|
+
reason: `authz state unreadable at ${path}: ${String(err)}`,
|
|
210
|
+
state: corruptFailClosedState(),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
export function loadAuthzState(projectRoot) {
|
|
215
|
+
return loadAuthzStateResult(projectRoot).state;
|
|
216
|
+
}
|
|
217
|
+
/** Persist single-use grant consumption after an allow decision (#2944). */
|
|
218
|
+
export function markGrantUsed(projectRoot, grantId, now = new Date()) {
|
|
219
|
+
const grant = loadGrant(projectRoot, grantId);
|
|
220
|
+
if (grant === null)
|
|
221
|
+
return null;
|
|
222
|
+
if (!grant.semantics.singleUse)
|
|
223
|
+
return grant;
|
|
224
|
+
if (grant.semantics.usedAt !== null)
|
|
225
|
+
return grant;
|
|
226
|
+
const used = {
|
|
227
|
+
...grant,
|
|
228
|
+
semantics: {
|
|
229
|
+
...grant.semantics,
|
|
230
|
+
usedAt: utcIso(now),
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
saveGrant(projectRoot, used);
|
|
234
|
+
return used;
|
|
235
|
+
}
|
|
236
|
+
export function saveAuthzState(projectRoot, state) {
|
|
237
|
+
writeJsonContained(projectRoot, authzStatePath(projectRoot), state);
|
|
238
|
+
}
|
|
239
|
+
export function loadGrant(projectRoot, grantId) {
|
|
240
|
+
const path = authzGrantPath(projectRoot, grantId);
|
|
241
|
+
if (!existsSync(path))
|
|
242
|
+
return null;
|
|
243
|
+
try {
|
|
244
|
+
return parseGrant(JSON.parse(readFileSync(path, "utf8")));
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
export function saveGrant(projectRoot, grant) {
|
|
251
|
+
writeJsonContained(projectRoot, authzGrantPath(projectRoot, grant.id), grant);
|
|
252
|
+
}
|
|
253
|
+
export function listGrants(projectRoot) {
|
|
254
|
+
const dir = authzGrantsDir(projectRoot);
|
|
255
|
+
if (!existsSync(dir))
|
|
256
|
+
return [];
|
|
257
|
+
const out = [];
|
|
258
|
+
for (const name of readdirSync(dir)) {
|
|
259
|
+
if (!name.endsWith(".json"))
|
|
260
|
+
continue;
|
|
261
|
+
try {
|
|
262
|
+
const grant = parseGrant(JSON.parse(readFileSync(join(dir, name), "utf8")));
|
|
263
|
+
if (grant !== null)
|
|
264
|
+
out.push(grant);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// skip corrupt grant files
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return out;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Active grants: non-revoked, optionally filtered by state.activeGrantIds,
|
|
274
|
+
* human-origin only (self-authored records stay on disk but do not activate).
|
|
275
|
+
*/
|
|
276
|
+
export function listActiveHumanGrants(projectRoot, state = loadAuthzState(projectRoot), now = new Date()) {
|
|
277
|
+
const all = listGrants(projectRoot);
|
|
278
|
+
const pin = state.activeGrantIds;
|
|
279
|
+
const pinSet = pin.length > 0 ? new Set(pin) : null;
|
|
280
|
+
const nowMs = now.getTime();
|
|
281
|
+
return all.filter((g) => {
|
|
282
|
+
if (pinSet !== null && !pinSet.has(g.id))
|
|
283
|
+
return false;
|
|
284
|
+
if (g.semantics.revokedAt !== null)
|
|
285
|
+
return false;
|
|
286
|
+
if (g.semantics.singleUse && g.semantics.usedAt !== null)
|
|
287
|
+
return false;
|
|
288
|
+
if (g.semantics.expiresAt !== null) {
|
|
289
|
+
const exp = Date.parse(g.semantics.expiresAt);
|
|
290
|
+
if (!Number.isNaN(exp) && exp <= nowMs)
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
return isHumanOrigin(g.origin);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
export function appendAuthzAudit(projectRoot, record) {
|
|
297
|
+
const root = resolve(projectRoot);
|
|
298
|
+
const path = authzAuditPath(projectRoot);
|
|
299
|
+
// #2980 wave B: product write sink routes through containedWrite.
|
|
300
|
+
containedWrite({
|
|
301
|
+
root,
|
|
302
|
+
target: path,
|
|
303
|
+
data: `${JSON.stringify(record)}\n`,
|
|
304
|
+
mode: "append",
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
export function mintOperatorOrigin(actor, mintedVia, now, eventRef = null) {
|
|
308
|
+
return {
|
|
309
|
+
kind: "operator-cli",
|
|
310
|
+
actor: actor.trim().length > 0 ? actor.trim() : "operator",
|
|
311
|
+
mintedAt: utcIso(now),
|
|
312
|
+
mintedVia,
|
|
313
|
+
eventRef,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
export { utcIso };
|
|
317
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1,139 @@
|
|
|
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 type { AuthzOperation, HumanOriginGrant } from "./types.js";
|
|
11
|
+
import { type VerbClassificationTable } from "./verb-classification.js";
|
|
12
|
+
/** Canonical AFK template names for Wave 4 release-class closed verbs. */
|
|
13
|
+
export declare const CLOSED_VERB_TEMPLATE_NAMES: readonly ["release-cut", "release-publish", "release-rollback"];
|
|
14
|
+
export type ClosedVerbTemplateName = (typeof CLOSED_VERB_TEMPLATE_NAMES)[number];
|
|
15
|
+
/** Walk-away finish-loop template (#871 Wave 5) — not a release closed-verb. */
|
|
16
|
+
export declare const FINISH_LOOP_TEMPLATE_NAME: "finish-loop";
|
|
17
|
+
/** All AFK templates accepted by `authz:grant --template`. */
|
|
18
|
+
export declare const AFK_TEMPLATE_NAMES: readonly ["release-cut", "release-publish", "release-rollback", "finish-loop"];
|
|
19
|
+
export type AfkTemplateName = (typeof AFK_TEMPLATE_NAMES)[number];
|
|
20
|
+
/**
|
|
21
|
+
* AuthzOperation set covered by the finish-loop template.
|
|
22
|
+
* Release-class ops are explicitly carved out (operator must mint a release-* grant).
|
|
23
|
+
*/
|
|
24
|
+
export declare const FINISH_LOOP_OPERATIONS: readonly AuthzOperation[];
|
|
25
|
+
/** Default AFK window for finish-loop (matches issue #871 walk-away notes). */
|
|
26
|
+
export declare const FINISH_LOOP_DEFAULT_EXPIRY_HOURS = 8;
|
|
27
|
+
export declare function isClosedVerbTemplateName(name: string): name is ClosedVerbTemplateName;
|
|
28
|
+
export declare function isFinishLoopTemplateName(name: string): boolean;
|
|
29
|
+
export declare function isAfkTemplateName(name: string): name is AfkTemplateName;
|
|
30
|
+
export interface MintClosedVerbTemplateInput {
|
|
31
|
+
readonly projectRoot: string;
|
|
32
|
+
readonly template: string;
|
|
33
|
+
/** Version / tag target the template binds into grant.surfaces. */
|
|
34
|
+
readonly target: string;
|
|
35
|
+
readonly actor?: string;
|
|
36
|
+
readonly expiresAt?: string | null;
|
|
37
|
+
readonly singleUse?: boolean;
|
|
38
|
+
readonly planRef?: string | null;
|
|
39
|
+
readonly repo?: string | null;
|
|
40
|
+
readonly branch?: string | null;
|
|
41
|
+
readonly now?: Date;
|
|
42
|
+
readonly classification?: VerbClassificationTable;
|
|
43
|
+
/** Optional extra mint fields forwarded to mintHumanOriginGrant. */
|
|
44
|
+
readonly pinActive?: boolean;
|
|
45
|
+
readonly eventRef?: string | null;
|
|
46
|
+
}
|
|
47
|
+
export interface MintFinishLoopTemplateInput {
|
|
48
|
+
readonly projectRoot: string;
|
|
49
|
+
readonly actor?: string;
|
|
50
|
+
readonly expiresAt?: string | null;
|
|
51
|
+
readonly singleUse?: boolean;
|
|
52
|
+
readonly planRef?: string | null;
|
|
53
|
+
readonly repo?: string | null;
|
|
54
|
+
readonly branch?: string | null;
|
|
55
|
+
readonly surfaces?: readonly string[];
|
|
56
|
+
readonly storyIds?: readonly string[];
|
|
57
|
+
readonly issueIds?: readonly number[];
|
|
58
|
+
readonly cohortId?: string | null;
|
|
59
|
+
readonly now?: Date;
|
|
60
|
+
readonly pinActive?: boolean;
|
|
61
|
+
readonly eventRef?: string | null;
|
|
62
|
+
/** Duration hours when expiresAt omitted (default 8). */
|
|
63
|
+
readonly durationHours?: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve template → mint input (operations + surfaces). Pure; no I/O.
|
|
67
|
+
* Always routes operations through Wave 1 AuthzOperation names.
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveClosedVerbTemplate(input: {
|
|
70
|
+
readonly template: string;
|
|
71
|
+
readonly target: string;
|
|
72
|
+
readonly now?: Date;
|
|
73
|
+
readonly expiresAt?: string | null;
|
|
74
|
+
readonly classification?: VerbClassificationTable;
|
|
75
|
+
}): {
|
|
76
|
+
operations: AuthzOperation[];
|
|
77
|
+
surfaces: string[];
|
|
78
|
+
expiresAt: string | null;
|
|
79
|
+
template: ClosedVerbTemplateName;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Resolve finish-loop AFK template → mint ops (edit/push/pr/merge). Pure; no I/O.
|
|
83
|
+
* Does not mint release-class operations.
|
|
84
|
+
*/
|
|
85
|
+
export declare function resolveFinishLoopTemplate(input: {
|
|
86
|
+
readonly now?: Date;
|
|
87
|
+
readonly expiresAt?: string | null;
|
|
88
|
+
readonly durationHours?: number;
|
|
89
|
+
readonly surfaces?: readonly string[];
|
|
90
|
+
}): {
|
|
91
|
+
operations: AuthzOperation[];
|
|
92
|
+
surfaces: string[];
|
|
93
|
+
expiresAt: string | null;
|
|
94
|
+
template: typeof FINISH_LOOP_TEMPLATE_NAME;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Mint a human-origin grant for an AFK closed-verb template.
|
|
98
|
+
* **Sole mint path:** mintHumanOriginGrant (operator-cli). No session-auth SoT.
|
|
99
|
+
*/
|
|
100
|
+
export declare function mintClosedVerbTemplateGrant(input: MintClosedVerbTemplateInput): HumanOriginGrant;
|
|
101
|
+
/**
|
|
102
|
+
* Mint a human-origin grant for the walk-away finish-loop template (#871).
|
|
103
|
+
* Covers edit/push/pr/merge only — never release-cut / release-publish / release-rollback.
|
|
104
|
+
* **Sole mint path:** mintHumanOriginGrant (operator-cli).
|
|
105
|
+
*/
|
|
106
|
+
export declare function mintFinishLoopTemplateGrant(input: MintFinishLoopTemplateInput): HumanOriginGrant;
|
|
107
|
+
/**
|
|
108
|
+
* Dispatch AFK template mint by name. Closed-verb templates require target;
|
|
109
|
+
* finish-loop does not.
|
|
110
|
+
*/
|
|
111
|
+
export declare function mintAfkTemplateGrant(input: {
|
|
112
|
+
readonly projectRoot: string;
|
|
113
|
+
readonly template: string;
|
|
114
|
+
readonly target?: string | null;
|
|
115
|
+
readonly actor?: string;
|
|
116
|
+
readonly expiresAt?: string | null;
|
|
117
|
+
readonly singleUse?: boolean;
|
|
118
|
+
readonly planRef?: string | null;
|
|
119
|
+
readonly repo?: string | null;
|
|
120
|
+
readonly branch?: string | null;
|
|
121
|
+
readonly surfaces?: readonly string[];
|
|
122
|
+
readonly storyIds?: readonly string[];
|
|
123
|
+
readonly issueIds?: readonly number[];
|
|
124
|
+
readonly cohortId?: string | null;
|
|
125
|
+
readonly now?: Date;
|
|
126
|
+
readonly classification?: VerbClassificationTable;
|
|
127
|
+
readonly pinActive?: boolean;
|
|
128
|
+
readonly eventRef?: string | null;
|
|
129
|
+
readonly durationHours?: number;
|
|
130
|
+
}): HumanOriginGrant;
|
|
131
|
+
/**
|
|
132
|
+
* Assert that authorization for closed verbs must not use an independent
|
|
133
|
+
* session-auth mint engine. Used by unit tests as a dual-mint guard.
|
|
134
|
+
*/
|
|
135
|
+
export declare function assertNoIndependentSessionAuthMint(): {
|
|
136
|
+
mintPath: "mintHumanOriginGrant";
|
|
137
|
+
sessionAuthIsAuthority: false;
|
|
138
|
+
};
|
|
139
|
+
//# sourceMappingURL=templates.d.ts.map
|