@deftai/directive-core 0.105.0 → 0.107.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 (173) hide show
  1. package/dist/authz/classify.js +591 -34
  2. package/dist/check/gate-lists.js +2 -0
  3. package/dist/check/named-cause.js +1 -1
  4. package/dist/design-critique/exclusive-chip.d.ts +33 -0
  5. package/dist/design-critique/exclusive-chip.js +68 -0
  6. package/dist/design-critique/parent-audit.d.ts +59 -0
  7. package/dist/design-critique/parent-audit.js +121 -0
  8. package/dist/doctor/checks.d.ts +6 -0
  9. package/dist/doctor/checks.js +35 -0
  10. package/dist/forward-coverage/diff-coverage.d.ts +39 -0
  11. package/dist/forward-coverage/diff-coverage.js +112 -0
  12. package/dist/forward-coverage/diff-lines.d.ts +19 -0
  13. package/dist/forward-coverage/diff-lines.js +173 -0
  14. package/dist/forward-coverage/evaluate.d.ts +24 -9
  15. package/dist/forward-coverage/evaluate.js +125 -19
  16. package/dist/hooks/dest-form.d.ts +46 -0
  17. package/dist/hooks/dest-form.js +575 -0
  18. package/dist/hooks/dispatcher.d.ts +17 -1
  19. package/dist/hooks/dispatcher.js +82 -11
  20. package/dist/hooks/index.d.ts +1 -0
  21. package/dist/hooks/index.js +1 -0
  22. package/dist/init-deposit/agent-hooks.d.ts +1 -1
  23. package/dist/init-deposit/agent-hooks.js +1 -1
  24. package/dist/intake/clause-derivation.d.ts +9 -0
  25. package/dist/intake/clause-derivation.js +30 -3
  26. package/dist/intake/github-auth-modes-cli.js +14 -2
  27. package/dist/intake/github-auth-modes.d.ts +54 -9
  28. package/dist/intake/github-auth-modes.js +326 -123
  29. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
  30. package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
  31. package/dist/lifecycle/completed-write-guard.d.ts +48 -0
  32. package/dist/lifecycle/completed-write-guard.js +373 -0
  33. package/dist/lifecycle/index.d.ts +1 -0
  34. package/dist/lifecycle/index.js +1 -0
  35. package/dist/lifecycle-visible/evaluate.d.ts +98 -0
  36. package/dist/lifecycle-visible/evaluate.js +710 -0
  37. package/dist/lifecycle-visible/index.d.ts +2 -0
  38. package/dist/lifecycle-visible/index.js +2 -0
  39. package/dist/literal-acceptance/capture.d.ts +16 -0
  40. package/dist/literal-acceptance/capture.js +78 -10
  41. package/dist/literal-acceptance/evaluate.js +25 -3
  42. package/dist/literal-acceptance/index.d.ts +2 -2
  43. package/dist/literal-acceptance/index.js +2 -2
  44. package/dist/literal-acceptance/run.js +9 -3
  45. package/dist/literal-acceptance/types.d.ts +6 -0
  46. package/dist/literal-acceptance/types.js +6 -0
  47. package/dist/orchestration/judgment-policy.d.ts +13 -0
  48. package/dist/orchestration/judgment-policy.js +15 -0
  49. package/dist/orchestration/probe-session.js +13 -8
  50. package/dist/orchestration/verify-judgment-gates.d.ts +4 -0
  51. package/dist/orchestration/verify-judgment-gates.js +43 -11
  52. package/dist/policy/ceremony-dial.js +7 -7
  53. package/dist/policy/host-hooks.d.ts +20 -0
  54. package/dist/policy/host-hooks.js +121 -2
  55. package/dist/policy/org-force-on-migration.js +2 -1
  56. package/dist/policy/plan-extensions.d.ts +1 -1
  57. package/dist/policy/plan-extensions.js +2 -0
  58. package/dist/policy/product-signal.js +6 -7
  59. package/dist/policy/require-human-merge.js +8 -6
  60. package/dist/policy/resolve.d.ts +7 -2
  61. package/dist/policy/resolve.js +25 -8
  62. package/dist/policy/runtime-authority.d.ts +18 -0
  63. package/dist/policy/runtime-authority.js +11 -0
  64. package/dist/policy/value-feedback.js +7 -8
  65. package/dist/policy/write-fence.js +3 -0
  66. package/dist/preflight-cache/evaluate.d.ts +12 -0
  67. package/dist/preflight-cache/evaluate.js +24 -4
  68. package/dist/preflight-cache/index.d.ts +1 -1
  69. package/dist/preflight-cache/index.js +1 -1
  70. package/dist/product-first-done-gate/evaluate.d.ts +4 -0
  71. package/dist/product-first-done-gate/evaluate.js +82 -10
  72. package/dist/release/closed-verb-gate.d.ts +12 -0
  73. package/dist/release/closed-verb-gate.js +51 -0
  74. package/dist/release/index.d.ts +1 -0
  75. package/dist/release/index.js +1 -0
  76. package/dist/release/pipeline.js +7 -0
  77. package/dist/release/types.d.ts +12 -0
  78. package/dist/render/constants.d.ts +2 -1
  79. package/dist/render/constants.js +2 -1
  80. package/dist/render/framework-commands.js +4 -0
  81. package/dist/render/spec-validate.js +23 -12
  82. package/dist/run-summary/types.d.ts +4 -0
  83. package/dist/scm/design-critique-chip.d.ts +37 -0
  84. package/dist/scm/design-critique-chip.js +171 -0
  85. package/dist/scm/index.d.ts +1 -0
  86. package/dist/scm/index.js +1 -0
  87. package/dist/scm/main.d.ts +3 -0
  88. package/dist/scm/main.js +15 -1
  89. package/dist/scm/readiness-cli.d.ts +2 -0
  90. package/dist/scm/readiness-cli.js +42 -1
  91. package/dist/scm/readiness.d.ts +2 -1
  92. package/dist/scm/readiness.js +7 -1
  93. package/dist/scope/decompose.js +2 -1
  94. package/dist/scope/delivery-evidence.d.ts +27 -1
  95. package/dist/scope/index.d.ts +1 -0
  96. package/dist/scope/index.js +1 -0
  97. package/dist/scope/lifecycle-write.d.ts +24 -0
  98. package/dist/scope/lifecycle-write.js +64 -0
  99. package/dist/scope/main.js +18 -6
  100. package/dist/scope/promote-from-issue.js +5 -5
  101. package/dist/scope/transition.js +90 -11
  102. package/dist/scope-provenance/evaluate.js +8 -4
  103. package/dist/session/ac-pass-banking.d.ts +5 -0
  104. package/dist/session/ac-pass-banking.js +3 -2
  105. package/dist/session/git.d.ts +18 -0
  106. package/dist/session/git.js +81 -1
  107. package/dist/session/occupancy.d.ts +2 -1
  108. package/dist/session/occupancy.js +11 -4
  109. package/dist/session/process-cost.d.ts +32 -10
  110. package/dist/session/process-cost.js +189 -1
  111. package/dist/session/product-state-hash.js +41 -24
  112. package/dist/session/ritual-entrypoint.js +16 -1
  113. package/dist/session/session-start.d.ts +18 -0
  114. package/dist/session/session-start.js +49 -2
  115. package/dist/session/verify-session-ritual.d.ts +46 -4
  116. package/dist/session/verify-session-ritual.js +118 -10
  117. package/dist/swarm/finalize-cohort-cli.d.ts +7 -2
  118. package/dist/swarm/finalize-cohort-cli.js +165 -35
  119. package/dist/swarm/finalize-cohort.d.ts +1 -0
  120. package/dist/swarm/finalize-cohort.js +14 -2
  121. package/dist/swarm/launch.d.ts +69 -0
  122. package/dist/swarm/launch.js +191 -1
  123. package/dist/swarm/routing-set-cli.js +2 -0
  124. package/dist/swarm/routing-verify.d.ts +1 -1
  125. package/dist/swarm/routing-verify.js +2 -2
  126. package/dist/triage/actions/index.d.ts +4 -0
  127. package/dist/triage/actions/index.js +6 -0
  128. package/dist/triage/bootstrap/index.js +6 -6
  129. package/dist/triage/evaluate/evaluate.d.ts +11 -0
  130. package/dist/triage/evaluate/evaluate.js +169 -0
  131. package/dist/triage/evaluate/github.d.ts +16 -0
  132. package/dist/triage/evaluate/github.js +153 -0
  133. package/dist/triage/evaluate/index.d.ts +9 -0
  134. package/dist/triage/evaluate/index.js +8 -0
  135. package/dist/triage/evaluate/paths.d.ts +7 -0
  136. package/dist/triage/evaluate/paths.js +25 -0
  137. package/dist/triage/evaluate/sink.d.ts +5 -0
  138. package/dist/triage/evaluate/sink.js +52 -0
  139. package/dist/triage/evaluate/types.d.ts +93 -0
  140. package/dist/triage/evaluate/types.js +13 -0
  141. package/dist/triage/evaluate/validity.d.ts +8 -0
  142. package/dist/triage/evaluate/validity.js +87 -0
  143. package/dist/triage/evaluate/value.d.ts +12 -0
  144. package/dist/triage/evaluate/value.js +33 -0
  145. package/dist/triage/evaluate/wip-census.d.ts +5 -0
  146. package/dist/triage/evaluate/wip-census.js +39 -0
  147. package/dist/triage/evaluate/worktrees.d.ts +8 -0
  148. package/dist/triage/evaluate/worktrees.js +68 -0
  149. package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
  150. package/dist/triage/evaluate/xbrief-refs.js +60 -0
  151. package/dist/triage/help/registry-data.d.ts +22 -8
  152. package/dist/triage/help/registry-data.js +54 -6
  153. package/dist/triage/index.d.ts +1 -0
  154. package/dist/triage/index.js +1 -0
  155. package/dist/triage/welcome/writers.d.ts +1 -1
  156. package/dist/triage/welcome/writers.js +10 -5
  157. package/dist/value/feedback-file.d.ts +17 -2
  158. package/dist/value/feedback-file.js +80 -6
  159. package/dist/value/readback.d.ts +4 -1
  160. package/dist/value/readback.js +51 -12
  161. package/dist/vbrief-reconcile/labels.d.ts +1 -0
  162. package/dist/vbrief-reconcile/labels.js +30 -0
  163. package/dist/vbrief-validate/schema.d.ts +4 -0
  164. package/dist/vbrief-validate/schema.js +1 -1
  165. package/dist/vbrief-validation/story-quality.d.ts +7 -0
  166. package/dist/vbrief-validation/story-quality.js +8 -1
  167. package/dist/verify-env/agent-hook-readiness.d.ts +2 -2
  168. package/dist/verify-env/agent-hook-readiness.js +12 -8
  169. package/dist/verify-env/agent-hooks-live-probe.d.ts +5 -1
  170. package/dist/verify-env/agent-hooks-live-probe.js +26 -5
  171. package/dist/verify-env/agent-hooks.js +3 -4
  172. package/dist/xbrief-migrate/agents-header.js +69 -7
  173. package/package.json +7 -3
