@deftai/directive-core 0.108.0 → 0.109.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.
Files changed (138) hide show
  1. package/dist/cache/operations.js +1 -1
  2. package/dist/check/gate-lists.d.ts +14 -0
  3. package/dist/check/gate-lists.js +26 -3
  4. package/dist/check/session-completed-ac.d.ts +1 -1
  5. package/dist/check/session-completed-ac.js +1 -1
  6. package/dist/consumer-check-contract/evaluate.d.ts +47 -0
  7. package/dist/consumer-check-contract/evaluate.js +181 -15
  8. package/dist/delivery-attempt/evaluate.d.ts +9 -1
  9. package/dist/delivery-attempt/evaluate.js +69 -0
  10. package/dist/delivery-attempt/index.d.ts +1 -1
  11. package/dist/delivery-attempt/index.js +1 -1
  12. package/dist/deposit/live-procedure-exclusions.d.ts +18 -0
  13. package/dist/deposit/live-procedure-exclusions.js +110 -0
  14. package/dist/deposit/live-procedure-targets.d.ts +45 -0
  15. package/dist/deposit/live-procedure-targets.js +274 -0
  16. package/dist/deposit/python-free.d.ts +6 -0
  17. package/dist/deposit/python-free.js +15 -0
  18. package/dist/deposit/rewrite-deposit-links.d.ts +42 -0
  19. package/dist/deposit/rewrite-deposit-links.js +148 -0
  20. package/dist/deposit/run-stage-content-pack.d.ts +2 -0
  21. package/dist/deposit/run-stage-content-pack.js +3 -0
  22. package/dist/deposit/stage-content-pack.d.ts +17 -0
  23. package/dist/deposit/stage-content-pack.js +91 -0
  24. package/dist/design-critique/citation-grammar.d.ts +7 -0
  25. package/dist/design-critique/citation-grammar.js +1 -1
  26. package/dist/design-critique/completed-arc-record.d.ts +1 -1
  27. package/dist/design-critique/completed-arc-record.js +59 -14
  28. package/dist/hooks/classify/host-session-identity.d.ts +24 -11
  29. package/dist/hooks/classify/host-session-identity.js +46 -49
  30. package/dist/hooks/classify/index.d.ts +2 -2
  31. package/dist/hooks/classify/index.js +2 -2
  32. package/dist/hooks/classify/paths.d.ts +2 -0
  33. package/dist/hooks/classify/paths.js +8 -4
  34. package/dist/hooks/classify/stdin.js +69 -3
  35. package/dist/hooks/dest-form.d.ts +20 -1
  36. package/dist/hooks/dest-form.js +158 -21
  37. package/dist/hooks/dispatcher.d.ts +6 -2
  38. package/dist/hooks/dispatcher.js +258 -62
  39. package/dist/hooks/fixtures/cases.d.ts +6 -0
  40. package/dist/hooks/fixtures/cases.js +53 -0
  41. package/dist/hooks/git-destructive-log.d.ts +32 -0
  42. package/dist/hooks/git-destructive-log.js +46 -0
  43. package/dist/hooks/index.d.ts +2 -0
  44. package/dist/hooks/index.js +2 -0
  45. package/dist/hooks/shell-write-targets.d.ts +10 -0
  46. package/dist/hooks/shell-write-targets.js +274 -0
  47. package/dist/hooks/tools.d.ts +20 -2
  48. package/dist/hooks/tools.js +30 -1
  49. package/dist/init-deposit/gitignore.d.ts +7 -0
  50. package/dist/init-deposit/gitignore.js +24 -0
  51. package/dist/init-deposit/init-deposit.js +3 -0
  52. package/dist/init-deposit/refresh.js +7 -0
  53. package/dist/init-deposit/runtime-writers.d.ts +14 -0
  54. package/dist/init-deposit/runtime-writers.js +33 -0
  55. package/dist/intake/issue-ingest.d.ts +6 -1
  56. package/dist/intake/issue-ingest.js +21 -2
  57. package/dist/intake/reconcile-issues.js +13 -13
  58. package/dist/lifecycle/brief-envelope.d.ts +25 -0
  59. package/dist/lifecycle/brief-envelope.js +42 -0
  60. package/dist/lifecycle/index.d.ts +1 -0
  61. package/dist/lifecycle/index.js +1 -0
  62. package/dist/literal-acceptance/evaluate.js +14 -5
  63. package/dist/literal-acceptance/index.d.ts +1 -1
  64. package/dist/literal-acceptance/index.js +1 -1
  65. package/dist/literal-acceptance/run.d.ts +2 -0
  66. package/dist/literal-acceptance/run.js +19 -1
  67. package/dist/orphan-active/candidate-scope.d.ts +53 -0
  68. package/dist/orphan-active/candidate-scope.js +157 -0
  69. package/dist/orphan-active/evaluate.d.ts +27 -0
  70. package/dist/orphan-active/evaluate.js +63 -6
  71. package/dist/orphan-active/index.d.ts +1 -0
  72. package/dist/orphan-active/index.js +1 -0
  73. package/dist/policy/merge-approval-head.js +7 -6
  74. package/dist/pr-closing-keywords/gh.js +32 -9
  75. package/dist/pr-closing-keywords/main.d.ts +1 -0
  76. package/dist/pr-closing-keywords/main.js +68 -3
  77. package/dist/pr-closing-keywords/types.d.ts +2 -0
  78. package/dist/pr-merge-readiness/gh.js +32 -9
  79. package/dist/pr-protected-issues/gh.js +36 -9
  80. package/dist/pr-wait-mergeable/wrappers.js +3 -3
  81. package/dist/product-first-done-gate/evaluate.js +6 -2
  82. package/dist/product-first-done-gate/types.js +2 -0
  83. package/dist/release/consumer-hard-stops.d.ts +48 -0
  84. package/dist/release/consumer-hard-stops.js +140 -0
  85. package/dist/release/consumer-readiness-disclosure.d.ts +18 -0
  86. package/dist/release/consumer-readiness-disclosure.js +51 -0
  87. package/dist/release/index.d.ts +2 -0
  88. package/dist/release/index.js +2 -0
  89. package/dist/release/pipeline.js +55 -0
  90. package/dist/release/run-consumer-readiness.d.ts +15 -0
  91. package/dist/release/run-consumer-readiness.js +24 -0
  92. package/dist/release/types.d.ts +6 -0
  93. package/dist/review-monitor/github-lease.js +4 -3
  94. package/dist/run-summary/types.d.ts +2 -2
  95. package/dist/scm/build-command.d.ts +2 -2
  96. package/dist/scm/build-command.js +2 -2
  97. package/dist/scm/call-shape.d.ts +25 -0
  98. package/dist/scm/call-shape.js +59 -0
  99. package/dist/scm/call.d.ts +7 -4
  100. package/dist/scm/call.js +41 -11
  101. package/dist/scm/gh-rest.d.ts +25 -12
  102. package/dist/scm/gh-rest.js +65 -15
  103. package/dist/scm/index.d.ts +2 -0
  104. package/dist/scm/index.js +2 -0
  105. package/dist/scm/spawn-status.d.ts +33 -0
  106. package/dist/scm/spawn-status.js +53 -0
  107. package/dist/scope/acceptance-evidence.d.ts +1 -1
  108. package/dist/scope/transition.js +4 -14
  109. package/dist/session/ac-pass-banking.d.ts +2 -2
  110. package/dist/session/ac-pass-banking.js +2 -2
  111. package/dist/session/host-session-owner.d.ts +53 -0
  112. package/dist/session/host-session-owner.js +89 -0
  113. package/dist/session/index.d.ts +1 -0
  114. package/dist/session/index.js +1 -0
  115. package/dist/session/occupancy.d.ts +21 -1
  116. package/dist/session/occupancy.js +70 -12
  117. package/dist/session/verify-ac-session-cache.d.ts +2 -2
  118. package/dist/session/verify-ac-session-cache.js +2 -2
  119. package/dist/triage/evaluate/worktrees.js +153 -6
  120. package/dist/umbrella-current-shape/index.d.ts +51 -3
  121. package/dist/umbrella-current-shape/index.js +106 -18
  122. package/dist/validate-content/deposit-required.d.ts +39 -0
  123. package/dist/validate-content/deposit-required.js +147 -0
  124. package/dist/validate-content/index.d.ts +1 -0
  125. package/dist/validate-content/index.js +1 -0
  126. package/dist/validate-content/validate-links.d.ts +2 -3
  127. package/dist/validate-content/validate-links.js +29 -2
  128. package/dist/vbrief-activate/activate.d.ts +7 -2
  129. package/dist/vbrief-activate/activate.js +26 -13
  130. package/dist/verify-ac/evaluate.d.ts +9 -0
  131. package/dist/verify-ac/evaluate.js +32 -9
  132. package/dist/verify-source/deposit-closure.d.ts +23 -0
  133. package/dist/verify-source/deposit-closure.js +162 -0
  134. package/dist/verify-source/index.d.ts +2 -0
  135. package/dist/verify-source/index.js +2 -0
  136. package/dist/verify-source/semantic-single-source.d.ts +36 -0
  137. package/dist/verify-source/semantic-single-source.js +349 -0
  138. package/package.json +3 -3