@@ -14,8 +14,9 @@ import { evaluateOccupancyWriteGate } from "../session/occupancy.js";
14
14
  import { emitSessionRitualBlockedProcessCost } from "../session/process-cost.js";
15
15
  import { markRitualStaleAfterCompact } from "../session/ritual-sentinel.js";
16
16
  import { runSessionStartHookWrite } from "../session/session-start-hook.js";
17
- import { formatRitualRecoveryInstruction, verifySessionRitual, } from "../session/verify-session-ritual.js";
17
+ import { formatRitualRecoveryInstruction, verifySessionRitual, writeGateRitualOptions, } from "../session/verify-session-ritual.js";
18
18
  import { hookMcpArgsText, hookShellCommand, hookToolName, hookWriteTargetPath, missingToolNameMessage, record, } from "./classify/index.js";
19
+ import { classifyProductDestForms, payloadWithInjectedWriteTarget } from "./dest-form.js";
19
20
  import { isAssistPosture, isEphemeralSpawn, isExploreSpawn, isReadOnlyHookContext, } from "./readonly.js";
20
21
  import { inspectActiveScope } from "./scope.js";
21
22
  import { isDirectWriteTool, isMcpTool, isShellTool, isSpawnTool } from "./tools.js";
@@ -255,6 +256,17 @@ export function isHookHost(value) {
255
256
  export function isHookEvent(value) {
256
257
  return HOOK_EVENTS.includes(value);
257
258
  }
259
+ function scopeNotReadyCoverageHint(toolName) {
260
+ if (isSpawnTool(toolName))
261
+ return "";
262
+ if (isShellTool(toolName)) {
263
+ return (" This deny is the active-scope write gate on a recognized Shell dest-form " +
264
+ "(git checkout --, git restore, rm/rmdir of in-repo paths).");
265
+ }
266
+ return (" This gate covers direct-write tools (Edit/Write) and implementation spawn, " +
267
+ "plus recognized Shell dest-forms (git checkout --, git restore, rm/rmdir). " +
268
+ "Other shell (python -c, cmd /c copy, git status) is not this deny.");
269
+ }
258
270
  function deny(input, code, toolName, message, scopePath = null) {
259
271
  return {
260
272
  verdict: "deny",
@@ -578,15 +590,18 @@ function inspectMutationGates(input, toolName, seams, options) {
578
590
  try {
579
591
  // Mutation dispatch is a live gated boundary: active verification reruns
580
592
  // non-cacheable agent-hook readiness instead of trusting persisted success.
593
+ // Write-surface authority executes agent_hooks only (#3738). cache_fresh
594
+ // is not required and is never executed. doctor is read from the record.
581
595
  // Its allow fixture is non-write and its deny fixture is read-only, so the
582
596
  // installed-shim probe cannot recurse into this mutation path.
583
597
  ritual = (seams.verifyRitual ??
584
598
  seams.inspectRitual ??
585
- ((root) => verifySessionRitual(root, {
586
- tier: "gated",
587
- posture: "mutation",
599
+ ((root) => verifySessionRitual(root, writeGateRitualOptions({
588
600
  bypass: false,
589
- })))(projectRoot);
601
+ runner: seams.ritualRunner,
602
+ detectWorkSelection: seams.detectWorkSelection,
603
+ runGit: seams.ritualRunGit,
604
+ }))))(projectRoot);
590
605
  }
591
606
  catch (cause) {
592
607
  // #2994: best-effort local process-cost; never changes deny verdict.
@@ -705,7 +720,8 @@ function inspectMutationGates(input, toolName, seams, options) {
705
720
  "assist posture (commands.md #1802) — do not fake `scope:activate` for notes.");
706
721
  }
707
722
  const denyCode = isSpawnTool(toolName) ? "spawn-not-ready" : "scope-not-ready";
708
- return deny(input, denyCode, toolName, `Directive denied ${toolName}: ${scope.message}${proposedPathHint}`);
723
+ return deny(input, denyCode, toolName, `Directive denied ${toolName}: ${scope.message}${proposedPathHint}` +
724
+ scopeNotReadyCoverageHint(toolName));
709
725
  }
710
726
  }
711
727
  const allowCode = isSpawnTool(toolName) ? "spawn-ready" : "write-ready";
@@ -742,6 +758,58 @@ function inspectMutationGates(input, toolName, seams, options) {
742
758
  scopePath: scope.path,
743
759
  };
744
760
  }
761
+ /**
762
+ * Route recognized Shell dest-forms through inspectMutationGates, then push/merge.
763
+ * Dest-form allow is kept when runtime authority has nothing classifiable.
764
+ */
765
+ function decideShellDestFormsThenRuntimeAuthority(input, toolName, seams) {
766
+ const command = hookShellCommand(input.payload);
767
+ let destAllow = null;
768
+ let expansionDeny = null;
769
+ // #3438 / #3594: opt-in. `off` (the default) leaves Shell exactly as it was
770
+ // before this gate existed — unrecognized and fail-open — so landing the
771
+ // classifier cannot deny anything a consumer runs today. `enforce` turns on
772
+ // BOTH halves together: resolved dests through inspectMutationGates and
773
+ // fail-closed for targets that cannot be proved. ⊗ Do not split those two:
774
+ // enforcing only resolved dests would allow `cd x && rm y` while denying
775
+ // `rm x/y`, and enforcing only fail-closed would deny the compound while
776
+ // letting the in-scope simple form through unchecked.
777
+ // An unreadable policy resolves to `off`, which is the no-new-denials
778
+ // direction and matches the opt-in default rather than failing closed.
779
+ const destFormsEnforced = loadRuntimeAuthorityPolicySafe(input, seams)?.shellDestForms === "enforce";
780
+ if (command !== null && destFormsEnforced) {
781
+ for (const dest of classifyProductDestForms(command)) {
782
+ if (dest.expansion === true) {
783
+ expansionDeny = deny(input, "scope-not-ready", toolName, `Directive denied ${toolName}: Shell dest-form target is not reconstructable ` +
784
+ "(compound command, glob/variable, leading ~, or a git context option). Name a " +
785
+ "concrete path in ONE simple command — split `cd x && rm y` into a single " +
786
+ "`rm x/y`, or one tool call per command — or use Edit/Write. Only a single simple " +
787
+ "command with an absolute dest is checked soundly; a relative dest assumes the " +
788
+ "shell is at the project root. Everything else stays fail-closed (#3438).");
789
+ continue;
790
+ }
791
+ const destInput = {
792
+ ...input,
793
+ payload: payloadWithInjectedWriteTarget(input.payload, dest.path),
794
+ };
795
+ const destDecision = inspectMutationGates(destInput, toolName, seams, {
796
+ proposedLifecycleExempt: true,
797
+ });
798
+ if (destDecision.verdict === "deny")
799
+ return destDecision;
800
+ destAllow = destDecision;
801
+ }
802
+ }
803
+ const runtime = decideShellOrMcpRuntimeAuthority(input, toolName, seams);
804
+ if (runtime.verdict === "deny")
805
+ return runtime;
806
+ if (expansionDeny !== null)
807
+ return expansionDeny;
808
+ if (destAllow !== null && runtime.code === "shell-op-unclassifiable") {
809
+ return destAllow;
810
+ }
811
+ return runtime;
812
+ }
745
813
  /** Decide a normalized event using only the P0 direct-write policy. */