@@ -1,4 +1,5 @@
1
- import { resolveBinary } from "./binary.js";
1
+ import { type SpawnSyncOptions } from "node:child_process";
2
+ import { type WhichFn } from "./binary.js";
2
3
  export declare const DEFAULT_TIMEOUT_S = 60;
3
4
  /** Raised when the `"owner/repo"` argument is malformed. */
4
5
  export declare class InvalidRepoError extends Error {
@@ -11,35 +12,47 @@ export declare class GhRestError extends Error {
11
12
  readonly endpoint: string;
12
13
  readonly payload: Record<string, unknown> | null;
13
14
  readonly hint: string;
15
+ readonly binary: string;
14
16
  constructor(options: {
15
17
  stderr: string;
16
18
  exitCode: number;
17
19
  endpoint: string;
18
20
  payload: Record<string, unknown> | null;
19
21
  hint?: string;
22
+ binary?: string;
20
23
  });
21
24
  }
22
- export type RunGhApiFn = (args: readonly string[], options?: {
23
- timeout?: number;
24
- whichFn?: Parameters<typeof resolveBinary>[0];
25
- }) => {
25
+ export type GhSpawnResult = {
26
+ readonly status: number | null;
27
+ readonly stdout?: string | Buffer | null;
28
+ readonly stderr?: string | Buffer | null;
29
+ readonly error?: {
30
+ readonly message?: string;
31
+ readonly code?: string;
32
+ };
33
+ };
34
+ export type GhSpawnFn = (command: string, args: readonly string[], options: SpawnSyncOptions) => GhSpawnResult;
35
+ export type GhApiProcess = {
26
36
  returncode: number;
27
37
  stdout: string;
28
38
  stderr: string;
39
+ binary?: string;
29
40
  };
41
+ export type RunGhApiFn = (args: readonly string[], options?: {
42
+ timeout?: number;
43
+ whichFn?: WhichFn;
44
+ spawnFn?: GhSpawnFn;
45
+ }) => GhApiProcess;
30
46
  /** Single subprocess seam invoked by every helper. */
31
47
  export declare function runGhApi(args: readonly string[], options?: {
32
48
  timeout?: number;
33
- whichFn?: Parameters<typeof resolveBinary>[0];
34
- }): {
35
- returncode: number;
36
- stdout: string;
37
- stderr: string;
38
- };
49
+ whichFn?: WhichFn;
50
+ spawnFn?: GhSpawnFn;
51
+ }): GhApiProcess;
39
52
  export declare function splitRepo(repo: string): [string, string];
40
53
  export interface GhRestSeams {
41
54
  readonly runGhApiFn?: RunGhApiFn;
42
- readonly whichFn?: Parameters<typeof resolveBinary>[0];
55
+ readonly whichFn?: WhichFn;
43
56
  }
44
57
  /** `GET /repos/{owner}/{repo}/issues/{n}` -- read a single issue. */
45
58
  export declare function restIssueView(repo: string, n: number, seams?: GhRestSeams): Record<string, unknown>;
@@ -3,8 +3,10 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { SUBPROCESS_MAX_BUFFER } from "../subprocess/max-buffer.js";
6
- import { resolveBinary } from "./binary.js";
6
+ import { defaultWhich } from "./binary.js";
7
+ import { classifyScmArgv, resolveBinaryForRole } from "./call-shape.js";
7
8
  import { pyRepr } from "./py-format.js";
9
+ import { classifySpawnStatus, formatScmSpawnDiagnostic, isAvailabilitySpawnFailure, } from "./spawn-status.js";
8
10
  export const DEFAULT_TIMEOUT_S = 60;
9
11
  /** Raised when the `"owner/repo"` argument is malformed. */
10
12
  export class InvalidRepoError extends Error {
@@ -23,10 +25,16 @@ export class GhRestError extends Error {
23
25
  endpoint;
24
26
  payload;
25
27
  hint;
28
+ binary;
26
29
  constructor(options) {
27
30
  const hint = options.hint ?? "";
28
- let msg = `gh api failed: endpoint=${pyRepr(options.endpoint)} ` +
29
- `exit=${options.exitCode} stderr=${pyRepr(options.stderr)}`;
31
+ const binary = options.binary ?? "gh";
32
+ const statusClass = classifySpawnStatus(options.exitCode);
33
+ let msg = `${binary} api failed: endpoint=${pyRepr(options.endpoint)} ` + `exit=${options.exitCode}`;
34
+ if (statusClass !== `exit ${options.exitCode}`) {
35
+ msg += ` (${statusClass})`;
36
+ }
37
+ msg += ` stderr=${pyRepr(options.stderr)}`;
30
38
  if (hint.length > 0) {
31
39
  msg += `; hint: ${hint}`;
32
40
  }
@@ -37,30 +45,70 @@ export class GhRestError extends Error {
37
45
  this.endpoint = options.endpoint;
38
46
  this.payload = options.payload;
39
47
  this.hint = hint;
48
+ this.binary = binary;
49
+ }
50
+ }
51
+ function defaultGhSpawn(command, args, options) {
52
+ return spawnSync(command, [...args], options);
53
+ }
54
+ function finalizeGhApiResult(binary, result) {
55
+ const rawStderr = typeof result.stderr === "string" ? result.stderr : "";
56
+ let stderr = rawStderr;
57
+ if (result.status === null && result.error && stderr.trim().length === 0) {
58
+ stderr = result.error.message ?? "";
40
59
  }
60
+ if (isAvailabilitySpawnFailure({
61
+ status: result.status,
62
+ error: result.error,
63
+ stdout: typeof result.stdout === "string" ? result.stdout : "",
64
+ stderr: rawStderr,
65
+ }) &&
66
+ stderr.trim().length === 0) {
67
+ stderr = formatScmSpawnDiagnostic(binary, result.status, stderr, result.error);
68
+ }
69
+ return {
70
+ returncode: result.status ?? 1,
71
+ stdout: typeof result.stdout === "string" ? result.stdout : "",
72
+ stderr,
73
+ binary,
74
+ rawStderr,
75
+ rawStatus: result.status,
76
+ error: result.error,
77
+ };
41
78
  }
42
79
  /** Single subprocess seam invoked by every helper. */
43
80
  export function runGhApi(args, options = {}) {
44
- const binary = resolveBinary(options.whichFn);
81
+ const whichFn = options.whichFn ?? defaultWhich;
82
+ const spawnFn = options.spawnFn ?? defaultGhSpawn;
83
+ const role = classifyScmArgv("api", args);
84
+ let binary = resolveBinaryForRole(role, whichFn);
45
85
  const timeoutMs = options.timeout !== undefined ? Math.round(options.timeout * 1000) : DEFAULT_TIMEOUT_S * 1000;
46
- const result = spawnSync(binary, ["api", ...args], {
86
+ const spawnOpts = {
47
87
  encoding: "utf8",
48
88
  timeout: timeoutMs,
49
89
  env: process.env,
50
90
  maxBuffer: SUBPROCESS_MAX_BUFFER,
51
91
  stdio: ["ignore", "pipe", "pipe"],
52
- });
53
- let stderr = typeof result.stderr === "string" ? result.stderr : "";
54
- // A spawn-level failure (e.g. ENOBUFS when stdout exceeds maxBuffer) yields a
55
- // null status and empty stderr; surface error.message so the GhRestError that
56
- // wraps this never reports a blank reason (#1867).
57
- if (result.status === null && result.error && stderr.trim().length === 0) {
58
- stderr = result.error.message;
92
+ };
93
+ const spawnOnce = (bin) => finalizeGhApiResult(bin, spawnFn(bin, ["api", ...args], spawnOpts));
94
+ let first = spawnOnce(binary);
95
+ if (role === "cached-get" &&
96
+ binary === "ghx" &&
97
+ isAvailabilitySpawnFailure({
98
+ status: first.rawStatus,
99
+ error: first.error,
100
+ stdout: first.stdout,
101
+ stderr: first.rawStderr,
102
+ }) &&
103
+ whichFn("gh") !== null) {
104
+ binary = "gh";
105
+ first = spawnOnce(binary);
59
106
  }
60
107
  return {
61
- returncode: result.status ?? 1,
62
- stdout: typeof result.stdout === "string" ? result.stdout : "",
63
- stderr,
108
+ returncode: first.returncode,
109
+ stdout: first.stdout,
110
+ stderr: first.stderr,
111
+ binary,
64
112
  };
65
113
  }
66
114
  export function splitRepo(repo) {
@@ -83,6 +131,7 @@ function execApi(args, options) {
83
131
  endpoint: options.endpoint,
84
132
  payload: options.payload,
85
133
  hint: options.hint ?? "",
134
+ binary: result.binary ?? "gh",
86
135
  });
87
136
  }
88
137
  const stdout = result.stdout.trim();
@@ -393,6 +442,7 @@ export function restIssueListOpenInventory(repo, seams = {}) {
393
442
  endpoint,
394
443
  payload: null,
395
444
  hint: "verify gh auth and core REST quota; open-issue inventory must be complete",
445
+ binary: result.binary ?? "gh",
396
446
  });
397
447
  }