746
814
  export function decideHook(input, seams = {}) {
747
815
  const projectRoot = resolve(input.projectRoot);
@@ -930,13 +998,16 @@ export function decideHook(input, seams = {}) {
930
998
  }
931
999
  return inspectMutationGates(input, toolName, seams, { proposedLifecycleExempt: false });
932
1000
  }
933
- // Shell/Bash and classifiable MCP: enforce scopes.push / scopes.merge (#2711).
1001
+ // Shell dest-forms (#3438): recognized product mutations share inspectMutationGates
1002
+ // with Edit/Write (assist/scratch, proposed lifecycle, file_scope). Push/merge stay
1003
+ // on runtimeAuthority (#2711). Non-dest unclassifiable shell (git status) fail-open.
1004
+ if (isShellTool(toolName)) {
1005
+ return decideShellDestFormsThenRuntimeAuthority(input, toolName, seams);
1006
+ }
1007
+ // Classifiable MCP: enforce scopes.push / scopes.merge (#2711).
934
1008
  // Route bare push/merge MCP names (merge_pull_request, git_push, …) even when
935
1009
  // isMcpTool is false — classifyMcpTool is the gate (dispatcher-side, no tools↔policy cycle).
936
- // Does not require active-scope ritual only runtimeAuthority when enabled.
937
- if (isShellTool(toolName) ||
938
- isMcpTool(toolName) ||
939
- classifyMcpTool(toolName, hookMcpArgsText(input.payload)) !== null) {
1010
+ if (isMcpTool(toolName) || classifyMcpTool(toolName, hookMcpArgsText(input.payload)) !== null) {
940
1011
  return decideShellOrMcpRuntimeAuthority(input, toolName, seams);
941
1012
  }
942
1013
  if (!isDirectWriteTool(toolName)) {
@@ -1,5 +1,6 @@
1
1
  export * from "./classify/index.js";
2
2
  export * from "./cursor-hooks.js";
3
+ export * from "./dest-form.js";
3
4
  export * from "./dispatcher.js";
4
5
  export * from "./fixtures/index.js";
5
6
  export * from "./scope.js";
@@ -1,5 +1,6 @@
1
1
  export * from "./classify/index.js";
2
2
  export * from "./cursor-hooks.js";
3
+ export * from "./dest-form.js";
3
4
  export * from "./dispatcher.js";
4
5
  export * from "./fixtures/index.js";
5
6
  export * from "./scope.js";
@@ -16,7 +16,7 @@ export declare const CURSOR_SESSION_HOOK_TIMEOUT_SECONDS = 5;
16
16
  * Mutation tool.before runs `inspectMutationGates` → gated
17
17
  * `verifySessionRitual`, which re-runs non-cacheable agent-hook readiness on
18
18
  * every boundary. Live readiness alone has a multi-host fixture ceiling of
19
- * ~12s (`content/contracts/agent-hook-readiness.md`); the historical deposit
19
+ * ~24s (`content/contracts/agent-hook-readiness.md`); the historical deposit
20
20
  * default of 5s was below that budget. Under Cursor `failClosed: true`, a
21
21
  * host timeout kill surfaces as opaque exit-1 with no Directive decision
22
22
  * code (#3246 / related #2864). Keep this above the readiness ceiling plus
@@ -24,7 +24,7 @@ export const CURSOR_SESSION_HOOK_TIMEOUT_SECONDS = 5;
24
24
  * Mutation tool.before runs `inspectMutationGates` → gated
25
25
  * `verifySessionRitual`, which re-runs non-cacheable agent-hook readiness on
26
26
  * every boundary. Live readiness alone has a multi-host fixture ceiling of
27
- * ~12s (`content/contracts/agent-hook-readiness.md`); the historical deposit
27
+ * ~24s (`content/contracts/agent-hook-readiness.md`); the historical deposit
28
28
  * default of 5s was below that budget. Under Cursor `failClosed: true`, a
29
29
  * host timeout kill surfaces as opaque exit-1 with no Directive decision
30
30
  * code (#3246 / related #2864). Keep this above the readiness ceiling plus
@@ -23,11 +23,15 @@ export interface ClauseStampPreparation {
23
23
  readonly ambiguity_attestation?: "none_found";
24
24
  readonly remediation?: string;
25
25
  }
26
+ export declare const MISSING_AMBIGUITY_ATTESTATION_CAUSE: "missing_ambiguity_attestation";
27
+ export declare const AMBIGUITY_ATTESTATION_REMEDIATION = "record ambiguity readings or attest none_found";
26
28
  export interface AmbiguityAttestationCheck {
27
29
  readonly ok: boolean;
28
30
  readonly attested: boolean;
29
31
  readonly kind: "ambiguous-clause" | "none_found" | "missing";
30
32
  readonly message: string;
33
+ readonly cause?: typeof MISSING_AMBIGUITY_ATTESTATION_CAUSE;
34
+ readonly remediation?: string;
31
35
  }
32
36
  /** True when activate/promote must run #3323 (absent, empty none_stated, or command-only). */
33
37
  export declare function needsClauseDerivation(acceptance: unknown): boolean;
@@ -45,6 +49,11 @@ export declare function countClauseProvenance(clauses: readonly TracedAcceptance
45
49
  export declare function prepareClauseStamp(clauses: readonly AcceptanceClause[], statement: string): ClauseStampPreparation;
46
50
  /** Config-error surface for verify:ac: attestation or an ambiguous clause with readings. */
47
51
  export declare function evaluateAmbiguityAttestation(acceptance: unknown): AmbiguityAttestationCheck;
52
+ /**
53
+ * Fail-closed check for a *stamped* plan.acceptance (#3559).
54
+ * Command-only / empty floor blocks are not stamps; they keep the empty-resolution path.
55
+ */
56
+ export declare function stampedAmbiguityAttestationError(acceptance: unknown): AmbiguityAttestationCheck | null;
48
57
  /**
49
58
  * Annotate stamped clauses with provenance and an ambiguity attestation.
50
59
  * An all-implementation stamp is stripped (never sufficient alone).
@@ -9,6 +9,8 @@ import { ENV_RUN_SUMMARY_PATH, RunSummaryEmitter, } from "../run-summary/index.j
9
9
  import { deriveAcceptanceClauses, readAcceptanceClauses, serializeAcceptanceClauses, } from "../verify-ac/clauses.js";
10
10
  /** One-line remediation when a stamp has no statement-traceable clause (#3398). */
11
11
  export const CLAUSE_STAMP_IMPLEMENTATION_ONLY_REMEDIATION = "derive clauses from the statement's testable constraints";
12
+ export const MISSING_AMBIGUITY_ATTESTATION_CAUSE = "missing_ambiguity_attestation";
13
+ export const AMBIGUITY_ATTESTATION_REMEDIATION = "record ambiguity readings or attest none_found";
12
14
  const NARRATIVE_KEYS = [
13
15
  "Overview",
14
16
  "Description",
@@ -165,12 +167,22 @@ export function prepareClauseStamp(clauses, statement) {
165
167
  ambiguity_attestation: traced.some(hasAmbiguousReadings) ? undefined : "none_found",
166
168
  };
167
169
  }
168
- const AMBIGUITY_ATTESTATION_MISSING = "verify:ac config error: missing ambiguity attestation; record at least one ambiguous: true clause with readings, or set ambiguity_attestation: none_found";
170
+ const AMBIGUITY_ATTESTATION_MISSING = `verify:ac config error: missing ambiguity attestation; ${AMBIGUITY_ATTESTATION_REMEDIATION}`;
171
+ function missingAttestationCheck() {
172
+ return {
173
+ ok: false,
174
+ attested: false,
175
+ kind: "missing",
176
+ message: AMBIGUITY_ATTESTATION_MISSING,
177
+ cause: MISSING_AMBIGUITY_ATTESTATION_CAUSE,
178
+ remediation: AMBIGUITY_ATTESTATION_REMEDIATION,
179
+ };
180
+ }
169
181
  /** Config-error surface for verify:ac: attestation or an ambiguous clause with readings. */
170
182
  export function evaluateAmbiguityAttestation(acceptance) {
171
183
  const rec = asRecord(acceptance);
172
184
  if (rec === null) {
173
- return { ok: false, attested: false, kind: "missing", message: AMBIGUITY_ATTESTATION_MISSING };
185
+ return missingAttestationCheck();
174
186
  }
175
187
  const clauses = readAcceptanceClauses(rec);
176
188
  if (clauses.some(hasAmbiguousReadings)) {
@@ -179,7 +191,22 @@ export function evaluateAmbiguityAttestation(acceptance) {
179
191
  if (rec.ambiguity_attestation === "none_found") {
180
192
  return { ok: true, attested: true, kind: "none_found", message: "" };
181
193
  }
182
- return { ok: false, attested: false, kind: "missing", message: AMBIGUITY_ATTESTATION_MISSING };
194
+ return missingAttestationCheck();
195
+ }
196
+ /**
197
+ * Fail-closed check for a *stamped* plan.acceptance (#3559).
198
+ * Command-only / empty floor blocks are not stamps; they keep the empty-resolution path.
199
+ */
200
+ export function stampedAmbiguityAttestationError(acceptance) {
201
+ const rec = asRecord(acceptance);
202
+ if (rec === null) {
203
+ return null;
204
+ }
205
+ if (readAcceptanceClauses(rec).length === 0) {
206
+ return null;
207
+ }
208
+ const check = evaluateAmbiguityAttestation(rec);
209
+ return check.ok ? null : check;
183
210
  }
184
211
  function serializeTracedClauses(clauses) {
185
212
  return serializeAcceptanceClauses(clauses).map((row, index) => ({
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { fileURLToPath } from "node:url";
3
- import { githubAuthModesMain } from "./github-auth-modes.js";
3
+ import { githubAuthModesMain, INSTALLATION_IDENTITY_ISSUE_URL } from "./github-auth-modes.js";
4
4
  function parseArgs(argv) {
5
5
  const out = {};
6
6
  for (let i = 0; i < argv.length; i += 1) {
@@ -11,11 +11,23 @@ function parseArgs(argv) {
11
11
  out.repo = argv[++i];
12
12
  else if (arg === "--github-auth-mode")
13
13
  out.githubAuthMode = argv[++i];
14
+ else if (arg === "--expected-login")
15
+ out.expectedLogin = argv[++i];
16
+ else if (arg === "--expected-app-slug" || arg === "--expected-installation-id") {
17
+ throw new Error(`${arg} is not accepted; GitHub App installation identity is deferred to ${INSTALLATION_IDENTITY_ISSUE_URL}`);
18
+ }
14
19
  }
15
20
  return out;
16
21
  }
17
22
  export function mainEntry(argv = process.argv.slice(2)) {
18
- return githubAuthModesMain(parseArgs(argv));
23
+ try {
24
+ return githubAuthModesMain(parseArgs(argv));
25
+ }
26
+ catch (err) {
27
+ const message = err instanceof Error ? err.message : String(err);
28
+ process.stderr.write(`${message}\n`);
29
+ return 2;
30
+ }
19
31
  }
20
32
  if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
21
33
  process.exit(mainEntry());
@@ -3,12 +3,23 @@ import { probeRuntimeCapabilities, type RuntimeCapabilityReport } from "./platfo
3
3
  export declare const GITHUB_AUTH_MODE_INJECTED_TOKEN = "injected-token";
4
4
  export declare const GITHUB_AUTH_MODE_HOST_GH = "host-gh";
5
5
  export declare const KNOWN_GITHUB_AUTH_MODES: Set<string>;
6
- export declare const DEFAULT_VALIDATION_REPO = "deftai/directive";
6
+ export declare const PRINCIPAL_KIND_USER = "user";
7
7
  export declare const FAILURE_MISSING_INJECTED_TOKEN = "missing_injected_token";
8
8
  export declare const FAILURE_GH_AUTH = "gh_auth_failed";
9
9
  export declare const FAILURE_API_UNREACHABLE = "api_unreachable";
10
10
  export declare const FAILURE_REPO_ACCESS = "repo_access_denied";
11
11
  export declare const FAILURE_INVALID_MODE = "invalid_auth_mode";
12
+ export declare const FAILURE_MISSING_EXPECTED_PRINCIPAL = "missing_expected_principal";
13
+ export declare const FAILURE_PRINCIPAL_MISMATCH = "principal_mismatch";
14
+ export declare const FAILURE_MISSING_TARGET_REPO = "missing_target_repo";
15
+ export declare const FAILURE_INSTALLATION_IDENTITY_UNVERIFIABLE = "installation_identity_unverifiable";
16
+ export declare const ENV_EXPECTED_GITHUB_LOGIN = "DEFT_EXPECTED_GITHUB_LOGIN";
17
+ export declare const INSTALLATION_IDENTITY_ISSUE_URL = "https://github.com/deftai/directive/issues/3693";
18
+ /** GitHub-specific expected worker principal. Not a universal (provider-neutral) identity. */
19
+ export type ExpectedGithubWorkerPrincipal = {
20
+ readonly kind: "user";
21
+ readonly login: string;
22
+ };
12
23
  export interface GitHubAuthValidationResult {
13
24
  readonly ok: boolean;
14
25
  readonly githubAuthMode: string;
@@ -17,31 +28,63 @@ export interface GitHubAuthValidationResult {
17
28
  readonly detail: string;
18
29
  readonly remediation: string | null;
19
30
  readonly login: string | null;
31
+ readonly principal: ExpectedGithubWorkerPrincipal | null;
32
+ readonly validationRepo: string | null;
20
33
  }
21
34
  export type GhRunner = (args: readonly string[], environ: NodeJS.ProcessEnv) => CompletedProcess;
22
- export declare function findInjectedToken(environ: NodeJS.ProcessEnv): string | null;
23
- export declare function inferGithubAuthMode(runtimeReport: RuntimeCapabilityReport): string;
24
- export declare function validateInjectedTokenMode(environ: NodeJS.ProcessEnv, options?: {
35
+ export type GitRemoteReader = (cwd?: string) => string | null;
36
+ export interface GithubAuthValidationOptions {
25
37
  repo?: string;
26
38
  runtimeMode?: string | null;
27
39
  runGh?: GhRunner;
28
- }): GitHubAuthValidationResult;
29
- export declare function validateHostGhMode(environ: NodeJS.ProcessEnv, options?: {
40
+ expectedPrincipal?: ExpectedGithubWorkerPrincipal | null;
41
+ gitRemoteUrl?: string | null;
42
+ readGitRemote?: GitRemoteReader;
43
+ cwd?: string;
44
+ }
45
+ export declare function findInjectedToken(environ: NodeJS.ProcessEnv): string | null;
46
+ export declare function inferGithubAuthMode(runtimeReport: RuntimeCapabilityReport): string;
47
+ export declare function defaultReadGitOriginUrl(cwd?: string): string | null;
48
+ export declare function parseOwnerRepoSlug(input: string): string | null;
49
+ export declare function deriveValidationRepo(options: {
30
50
  repo?: string;
31
- runtimeMode?: string | null;
32
- runGh?: GhRunner;
33
- }): GitHubAuthValidationResult;
51
+ environ?: NodeJS.ProcessEnv;
52
+ gitRemoteUrl?: string | null;
53
+ readGitRemote?: GitRemoteReader;
54
+ cwd?: string;
55
+ }): {
56
+ ok: true;
57
+ repo: string;
58
+ } | {
59
+ ok: false;
60
+ detail: string;
61
+ };
62
+ export declare function resolveExpectedGithubWorkerPrincipal(environ: NodeJS.ProcessEnv, explicit?: ExpectedGithubWorkerPrincipal | null): ExpectedGithubWorkerPrincipal | {
63
+ error: string;
64
+ } | null;
65
+ export declare function isInstallationUserEndpointInapplicable(proc: CompletedProcess): boolean;
66
+ export declare function formatUserApiFailureDetail(mode: string, proc: CompletedProcess): string;
67
+ export declare function validateInjectedTokenMode(environ: NodeJS.ProcessEnv, options?: GithubAuthValidationOptions): GitHubAuthValidationResult;
68
+ export declare function validateHostGhMode(environ: NodeJS.ProcessEnv, options?: GithubAuthValidationOptions): GitHubAuthValidationResult;
34
69
  export declare function validateGithubAuth(githubAuthMode: string, options?: {
35
70
  environ?: NodeJS.ProcessEnv;
36
71
  runtimeReport?: RuntimeCapabilityReport | null;
37
72
  repo?: string;
38
73
  runGh?: GhRunner;
74
+ expectedPrincipal?: ExpectedGithubWorkerPrincipal | null;
75
+ gitRemoteUrl?: string | null;
76
+ readGitRemote?: GitRemoteReader;
77
+ cwd?: string;
39
78
  }): GitHubAuthValidationResult;
40
79
  export declare function validateGithubAuthForWorker(githubAuthMode?: string | null, options?: {
41
80
  environ?: NodeJS.ProcessEnv;
42
81
  runtimeReport?: RuntimeCapabilityReport | null;
43
82
  repo?: string;
44
83
  runGh?: GhRunner;
84
+ expectedPrincipal?: ExpectedGithubWorkerPrincipal | null;
85
+ gitRemoteUrl?: string | null;
86
+ readGitRemote?: GitRemoteReader;
87
+ cwd?: string;
45
88
  }): GitHubAuthValidationResult;
46
89
  export declare function resultToDict(result: GitHubAuthValidationResult): Record<string, unknown>;
47
90
  export interface GitHubAuthModesCliArgs {
@@ -49,6 +92,8 @@ export interface GitHubAuthModesCliArgs {
49
92
  repo?: string;
50
93
  json?: boolean;
51
94
  runGh?: GhRunner;
95
+ expectedLogin?: string;
96
+ expectedPrincipal?: ExpectedGithubWorkerPrincipal | null;
52
97
  }
53
98
  export declare function githubAuthModesMain(args: GitHubAuthModesCliArgs): number;
54
99
  export { probeRuntimeCapabilities };