398
448
  const stdout = result.stdout.trim();
@@ -2,6 +2,7 @@ export * from "./argv.js";
2
2
  export * from "./binary.js";
3
3
  export * from "./build-command.js";
4
4
  export * from "./call.js";
5
+ export * from "./call-shape.js";
5
6
  export * from "./constants.js";
6
7
  export * from "./design-critique-chip.js";
7
8
  export * from "./errors.js";
@@ -10,4 +11,5 @@ export * from "./main.js";
10
11
  export * from "./py-format.js";
11
12
  export * from "./readiness.js";
12
13
  export * from "./rest-dispatch.js";
14
+ export * from "./spawn-status.js";
13
15
  //# sourceMappingURL=index.d.ts.map
package/dist/scm/index.js CHANGED
@@ -2,6 +2,7 @@ export * from "./argv.js";
2
2
  export * from "./binary.js";
3
3
  export * from "./build-command.js";
4
4
  export * from "./call.js";
5
+ export * from "./call-shape.js";
5
6
  export * from "./constants.js";
6
7
  export * from "./design-critique-chip.js";
7
8
  export * from "./errors.js";
@@ -10,4 +11,5 @@ export * from "./main.js";
10
11
  export * from "./py-format.js";
11
12
  export * from "./readiness.js";
12
13
  export * from "./rest-dispatch.js";
14
+ export * from "./spawn-status.js";
13
15
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,33 @@
1
+ import type { WhichFn } from "./binary.js";
2
+ import type { ScmBinaryRole } from "./call-shape.js";
3
+ /** Windows NTSTATUS STATUS_DLL_INIT_FAILED (#3737). Unsigned and signed 32-bit. */
4
+ export declare const STATUS_DLL_INIT_FAILED = 3221225794;
5
+ export declare function unsignedExitStatus(status: number): number;
6
+ export declare function classifySpawnStatus(status: number | null | undefined, error?: {
7
+ readonly message?: string;
8
+ readonly code?: string;
9
+ }): string;
10
+ export declare function isAvailabilitySpawnFailure(result: {
11
+ readonly status: number | null | undefined;
12
+ readonly error?: {
13
+ readonly message?: string;
14
+ readonly code?: string;
15
+ };
16
+ readonly stdout?: string;
17
+ readonly stderr?: string;
18
+ }): boolean;
19
+ export declare function formatScmSpawnDiagnostic(binary: string, status: number | null | undefined, stderr: string, error?: {
20
+ readonly message?: string;
21
+ readonly code?: string;
22
+ }): string;
23
+ /** Return `gh` when a cached-get ghx spawn failed and live gh is on PATH. */
24
+ export declare function ghxSpawnFallbackBinary(role: ScmBinaryRole, invoked: string, whichFn: WhichFn, failure: {
25
+ readonly status: number | null | undefined;
26
+ readonly error?: {
27
+ readonly message?: string;
28
+ readonly code?: string;
29
+ };
30
+ readonly stdout?: string;
31
+ readonly stderr?: string;
32
+ }): string | null;
33
+ //# sourceMappingURL=spawn-status.d.ts.map
@@ -0,0 +1,53 @@
1
+ /** Windows NTSTATUS STATUS_DLL_INIT_FAILED (#3737). Unsigned and signed 32-bit. */
2
+ export const STATUS_DLL_INIT_FAILED = 0xc0000142;
3
+ const STATUS_DLL_INIT_FAILED_SIGNED = -1073741502;
4
+ export function unsignedExitStatus(status) {
5
+ return status >>> 0;
6
+ }
7
+ export function classifySpawnStatus(status, error) {
8
+ if (typeof status !== "number") {
9
+ if (typeof error?.code === "string" && error.code.length > 0) {
10
+ return error.code;
11
+ }
12
+ if (typeof error?.message === "string" && error.message.trim().length > 0) {
13
+ return error.message.trim();
14
+ }
15
+ return "spawn-failed";
16
+ }
17
+ const unsigned = unsignedExitStatus(status);
18
+ if (unsigned === STATUS_DLL_INIT_FAILED || status === STATUS_DLL_INIT_FAILED_SIGNED) {
19
+ return "0xC0000142 STATUS_DLL_INIT_FAILED";
20
+ }
21
+ if (unsigned >= 0xc0000000) {
22
+ return `0x${unsigned.toString(16).toUpperCase()}`;
23
+ }
24
+ return `exit ${status}`;
25
+ }
26
+ export function isAvailabilitySpawnFailure(result) {
27
+ if (result.error !== undefined) {
28
+ return true;
29
+ }
30
+ if (typeof result.status !== "number") {
31
+ return true;
32
+ }
33
+ const unsigned = unsignedExitStatus(result.status);
34
+ const emptyOut = (result.stdout ?? "").trim().length === 0 && (result.stderr ?? "").trim().length === 0;
35
+ return (emptyOut &&
36
+ (unsigned === STATUS_DLL_INIT_FAILED || result.status === STATUS_DLL_INIT_FAILED_SIGNED));
37
+ }
38
+ export function formatScmSpawnDiagnostic(binary, status, stderr, error) {
39
+ const cls = classifySpawnStatus(status, error);
40
+ const err = stderr.trim();
41
+ if (err.length > 0) {
42
+ return `${binary} failed (${cls}): ${err}`;
43
+ }
44
+ return `${binary} failed (${cls}); stderr empty`;
45
+ }
46
+ /** Return `gh` when a cached-get ghx spawn failed and live gh is on PATH. */
47
+ export function ghxSpawnFallbackBinary(role, invoked, whichFn, failure) {
48
+ if (role !== "cached-get" || invoked !== "ghx" || whichFn("gh") === null) {
49
+ return null;
50
+ }
51
+ return isAvailabilitySpawnFailure(failure) ? "gh" : null;
52
+ }
53
+ //# sourceMappingURL=spawn-status.js.map
@@ -58,7 +58,7 @@ export interface AcceptanceEvidenceGateResult {
58
58
  readonly message: string;
59
59
  readonly reports: readonly CriterionAcceptanceReport[];
60
60
  /** How the #3357 walk obtained AC (#3387). */
61
- readonly servedFrom?: "bank" | "cache" | "executed";
61
+ readonly servedFrom?: "bank" | "cache" | "executed" | "refused";
62
62
  /** Which acceptance check decided the walk (#3497). Undefined when no walk ran. */
63
63
  readonly predicate?: AcceptancePredicate;
64
64
  }
@@ -5,6 +5,7 @@ import { InstrumentedVbriefCrud, persistCrudMetrics } from "../eval/crud-telemet
5
5
  import { assertProjectionContained, ProjectionContainmentError, } from "../fs/projection-containment.js";
6
6
  import { applyClauseDerivationToPlan, maybeEmitAcceptanceStampFromChange, } from "../intake/clause-derivation.js";
7
7
  import { hasArtifactSuffix } from "../layout/resolve.js";
8
+ import { stampExistingEnvelopes } from "../lifecycle/brief-envelope.js";
8
9
  import { evaluateCompletedPlanConsistency } from "../lifecycle/completed-consistency.js";
9
10
  import { evaluateLiteralAcceptanceFromPlan } from "../literal-acceptance/index.js";
10
11
  import { evaluateAcceptanceActivateGate } from "./acceptance-activate-gate.js";
@@ -45,18 +46,6 @@ function advanceNonTerminalOwnItems(items, targetStatus) {
45
46
  advanceNonTerminalOwnItems(obj.items, targetStatus);
46
47
  }
47
48
  }
48
- /**
49
- * Refresh the document envelope `updated` stamp to match `plan.updated`.
50
- * Stamps whichever of xBRIEFInfo (v0.8) / vBRIEFInfo (v0.6) is present — never creates one (#2862 / #2346).
51
- */
52
- function stampEnvelopeUpdated(data, nowIso) {
53
- for (const key of ["xBRIEFInfo", "vBRIEFInfo"]) {
54
- const env = data[key];
55
- if (typeof env === "object" && env !== null && !Array.isArray(env)) {
56
- env.updated = nowIso;
57
- }
58
- }
59
- }
60
49
  export function runTransition(action, filePath, now = new Date(), options = {}) {
61
50
  if (!(action in TRANSITIONS)) {
62
51
  const valid = Object.keys(TRANSITIONS).sort().join(", ");
@@ -255,7 +244,8 @@ export function runTransition(action, filePath, now = new Date(), options = {})
255
244
  planObj.status = targetStatus;
256
245
  planObj.updated = nowIso;
257
246
  // Keep the envelope clock aligned with plan.updated on every mutating transition (#2862).
258
- stampEnvelopeUpdated(data, nowIso);
247
+ // Stamps whichever envelope is present, never creates one (#3933 / #2346).
248
+ stampExistingEnvelopes(data, nowIso);
259
249
  // Reconcile the completing brief's own plan.items (mirrors #1527 / #2566 registry sync) (#2862).
260
250
  // On complete, items only reach here when #3240 evidence/disposition gate passed.
261
251
  if (OWN_ITEMS_RECONCILE_ACTIONS.has(act)) {
@@ -412,7 +402,7 @@ function restampCompletedBrief(args) {
412
402
  }
413
403
  planObj.status = "completed";
414
404
  planObj.updated = nowIso;
415
- stampEnvelopeUpdated(data, nowIso);
405
+ stampExistingEnvelopes(data, nowIso);
416
406
  stampCompletionMetadata(planObj, projectRoot, nowIso, {
417
407
  completedSessionId: resolveCompletionSessionId(projectRoot),
418
408
  });
@@ -12,8 +12,8 @@
12
12
  */
13
13
  import { type AcPassBankingConfig } from "../policy/ac-pass-banking.js";
14
14
  import type { HardEffortBudget, VerificationDepthPolicy } from "./effort-budget.js";
15
- /** Durable bank ledger directory (gitignored under `.deft/`). */
16
- export declare const AC_PASS_BANK_DIR = ".deft/ac-pass-banks";
15
+ /** Durable bank ledger directory (under already-ignored `.deft/cache/`). */
16
+ export declare const AC_PASS_BANK_DIR = ".deft/cache/ac-pass-banks";
17
17
  export declare const AC_PASS_BANK_SCHEMA_VERSION: 1;
18
18
  /** Env path for optional JSONL run summary (#3282). Unset = silent. */
19
19
  export declare const ENV_RUN_SUMMARY_PATH = "DEFT_RUN_SUMMARY_PATH";
@@ -18,8 +18,8 @@ import { DEFAULT_SURPLUS_THRESHOLD, resolveAcPassBanking, } from "../policy/ac-p
18
18
  import { RunSummaryEmitter } from "../run-summary/emit.js";
19
19
  import { detectHardEffortBudget, formatDeepeningSkippedNote, recommendVerificationDepth, } from "./effort-budget.js";
20
20
  import { gitHead } from "./git.js";
21
- /** Durable bank ledger directory (gitignored under `.deft/`). */
22
- export const AC_PASS_BANK_DIR = ".deft/ac-pass-banks";
21
+ /** Durable bank ledger directory (under already-ignored `.deft/cache/`). */
22
+ export const AC_PASS_BANK_DIR = ".deft/cache/ac-pass-banks";
23
23
  export const AC_PASS_BANK_SCHEMA_VERSION = 1;
24
24
  /** Env path for optional JSONL run summary (#3282). Unset = silent. */
25
25
  export const ENV_RUN_SUMMARY_PATH = "DEFT_RUN_SUMMARY_PATH";
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Where a host publishes the session id Directive uses as the occupancy owner
3
+ * (#3611 / #3873).
4
+ *
5
+ * This is payload/environment classification, not authentication: cooperative
6
+ * owner ids are locally forgeable by another same-user process. It lives here,
7
+ * outside the hook classifier, because two processes must agree on the answer.
8
+ * The hook resolves it from its own environment; the CLI resolves it from the
9
+ * shell the same host spawned. A claim made under a different id than the write
10
+ * gate later presents is the #3873 defect -- the session denied by its own lease.
11
+ */
12
+ export declare const HOST_IDENTITY_PROVIDERS: readonly ["codex", "claude", "cursor", "grok"];
13
+ export type HookHostIdentityProvider = (typeof HOST_IDENTITY_PROVIDERS)[number];
14
+ export declare const MAX_HOOK_HOST_IDENTITY_UTF8_BYTES = 512;
15
+ /**
16
+ * Where a provider publishes its owner id.
17
+ *
18
+ * `payload` providers carry a verified field on every hook invocation.
19
+ * `host-env` providers publish a stable id in the environment of the processes
20
+ * the host spawns. That environment is host-set: the hook is a sibling of the
21
+ * agent's shell rather than a descendant, so a shell export cannot reach it.
22
+ */
23
+ export type HookHostIdentitySource = {
24
+ readonly kind: "payload";
25
+ readonly field: string;
26
+ } | {
27
+ readonly kind: "host-env";
28
+ readonly variable: string;
29
+ };
30
+ /** The identity source for a host, or null when the host has no contract. */
31
+ export declare function hookHostIdentitySource(host: string): HookHostIdentitySource | null;
32
+ /** Bound a raw host id by UTF-8 bytes, control characters and surrogate pairing. */
33
+ export declare function isUsableHostSessionId(raw: string): boolean;
34
+ export declare function canonicalHostSessionId(provider: HookHostIdentityProvider, rawSessionId: string): string;
35
+ export type HostEnvIdentityStatus = "ok" | "missing" | "invalid";
36
+ export type HostEnvIdentityResolution = {
37
+ readonly status: "ok";
38
+ readonly rawSessionId: string;
39
+ } | {
40
+ readonly status: "missing" | "invalid";
41
+ readonly rawSessionId: null;
42
+ };
43
+ /** Read one `host-env` provider's variable out of a process environment. */
44
+ export declare function readHostEnvIdentity(environ: NodeJS.ProcessEnv, variable: string): HostEnvIdentityResolution;
45
+ /**
46
+ * The canonical owner the running host published to this process, or null.
47
+ *
48
+ * Used by the CLI claim path so `session:start` binds the lease to the same id
49
+ * the write hook will present. Two live host variables are ambiguous and
50
+ * resolve to null: picking one would bind a claim to a guess.
51
+ */
52
+ export declare function ambientHostSessionOwner(environ?: NodeJS.ProcessEnv): string | null;
53
+ //# sourceMappingURL=host-session-owner.d.ts.map
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Where a host publishes the session id Directive uses as the occupancy owner
3
+ * (#3611 / #3873).
4
+ *
5
+ * This is payload/environment classification, not authentication: cooperative
6
+ * owner ids are locally forgeable by another same-user process. It lives here,
7
+ * outside the hook classifier, because two processes must agree on the answer.
8
+ * The hook resolves it from its own environment; the CLI resolves it from the
9
+ * shell the same host spawned. A claim made under a different id than the write
10
+ * gate later presents is the #3873 defect -- the session denied by its own lease.
11
+ */
12
+ export const HOST_IDENTITY_PROVIDERS = ["codex", "claude", "cursor", "grok"];
13
+ export const MAX_HOOK_HOST_IDENTITY_UTF8_BYTES = 512;
14
+ const HOST_IDENTITY_SOURCES = {
15
+ codex: { kind: "payload", field: "session_id" },
16
+ claude: { kind: "payload", field: "session_id" },
17
+ cursor: { kind: "payload", field: "conversation_id" },
18
+ grok: { kind: "host-env", variable: "GROK_SESSION_ID" },
19
+ };
20
+ /** The identity source for a host, or null when the host has no contract. */
21
+ export function hookHostIdentitySource(host) {
22
+ return (HOST_IDENTITY_SOURCES[host] ?? null);
23
+ }
24
+ function hasControlCharacter(value) {
25
+ for (let index = 0; index < value.length; index += 1) {
26
+ const unit = value.charCodeAt(index);
27
+ if (unit <= 0x1f || (unit >= 0x7f && unit <= 0x9f))
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+ function hasUnpairedUtf16Surrogate(value) {
33
+ for (let index = 0; index < value.length; index += 1) {
34
+ const unit = value.charCodeAt(index);
35
+ if (unit >= 0xd800 && unit <= 0xdbff) {
36
+ const next = value.charCodeAt(index + 1);
37
+ if (!(next >= 0xdc00 && next <= 0xdfff))
38
+ return true;
39
+ index += 1;
40
+ continue;
41
+ }
42
+ if (unit >= 0xdc00 && unit <= 0xdfff)
43
+ return true;
44
+ }
45
+ return false;
46
+ }
47
+ /** Bound a raw host id by UTF-8 bytes, control characters and surrogate pairing. */
48
+ export function isUsableHostSessionId(raw) {
49
+ return (raw.length > 0 &&
50
+ raw === raw.trim() &&
51
+ !hasControlCharacter(raw) &&
52
+ !hasUnpairedUtf16Surrogate(raw) &&
53
+ Buffer.byteLength(raw, "utf8") <= MAX_HOOK_HOST_IDENTITY_UTF8_BYTES);
54
+ }
55
+ export function canonicalHostSessionId(provider, rawSessionId) {
56
+ const encoded = Buffer.from(rawSessionId, "utf8").toString("base64url");
57
+ return `host:${provider}:v1:${encoded}`;
58
+ }
59
+ /** Read one `host-env` provider's variable out of a process environment. */
60
+ export function readHostEnvIdentity(environ, variable) {
61
+ const raw = environ[variable];
62
+ // An unset variable and an empty one are one state to a host: `set X=` on
63
+ // Windows deletes the entry, so absence must not read as a malformed value.
64
+ if (raw === undefined || raw.length === 0)
65
+ return { status: "missing", rawSessionId: null };
66
+ return isUsableHostSessionId(raw)
67
+ ? { status: "ok", rawSessionId: raw }
68
+ : { status: "invalid", rawSessionId: null };
69
+ }
70
+ /**
71
+ * The canonical owner the running host published to this process, or null.
72
+ *
73
+ * Used by the CLI claim path so `session:start` binds the lease to the same id
74
+ * the write hook will present. Two live host variables are ambiguous and
75
+ * resolve to null: picking one would bind a claim to a guess.
76
+ */
77
+ export function ambientHostSessionOwner(environ = process.env) {
78
+ const resolved = [];
79
+ for (const provider of HOST_IDENTITY_PROVIDERS) {
80
+ const source = HOST_IDENTITY_SOURCES[provider];
81
+ if (source.kind !== "host-env")
82
+ continue;
83
+ const value = readHostEnvIdentity(environ, source.variable);
84
+ if (value.status === "ok")
85
+ resolved.push(canonicalHostSessionId(provider, value.rawSessionId));
86
+ }
87
+ return resolved.length === 1 ? resolved[0] : null;
88
+ }
89
+ //# sourceMappingURL=host-session-owner.js.map
@@ -6,6 +6,7 @@ export * from "./compact-ritual.js";
6
6
  export * from "./deposit-sha.js";
7
7
  export * from "./effort-budget.js";
8
8
  export * from "./git.js";
9
+ export * from "./host-session-owner.js";
9
10
  export * from "./json.js";
10
11
  export * from "./occupancy.js";
11
12
  export * from "./openclaw-soft-rebind-deposit.js";
@@ -6,6 +6,7 @@ export * from "./compact-ritual.js";
6
6
  export * from "./deposit-sha.js";
7
7
  export * from "./effort-budget.js";
8
8
  export * from "./git.js";
9
+ export * from "./host-session-owner.js";
9
10
  export * from "./json.js";
10
11
  export * from "./occupancy.js";
11
12
  export * from "./openclaw-soft-rebind-deposit.js";
@@ -218,7 +218,18 @@ export declare function formatOccupancyStaleWarning(record: OccupancyRecord, now
218
218
  * lease is gone rather than merely quiet, so the answer is to re-claim.
219
219
  */
220
220
  export declare function formatOccupancyAgeCapRemediation(record: OccupancyRecord, now?: Date, maxLeaseMs?: number): string;
221
- export declare function formatOccupancyRemediation(record: OccupancyRecord, now?: Date): string;
221
+ /**
222
+ * Tell a refused caller who holds the lease and what it can actually run.
223
+ *
224
+ * `presented` is the id the refused caller offered (#3873). Without it the
225
+ * message can only print `<your-session-id>` placeholders, which is fine for a
226
+ * CLI caller that passed its own `--session-id` and useless to a hook process,
227
+ * which does not know what identity it presented. Passing it also keeps the
228
+ * message honest when there is none: a grant cannot name an empty child --
229
+ * `occupancy:grant --child-session-id=` is refused at parse and at membership --
230
+ * so that remediation is not printed to a caller who could never run it.
231
+ */
232
+ export declare function formatOccupancyRemediation(record: OccupancyRecord, now?: Date, presented?: string): string;
222
233
  /**
223
234
  * Refuse an administrative verb to a granted child (#3755). Named apart from
224
235
  * the stranger refusal because the answer differs: this caller is admitted, and
@@ -226,6 +237,15 @@ export declare function formatOccupancyRemediation(record: OccupancyRecord, now?
226
237
  * derives from. A grant admits writes; the lease has one owner.
227
238
  */
228
239
  export declare function formatOccupancyMemberAdministrationRefusal(record: OccupancyRecord, grant: OccupancyGrant, verb: string): string;
240
+ /**
241
+ * The owner a claim is made under: an explicit id, then `DEFT_SESSION_ID`, then
242
+ * the id the running host published, then a mint.
243
+ *
244
+ * The host step is what makes an identified host's claim reachable (#3873).
245
+ * Minting instead binds the lease to an id no later hook process can present,
246
+ * so the session that claimed the worktree is refused by its own lease. The
247
+ * mint stays as the last resort for hosts that publish nothing.
248
+ */
229
249
  export declare function resolveOccupancySessionId(input?: ApplyOccupancyInput): string;
230
250
  export declare function readOccupancy(projectRoot: string): OccupancyRecord | null;
231
251
  export declare function liveOccupant(projectRoot: string, now?: Date, ttlMs?: number, maxLeaseMs?: number): OccupancyRecord | null;