@skrr-ai/cli 0.1.127 → 0.1.129

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 (165) hide show
  1. package/dist/base-command.d.ts +35 -0
  2. package/dist/base-command.js +107 -0
  3. package/dist/commands/agenda/trigger/list.d.ts +2 -1
  4. package/dist/commands/agenda/trigger/list.js +55 -6
  5. package/dist/commands/agents/chat.js +7 -0
  6. package/dist/commands/agents/create.js +3 -1
  7. package/dist/commands/browser/persistent-profile.d.ts +22 -0
  8. package/dist/commands/browser/persistent-profile.js +46 -0
  9. package/dist/commands/commitments/action-history.js +1 -1
  10. package/dist/commands/commitments/cases.js +3 -3
  11. package/dist/commands/commitments/check-now.d.ts +10 -2
  12. package/dist/commands/commitments/check-now.js +53 -3
  13. package/dist/commands/commitments/create.d.ts +6 -14
  14. package/dist/commands/commitments/create.js +64 -7
  15. package/dist/commands/commitments/doctor.js +3 -1
  16. package/dist/commands/commitments/effective-policy.d.ts +11 -1
  17. package/dist/commands/commitments/effective-policy.js +90 -4
  18. package/dist/commands/commitments/explain.js +10 -1
  19. package/dist/commands/commitments/hypotheses.js +12 -12
  20. package/dist/commands/commitments/next-tick.js +5 -0
  21. package/dist/commands/commitments/observations.js +18 -17
  22. package/dist/commands/commitments/pack/list.js +2 -1
  23. package/dist/commands/commitments/pack/plan.d.ts +6 -0
  24. package/dist/commands/commitments/pack/plan.js +19 -0
  25. package/dist/commands/commitments/plan.d.ts +14 -0
  26. package/dist/commands/commitments/plan.js +32 -0
  27. package/dist/commands/commitments/preflight.d.ts +20 -1
  28. package/dist/commands/commitments/preflight.js +118 -12
  29. package/dist/commands/commitments/show.d.ts +9 -0
  30. package/dist/commands/commitments/show.js +14 -1
  31. package/dist/commands/commitments/trigger-status.js +8 -0
  32. package/dist/commands/commitments/update.d.ts +1 -0
  33. package/dist/commands/commitments/update.js +45 -4
  34. package/dist/commands/commitments/wake-capabilities.js +13 -0
  35. package/dist/commands/compass/archive.js +4 -0
  36. package/dist/commands/compass/commitments.js +15 -1
  37. package/dist/commands/compass/create.js +11 -3
  38. package/dist/commands/compass/effective-policy.d.ts +1 -0
  39. package/dist/commands/compass/effective-policy.js +8 -7
  40. package/dist/commands/compass/revert.d.ts +8 -0
  41. package/dist/commands/compass/revert.js +25 -1
  42. package/dist/commands/compass/revisions.d.ts +6 -0
  43. package/dist/commands/compass/revisions.js +26 -2
  44. package/dist/commands/compass/tactic.d.ts +10 -3
  45. package/dist/commands/compass/tactic.js +22 -7
  46. package/dist/commands/compass/update.js +19 -3
  47. package/dist/commands/followups/act.js +1 -1
  48. package/dist/commands/followups/cancel.js +13 -0
  49. package/dist/commands/followups/observability.js +20 -1
  50. package/dist/commands/followups/plans/create.js +12 -1
  51. package/dist/commands/followups/show.js +13 -0
  52. package/dist/commands/followups/watch.js +1 -1
  53. package/dist/commands/goals/planner-config.d.ts +11 -0
  54. package/dist/commands/goals/planner-config.js +16 -0
  55. package/dist/commands/harnesses/list.js +1 -1
  56. package/dist/commands/machines/update.d.ts +5 -3
  57. package/dist/commands/machines/update.js +70 -9
  58. package/dist/commands/northstars/create.js +3 -1
  59. package/dist/commands/northstars/governance/configure.d.ts +4 -0
  60. package/dist/commands/northstars/governance/configure.js +76 -10
  61. package/dist/commands/northstars/intents/decide.d.ts +6 -0
  62. package/dist/commands/northstars/intents/decide.js +68 -15
  63. package/dist/commands/northstars/intents/list.d.ts +2 -1
  64. package/dist/commands/northstars/intents/list.js +15 -5
  65. package/dist/commands/northstars/intents.d.ts +26 -0
  66. package/dist/commands/northstars/intents.js +49 -0
  67. package/dist/commands/northstars/list.d.ts +1 -0
  68. package/dist/commands/northstars/list.js +24 -3
  69. package/dist/commands/northstars/recommendations/list.d.ts +1 -1
  70. package/dist/commands/northstars/recommendations/list.js +3 -3
  71. package/dist/commands/northstars/recommendations.d.ts +19 -0
  72. package/dist/commands/northstars/recommendations.js +42 -0
  73. package/dist/commands/northstars/review-now.d.ts +1 -0
  74. package/dist/commands/northstars/review-now.js +15 -4
  75. package/dist/commands/northstars/reviews.d.ts +1 -0
  76. package/dist/commands/northstars/reviews.js +34 -6
  77. package/dist/commands/northstars/update.js +8 -1
  78. package/dist/commands/proposals/list.js +11 -2
  79. package/dist/commands/proposals/show.js +9 -1
  80. package/dist/commands/spaces/auto-executor/preview.d.ts +14 -1
  81. package/dist/commands/spaces/auto-executor/preview.js +35 -1
  82. package/dist/commands/spaces/auto-executor/run.d.ts +17 -0
  83. package/dist/commands/spaces/auto-executor/run.js +60 -2
  84. package/dist/commands/spaces/auto-executor/runs.d.ts +9 -1
  85. package/dist/commands/spaces/auto-executor/runs.js +18 -1
  86. package/dist/commands/spaces/files/upload.js +1 -2
  87. package/dist/commands/spaces/heartbeats/create.js +7 -5
  88. package/dist/commands/spaces/heartbeats/update.js +5 -3
  89. package/dist/commands/spaces/index.js +23 -0
  90. package/dist/commands/store/artifacts/releases.d.ts +20 -0
  91. package/dist/commands/store/artifacts/releases.js +85 -0
  92. package/dist/commands/store/library.d.ts +14 -0
  93. package/dist/commands/store/library.js +71 -0
  94. package/dist/commands/tasks/list.d.ts +48 -0
  95. package/dist/commands/tasks/list.js +141 -9
  96. package/dist/commands/tasks/remind.js +23 -11
  97. package/dist/commands/tasks/retry-schedule.js +6 -3
  98. package/dist/commands/tasks/schedule.js +29 -7
  99. package/dist/commands/tasks/show.js +4 -0
  100. package/dist/generated/agent-guides.json +69 -69
  101. package/dist/lib/agent-trigger-listing.d.ts +26 -0
  102. package/dist/lib/agent-trigger-listing.js +63 -0
  103. package/dist/lib/agentic-stream.d.ts +9 -0
  104. package/dist/lib/agentic-stream.js +152 -41
  105. package/dist/lib/command-miss.js +11 -2
  106. package/dist/lib/commitment-attention-copy.d.ts +96 -0
  107. package/dist/lib/commitment-attention-copy.js +209 -0
  108. package/dist/lib/commitment-ledgers.js +3 -1
  109. package/dist/lib/commitment-loop-status.d.ts +6 -0
  110. package/dist/lib/commitment-loop-status.js +16 -6
  111. package/dist/lib/commitments.d.ts +112 -0
  112. package/dist/lib/commitments.js +250 -2
  113. package/dist/lib/compass-body-help.d.ts +20 -0
  114. package/dist/lib/compass-body-help.js +61 -0
  115. package/dist/lib/compass-refusals.d.ts +35 -0
  116. package/dist/lib/compass-refusals.js +50 -0
  117. package/dist/lib/compasses.d.ts +82 -4
  118. package/dist/lib/compasses.js +232 -12
  119. package/dist/lib/followups.d.ts +22 -1
  120. package/dist/lib/followups.js +21 -1
  121. package/dist/lib/format.d.ts +11 -0
  122. package/dist/lib/format.js +29 -0
  123. package/dist/lib/initiatives.d.ts +9 -2
  124. package/dist/lib/initiatives.js +13 -2
  125. package/dist/lib/iso-instant.d.ts +52 -0
  126. package/dist/lib/iso-instant.js +162 -0
  127. package/dist/lib/machines.d.ts +14 -1
  128. package/dist/lib/machines.js +24 -0
  129. package/dist/lib/northstars.d.ts +201 -24
  130. package/dist/lib/northstars.js +655 -27
  131. package/dist/lib/proposal-decision-command.js +21 -1
  132. package/dist/lib/proposals.d.ts +26 -1
  133. package/dist/lib/proposals.js +167 -0
  134. package/dist/lib/rate-limit-retry.d.ts +5 -0
  135. package/dist/lib/rate-limit-retry.js +19 -0
  136. package/dist/lib/roles.d.ts +1 -1
  137. package/dist/lib/space-charter.js +9 -0
  138. package/dist/lib/store-artifact-releases.d.ts +39 -0
  139. package/dist/lib/store-artifact-releases.js +12 -0
  140. package/dist/lib/store-library.d.ts +37 -0
  141. package/dist/lib/store-library.js +12 -0
  142. package/dist/lib/task-asset-upload.js +1 -2
  143. package/dist/lib/task-calendar.d.ts +19 -0
  144. package/dist/lib/task-calendar.js +72 -0
  145. package/dist/lib/task-recurrence-outcome.js +3 -1
  146. package/dist/lib/task-recurrence-summary.d.ts +15 -0
  147. package/dist/lib/task-recurrence-summary.js +228 -0
  148. package/dist/lib/task-reminder.d.ts +28 -0
  149. package/dist/lib/task-reminder.js +50 -0
  150. package/dist/lib/triggers.d.ts +13 -2
  151. package/dist/lib/triggers.js +31 -13
  152. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +11 -1
  153. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.d.ts +123 -0
  154. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.js +160 -0
  155. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -0
  156. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +17 -0
  157. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +11 -1
  158. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.d.ts +123 -0
  159. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.js +173 -0
  160. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -0
  161. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -0
  162. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  163. package/dist/node_modules/@skrr-ai/data-provider/index.js +6922 -6828
  164. package/oclif.manifest.json +612 -62
  165. package/package.json +4 -4
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var executorStopReceipt_exports = {};
20
+ __export(executorStopReceipt_exports, {
21
+ EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS: () => EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS,
22
+ EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES: () => EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES,
23
+ EXECUTOR_STOP_RECEIPT_TYP: () => EXECUTOR_STOP_RECEIPT_TYP,
24
+ canonicalReceiptJson: () => canonicalReceiptJson,
25
+ executorStopReceiptHtu: () => executorStopReceiptHtu,
26
+ parseExecutorStopReceiptBody: () => parseExecutorStopReceiptBody,
27
+ signExecutorStopReceipt: () => signExecutorStopReceipt,
28
+ verifyExecutorStopReceipt: () => verifyExecutorStopReceipt
29
+ });
30
+ module.exports = __toCommonJS(executorStopReceipt_exports);
31
+ var import_deviceKey = require("./deviceKey.js");
32
+ const EXECUTOR_STOP_RECEIPT_TYP = "skrr-executor-stop-receipt+jwt";
33
+ const EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES = ["session_temp"];
34
+ const EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS = 24 * 60 * 60;
35
+ const MAX_ID_LENGTH = 160;
36
+ const BODY_KEYS = [
37
+ "version",
38
+ "daemonId",
39
+ "sourceId",
40
+ "sessionId",
41
+ "requestId",
42
+ "dispatchId",
43
+ "status",
44
+ "stoppedAt",
45
+ "descendants",
46
+ "erasure"
47
+ ];
48
+ const BINDING_KEYS = ["daemonId", "sourceId", "sessionId", "requestId", "dispatchId"];
49
+ const isPlainObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
50
+ const hasExactKeys = (value, keys) => Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
51
+ const isId = (value) => typeof value === "string" && value.length > 0 && value.length <= MAX_ID_LENGTH;
52
+ const isCount = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
53
+ function canonicalReceiptJson(value) {
54
+ if (value === null || typeof value !== "object")
55
+ return JSON.stringify(value);
56
+ if (Array.isArray(value))
57
+ return `[${value.map(canonicalReceiptJson).join(",")}]`;
58
+ const record = value;
59
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalReceiptJson(record[key])}`).join(",")}}`;
60
+ }
61
+ function executorStopReceiptHtu(dispatchId) {
62
+ return `https://executor-stop-receipt.skrr.invalid/v1/${encodeURIComponent(dispatchId)}`;
63
+ }
64
+ function parseExecutorStopReceiptBody(value) {
65
+ if (!isPlainObject(value) || !hasExactKeys(value, BODY_KEYS) || value.version !== 1)
66
+ return null;
67
+ if (!BINDING_KEYS.every((key) => isId(value[key])))
68
+ return null;
69
+ if (value.status !== "completed" && value.status !== "failed" && value.status !== "cancelled")
70
+ return null;
71
+ if (typeof value.stoppedAt !== "string" || Number.isNaN(Date.parse(value.stoppedAt)))
72
+ return null;
73
+ const d = value.descendants;
74
+ if (!isPlainObject(d) || !hasExactKeys(d, ["observed", "stopped", "survivors"]))
75
+ return null;
76
+ if (!isCount(d.observed) || !isCount(d.stopped) || !isCount(d.survivors))
77
+ return null;
78
+ if (d.observed !== d.stopped + d.survivors)
79
+ return null;
80
+ const e = value.erasure;
81
+ if (!isPlainObject(e))
82
+ return null;
83
+ if (e.performed === false) {
84
+ if (!hasExactKeys(e, ["performed"]))
85
+ return null;
86
+ } else if (e.performed === true) {
87
+ if (!hasExactKeys(e, ["performed", "scope", "artifacts", "bytes"]))
88
+ return null;
89
+ if (!EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES.includes(e.scope))
90
+ return null;
91
+ if (!isCount(e.artifacts) || !isCount(e.bytes))
92
+ return null;
93
+ } else {
94
+ return null;
95
+ }
96
+ return value;
97
+ }
98
+ async function signExecutorStopReceipt(signer, body, options = {}) {
99
+ const parsed = parseExecutorStopReceiptBody(body);
100
+ if (!parsed)
101
+ throw new Error("signExecutorStopReceipt: body is not a v1 executor stop receipt body");
102
+ const proof = await (0, import_deviceKey.signProofWith)(signer, {
103
+ method: "POST",
104
+ url: executorStopReceiptHtu(parsed.dispatchId),
105
+ // `accessToken` is hashed inline into the `ath` claim: the commitment to the body.
106
+ accessToken: canonicalReceiptJson(parsed),
107
+ ...options.iat !== void 0 ? { iat: options.iat } : {},
108
+ ...options.jti !== void 0 ? { jti: options.jti } : {}
109
+ }, EXECUTOR_STOP_RECEIPT_TYP);
110
+ return { body: parsed, proof };
111
+ }
112
+ function verifyExecutorStopReceipt(receipt, options) {
113
+ if (!isPlainObject(receipt) || !hasExactKeys(receipt, ["body", "proof"])) {
114
+ return { valid: false, reason: "bad_receipt" };
115
+ }
116
+ const body = parseExecutorStopReceiptBody(receipt.body);
117
+ if (!body || typeof receipt.proof !== "string")
118
+ return { valid: false, reason: "bad_receipt" };
119
+ const pin = options.pinnedPublicJwk;
120
+ if (!pin || !(0, import_deviceKey.isDevicePublicJwk)(pin))
121
+ return { valid: false, reason: "no_pin" };
122
+ const result = (0, import_deviceKey.verifyDevicePublicKeyProof)(receipt.proof, {
123
+ expectedHtm: "POST",
124
+ expectedHtu: executorStopReceiptHtu(body.dispatchId),
125
+ expectedAth: (0, import_deviceKey.tokenHash)(canonicalReceiptJson(body)),
126
+ pinnedPublicJwk: pin,
127
+ // The algorithm of the PIN, never a list: a receipt cannot pick a weaker one.
128
+ allowedAlgs: [(0, import_deviceKey.algForJwk)(pin)],
129
+ maxAgeSeconds: options.maxAgeSeconds ?? EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS,
130
+ ...options.clockSkewSeconds !== void 0 ? { clockSkewSeconds: options.clockSkewSeconds } : {},
131
+ ...options.now !== void 0 ? { now: options.now } : {}
132
+ }, EXECUTOR_STOP_RECEIPT_TYP);
133
+ if (!result.valid)
134
+ return { valid: false, reason: result.reason };
135
+ for (const key of BINDING_KEYS) {
136
+ const want = options.expected?.[key];
137
+ if (want !== void 0 && body[key] !== want)
138
+ return { valid: false, reason: "binding_mismatch" };
139
+ }
140
+ if (body.descendants.survivors > 0)
141
+ return { valid: false, reason: "not_quiescent" };
142
+ return {
143
+ valid: true,
144
+ body,
145
+ alg: result.alg,
146
+ thumbprint: (0, import_deviceKey.jwkThumbprint)(result.publicJwk),
147
+ signedAtSeconds: result.claims.iat
148
+ };
149
+ }
150
+ // Annotate the CommonJS export names for ESM import in node:
151
+ 0 && (module.exports = {
152
+ EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS,
153
+ EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES,
154
+ EXECUTOR_STOP_RECEIPT_TYP,
155
+ canonicalReceiptJson,
156
+ executorStopReceiptHtu,
157
+ parseExecutorStopReceiptBody,
158
+ signExecutorStopReceipt,
159
+ verifyExecutorStopReceipt
160
+ });
@@ -28,6 +28,7 @@ export { buildCredentialAad, decrypt as credEnvelopeDecrypt, deserialize as cred
28
28
  export { __setKekStrategyForTest, getKekStrategy, InMemoryKek, UnavailableKek, KekUnavailableError, classifyDaemonKeychainService, isMasterKekKeychainService, isSessionKeychainService, isUserOwnedSecretKeychainService, LinuxKek, MacosKek, MASTER_KEK_KEYCHAIN_ACCOUNT, MASTER_KEK_KEYCHAIN_SERVICE, registerZeroizeHook, WindowsKek, zeroizeKekCaches, type DaemonKeychainServiceClass, type KekStrategy, } from './kek/index.js';
29
29
  export { ALLOW_LIVE_STATE_ENV, __setAccountHomeForTest, DAEMON_BYOK_KEYCHAIN_SERVICE_BASE, DAEMON_KEYCHAIN_SERVICE_BASE, HERMETIC_HOME_ENV, LEGACY_DAEMON_KEYCHAIN_SERVICE, LIVE_DEFAULT_PROFILE, LIVE_STATE_REFUSED_CODE, LiveStateRefusedError, accountHomeDir, assertMayMutateLiveKeychain, assertMayMutateLiveState, isLiveKeychainService, isLiveStateRefusal, isTestProcess, keychainSuffixForDir, liveKeychainServices, liveStateRoot, refusesLiveKeychainMutation, refusesLiveStateInTest, rethrowIfLiveStateRefusal, testProcessSignal, type LiveKeychainProbe, type LiveStateProbe, } from './liveState.js';
30
30
  export { algForJwk, buildProof as buildDeviceProof, DEVICE_KEY_ALGS, generateDeviceKeyPair, isDevicePublicJwk, jwkThumbprint, normalizeHtu, p256PublicJwkFromRaw, proofSigningInput, publicJwkFromX, signProofWith, tokenHash, verifyProof as verifyDeviceProof, type DeviceKeyAlg, type DeviceKeyPair, type DeviceKeySigner, type DevicePublicJwk, type DeviceProofClaims, type DeviceProofHeader, type Ed25519PrivateJwk, type Ed25519PublicJwk, type P256PublicJwk, type VerifyFailureReason as DeviceProofVerifyFailureReason, type VerifyFailure as DeviceProofVerifyFailure, type VerifyOptions as DeviceProofVerifyOptions, type VerifySuccess as DeviceProofVerifySuccess, } from './deviceKey.js';
31
+ export { canonicalReceiptJson, EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS, EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES, EXECUTOR_STOP_RECEIPT_TYP, executorStopReceiptHtu, parseExecutorStopReceiptBody, signExecutorStopReceipt, verifyExecutorStopReceipt, type ExecutorStopReceipt, type ExecutorStopReceiptBody, type ExecutorStopReceiptErasureScope, type ExecutorStopReceiptFailureReason, type ExecutorStopReceiptStatus, type SignExecutorStopReceiptOptions, type VerifyExecutorStopReceiptOptions, type VerifyExecutorStopReceiptResult, } from './executorStopReceipt.js';
31
32
  export { softwareEd25519Handle, type DeviceKeyCustody, type DeviceKeyHandle } from './deviceKeyHandle.js';
32
33
  export { AUTH_SERVER_METADATA_PATH, readServerDeviceKeyAlgs, SERVER_DEVICE_KEY_ALGS_FIELD, } from './deviceKeyServerSupport.js';
33
34
  export { configureHardwareDeviceKeyHelper, HARDWARE_KEY_HELPER_NAME, HardwareDeviceKeyError, probeHardwareDeviceKey, resolveHardwareDeviceKeyHelper, type HardwareDeviceKeyErrorCode, type HardwareKeyCapability, } from './deviceKeyHardware.js';
@@ -46,6 +46,9 @@ __export(index_exports, {
46
46
  DPOP_PROOF_REQUIRED: () => import_dpop.DPOP_PROOF_REQUIRED,
47
47
  DPOP_PROOF_TYP: () => import_dpop.DPOP_PROOF_TYP,
48
48
  DPOP_PROOF_VERIFY_UNAVAILABLE: () => import_dpop.DPOP_PROOF_VERIFY_UNAVAILABLE,
49
+ EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS: () => import_executorStopReceipt.EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS,
50
+ EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES: () => import_executorStopReceipt.EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES,
51
+ EXECUTOR_STOP_RECEIPT_TYP: () => import_executorStopReceipt.EXECUTOR_STOP_RECEIPT_TYP,
49
52
  EXIT_NEEDS_REAUTH: () => import_messages.EXIT_NEEDS_REAUTH,
50
53
  FIRST_PARTY_HARNESS_CHANNELS: () => import_firstPartyHarnessChannels.FIRST_PARTY_HARNESS_CHANNELS,
51
54
  FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX: () => import_firstPartyHarnessHome.FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX,
@@ -148,6 +151,7 @@ __export(index_exports, {
148
151
  buildManifestMessage: () => import_releaseManifest.buildManifestMessage,
149
152
  buildPairAad: () => import_daemonPair.buildPairAad,
150
153
  buildPairUrl: () => import_daemonPair.buildPairUrl,
154
+ canonicalReceiptJson: () => import_executorStopReceipt.canonicalReceiptJson,
151
155
  challenge: () => import_pkce.challenge,
152
156
  classifyDaemonKeychainService: () => import_kek.classifyDaemonKeychainService,
153
157
  classifyRefreshResponse: () => import_refreshClassification.classifyRefreshResponse,
@@ -186,6 +190,7 @@ __export(index_exports, {
186
190
  enrollWithServer: () => import_deviceIdentityBridge.enrollWithServer,
187
191
  ensureMachineDaemonId: () => import_machineId.ensureMachineDaemonId,
188
192
  envHasProxy: () => import_spawnEnv.envHasProxy,
193
+ executorStopReceiptHtu: () => import_executorStopReceipt.executorStopReceiptHtu,
189
194
  findLegacyLocalState: () => import_legacyStatePreflight.findLegacyLocalState,
190
195
  firstPartyHarnessBinDir: () => import_firstPartyHarnessHome.firstPartyHarnessBinDir,
191
196
  firstPartyHarnessChannelPrefix: () => import_firstPartyHarnessChannels.firstPartyHarnessChannelPrefix,
@@ -270,6 +275,7 @@ __export(index_exports, {
270
275
  pairBase64UrlToUint8Array: () => import_daemonPair.base64UrlToUint8Array,
271
276
  pairUint8ArrayToBase64: () => import_daemonPair.uint8ArrayToBase64,
272
277
  pairUint8ArrayToBase64Url: () => import_daemonPair.uint8ArrayToBase64Url,
278
+ parseExecutorStopReceiptBody: () => import_executorStopReceipt.parseExecutorStopReceiptBody,
273
279
  parsePairBundle: () => import_daemonPair.parsePairBundle,
274
280
  parsePairPlaintext: () => import_daemonPair.parsePairPlaintext,
275
281
  parsePairUrl: () => import_daemonPair.parsePairUrl,
@@ -309,6 +315,7 @@ __export(index_exports, {
309
315
  sanitizeSpawnEnv: () => import_spawnEnv.sanitizeSpawnEnv,
310
316
  setMachineUuidProfile: () => import_machineUuid.setMachineUuidProfile,
311
317
  shutdownCredEnvelope: () => import_credentialEnvelopeBridge.shutdownCredEnvelope,
318
+ signExecutorStopReceipt: () => import_executorStopReceipt.signExecutorStopReceipt,
312
319
  signProofWith: () => import_deviceKey.signProofWith,
313
320
  signServerRequest: () => import_deviceIdentityBridge.signServerRequest,
314
321
  signWithCandidate: () => import_deviceIdentityBridge.signWithCandidate,
@@ -322,6 +329,7 @@ __export(index_exports, {
322
329
  verifyChallenge: () => import_pkce.verifyChallenge,
323
330
  verifyDeviceProof: () => import_deviceKey.verifyProof,
324
331
  verifyDpopProof: () => import_dpop.verifyDpopProof,
332
+ verifyExecutorStopReceipt: () => import_executorStopReceipt.verifyExecutorStopReceipt,
325
333
  verifyIndependentMachineReservation: () => import_independentMachineReservation.verifyIndependentMachineReservation,
326
334
  verifyManifest: () => import_releaseManifest.verifyManifest,
327
335
  withAuthLock: () => import_refresh.withAuthLock,
@@ -351,6 +359,7 @@ var import_credentialEnvelope = require("./credentialEnvelope.js");
351
359
  var import_kek = require("./kek/index.js");
352
360
  var import_liveState = require("./liveState.js");
353
361
  var import_deviceKey = require("./deviceKey.js");
362
+ var import_executorStopReceipt = require("./executorStopReceipt.js");
354
363
  var import_deviceKeyHandle = require("./deviceKeyHandle.js");
355
364
  var import_deviceKeyServerSupport = require("./deviceKeyServerSupport.js");
356
365
  var import_deviceKeyHardware = require("./deviceKeyHardware.js");
@@ -407,6 +416,9 @@ var import_independentMachineReservation = require("./independentMachineReservat
407
416
  DPOP_PROOF_REQUIRED,
408
417
  DPOP_PROOF_TYP,
409
418
  DPOP_PROOF_VERIFY_UNAVAILABLE,
419
+ EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS,
420
+ EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES,
421
+ EXECUTOR_STOP_RECEIPT_TYP,
410
422
  EXIT_NEEDS_REAUTH,
411
423
  FIRST_PARTY_HARNESS_CHANNELS,
412
424
  FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX,
@@ -509,6 +521,7 @@ var import_independentMachineReservation = require("./independentMachineReservat
509
521
  buildManifestMessage,
510
522
  buildPairAad,
511
523
  buildPairUrl,
524
+ canonicalReceiptJson,
512
525
  challenge,
513
526
  classifyDaemonKeychainService,
514
527
  classifyRefreshResponse,
@@ -547,6 +560,7 @@ var import_independentMachineReservation = require("./independentMachineReservat
547
560
  enrollWithServer,
548
561
  ensureMachineDaemonId,
549
562
  envHasProxy,
563
+ executorStopReceiptHtu,
550
564
  findLegacyLocalState,
551
565
  firstPartyHarnessBinDir,
552
566
  firstPartyHarnessChannelPrefix,
@@ -631,6 +645,7 @@ var import_independentMachineReservation = require("./independentMachineReservat
631
645
  pairBase64UrlToUint8Array,
632
646
  pairUint8ArrayToBase64,
633
647
  pairUint8ArrayToBase64Url,
648
+ parseExecutorStopReceiptBody,
634
649
  parsePairBundle,
635
650
  parsePairPlaintext,
636
651
  parsePairUrl,
@@ -670,6 +685,7 @@ var import_independentMachineReservation = require("./independentMachineReservat
670
685
  sanitizeSpawnEnv,
671
686
  setMachineUuidProfile,
672
687
  shutdownCredEnvelope,
688
+ signExecutorStopReceipt,
673
689
  signProofWith,
674
690
  signServerRequest,
675
691
  signWithCandidate,
@@ -683,6 +699,7 @@ var import_independentMachineReservation = require("./independentMachineReservat
683
699
  verifyChallenge,
684
700
  verifyDeviceProof,
685
701
  verifyDpopProof,
702
+ verifyExecutorStopReceipt,
686
703
  verifyIndependentMachineReservation,
687
704
  verifyManifest,
688
705
  withAuthLock,
@@ -101,6 +101,7 @@ const SESSION_TURN_DIFF_LIVE = 'session_turn_diff_live_v1';
101
101
  const WORKSPACE_ISOLATION_PROBE = 'workspace_isolation_probe_v1';
102
102
  const TOOL_RESPONSE_OUTBOX = 'tool_response_outbox_v1';
103
103
  const SESSION_STEER = 'session_steer_v1';
104
+ const QUESTION_ACK = 'question_ack_v1';
104
105
  // ---------------------------------------------------------------------------
105
106
  // Connection, identity, liveness
106
107
  // ---------------------------------------------------------------------------
@@ -267,7 +268,16 @@ const SESSIONS = [
267
268
  delivery: 'acked',
268
269
  answeredBy: 'daemon:claude:question:response',
269
270
  }),
270
- toDaemon('daemon:claude:question:response', "A person's answer to a question."),
271
+ // A daemon that announces `question_ack_v1` says whether it TOOK the answer: the
272
+ // question was still pending and the harness has it ('delivered'), or the daemon had
273
+ // already timed out or been cancelled ('not_pending'). Without the capability the
274
+ // worker settles an answer at the relay handoff, as it always did.
275
+ toDaemon('daemon:claude:question:response', "A person's answer to a question.", {
276
+ delivery: 'acked',
277
+ answeredBy: 'daemon:claude:question:ack',
278
+ capability: QUESTION_ACK,
279
+ }),
280
+ toServer('daemon:claude:question:ack', "Whether this daemon took a person's answer to a question, or had already stopped waiting.", { capability: QUESTION_ACK, payload: { kind: 'zod', schema: 'daemonQuestionAckSchema' } }),
271
281
  ];
272
282
  // ---------------------------------------------------------------------------
273
283
  // Tools (the cloud agent's tool calls, proxied to the machine)
@@ -0,0 +1,123 @@
1
+ /**
2
+ * executorStopReceipt.ts — a daemon-signed proof that a scheduled session stopped.
3
+ *
4
+ * OSK-20777, Gate 1 (docs/architecture/scheduled-publication-custody-enrollment-2026-11-01.md).
5
+ * Today the executor's terminal proof is the daemon reporting about itself over its own socket: the
6
+ * dispatch tuple is checked for consistency but nothing is signed, nothing covers descendant
7
+ * processes, and nothing says what was erased. A receipt is that report, signed with the device key
8
+ * the server already pins on the daemon's row, so a settlement that depends on it depends on the
9
+ * machine and not on whoever reached the socket.
10
+ *
11
+ * No new cryptography. A receipt is a DPoP-shaped device proof (`deviceKey.ts`) under its OWN header
12
+ * `typ`, so the two can never stand in for each other: a request proof fails here with `bad_typ`, and a
13
+ * receipt fails the request verifier the same way. The proof's `ath` commits to the SHA-256 of the
14
+ * canonical receipt body, its `htu` names the dispatch, and the pin decides which key may sign it.
15
+ *
16
+ * Verification is deliberately strict and returns a reason, never a boolean:
17
+ * - a pin is REQUIRED (a receipt with no key to compare against has no authority);
18
+ * - the algorithm admitted is the algorithm of the pin, never a list, so a receipt cannot choose a
19
+ * weaker algorithm than the device enrolled with;
20
+ * - the body is a closed shape with bounded fields;
21
+ * - every field the caller expects (the dispatch the server issued) must match exactly;
22
+ * - a descendant that survived is `not_quiescent`, not a valid receipt with a footnote;
23
+ * - erasure is claimed only for the one scope the owner allowed, `session_temp`.
24
+ *
25
+ * Freshness is the caller's decision (`maxAgeSeconds`). A receipt can legitimately arrive hours after it
26
+ * was signed, replayed from the daemon's response WAL after a reconnect, so it has no short window of its
27
+ * own. Replay protection is the dispatch binding: the server accepts one receipt per `dispatchId`.
28
+ */
29
+ import { type DeviceKeyAlg, type DeviceKeySigner, type DevicePublicJwk, type VerifyFailureReason } from './deviceKey.js';
30
+ /** Header `typ` of an executor stop receipt. Distinct from `DEVICE_PROOF_TYP` on purpose. */
31
+ export declare const EXECUTOR_STOP_RECEIPT_TYP = "skrr-executor-stop-receipt+jwt";
32
+ /** What the daemon may claim to have erased. One scope, decided by the owner (2026-11-01). */
33
+ export declare const EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES: readonly ["session_temp"];
34
+ export type ExecutorStopReceiptErasureScope = (typeof EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES)[number];
35
+ /** Default ceiling on a receipt's age, for a caller that does not decide: one day. */
36
+ export declare const EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS: number;
37
+ export type ExecutorStopReceiptStatus = 'completed' | 'failed' | 'cancelled';
38
+ export interface ExecutorStopReceiptBody {
39
+ version: 1;
40
+ daemonId: string;
41
+ /** The scheduled Run (or Source) the dispatch belongs to. */
42
+ sourceId: string;
43
+ sessionId: string;
44
+ requestId: string;
45
+ dispatchId: string;
46
+ status: ExecutorStopReceiptStatus;
47
+ /** ISO-8601, when the session's process tree was last observed stopped. */
48
+ stoppedAt: string;
49
+ /** Counted after the stop, not assumed. `observed === stopped + survivors`. */
50
+ descendants: {
51
+ observed: number;
52
+ stopped: number;
53
+ survivors: number;
54
+ };
55
+ /** `performed: false` carries no scope: nothing was erased and nothing is claimed. */
56
+ erasure: {
57
+ performed: false;
58
+ } | {
59
+ performed: true;
60
+ scope: ExecutorStopReceiptErasureScope;
61
+ artifacts: number;
62
+ bytes: number;
63
+ };
64
+ }
65
+ export interface ExecutorStopReceipt {
66
+ body: ExecutorStopReceiptBody;
67
+ /** The device-key proof (JWS compact) that commits to `body`. */
68
+ proof: string;
69
+ }
70
+ export type ExecutorStopReceiptFailureReason = VerifyFailureReason | 'bad_receipt' | 'no_pin' | 'binding_mismatch' | 'not_quiescent';
71
+ export type VerifyExecutorStopReceiptResult = {
72
+ valid: true;
73
+ body: ExecutorStopReceiptBody;
74
+ alg: DeviceKeyAlg;
75
+ thumbprint: string;
76
+ signedAtSeconds: number;
77
+ } | {
78
+ valid: false;
79
+ reason: ExecutorStopReceiptFailureReason;
80
+ };
81
+ declare const BINDING_KEYS: readonly ["daemonId", "sourceId", "sessionId", "requestId", "dispatchId"];
82
+ /**
83
+ * Stable JSON: object keys sorted at every level, no whitespace. The signer and the verifier must
84
+ * produce the same bytes from the same body, whatever order a transport reordered its keys into.
85
+ */
86
+ export declare function canonicalReceiptJson(value: unknown): string;
87
+ /** The URI a receipt's proof names. Reserved `.invalid` host: it can never be a real request target. */
88
+ export declare function executorStopReceiptHtu(dispatchId: string): string;
89
+ /** Shape check only. Returns the body as typed, or null when it is not exactly a v1 receipt body. */
90
+ export declare function parseExecutorStopReceiptBody(value: unknown): ExecutorStopReceiptBody | null;
91
+ export interface SignExecutorStopReceiptOptions {
92
+ /** Test hooks; production leaves both unset. */
93
+ iat?: number;
94
+ jti?: string;
95
+ }
96
+ /**
97
+ * Sign a receipt with any `DeviceKeySigner` (a software key, or a hardware handle). Throws on a body
98
+ * that is not exactly a v1 receipt body, so a daemon bug cannot produce a signed receipt the server
99
+ * would only reject later.
100
+ */
101
+ export declare function signExecutorStopReceipt(signer: DeviceKeySigner, body: ExecutorStopReceiptBody, options?: SignExecutorStopReceiptOptions): Promise<ExecutorStopReceipt>;
102
+ export interface VerifyExecutorStopReceiptOptions {
103
+ /** The key pinned on the daemon's row. REQUIRED: without it nothing authorizes the signature. */
104
+ pinnedPublicJwk: DevicePublicJwk | undefined;
105
+ /**
106
+ * What the server issued for this dispatch. Every key given must equal the receipt's, so a receipt for
107
+ * another daemon, Run, session, request or dispatch is `binding_mismatch`.
108
+ */
109
+ expected?: Partial<Pick<ExecutorStopReceiptBody, (typeof BINDING_KEYS)[number]>>;
110
+ /** Ceiling on the receipt's age in seconds. Defaults to one day. */
111
+ maxAgeSeconds?: number;
112
+ /** Tolerated clock skew in seconds. Defaults to the proof verifier's. */
113
+ clockSkewSeconds?: number;
114
+ /** Testing hook. */
115
+ now?: number;
116
+ }
117
+ /**
118
+ * Verify a receipt against the pinned key. Order matters: shape, then signature and pin, then the
119
+ * binding to the dispatch, then what the body claims. A forged receipt is reported as the forgery it
120
+ * is (`sig_invalid`, `pin_mismatch`, `ath_mismatch`), not as a quiescence failure.
121
+ */
122
+ export declare function verifyExecutorStopReceipt(receipt: unknown, options: VerifyExecutorStopReceiptOptions): VerifyExecutorStopReceiptResult;
123
+ export {};
@@ -0,0 +1,173 @@
1
+ /**
2
+ * executorStopReceipt.ts — a daemon-signed proof that a scheduled session stopped.
3
+ *
4
+ * OSK-20777, Gate 1 (docs/architecture/scheduled-publication-custody-enrollment-2026-11-01.md).
5
+ * Today the executor's terminal proof is the daemon reporting about itself over its own socket: the
6
+ * dispatch tuple is checked for consistency but nothing is signed, nothing covers descendant
7
+ * processes, and nothing says what was erased. A receipt is that report, signed with the device key
8
+ * the server already pins on the daemon's row, so a settlement that depends on it depends on the
9
+ * machine and not on whoever reached the socket.
10
+ *
11
+ * No new cryptography. A receipt is a DPoP-shaped device proof (`deviceKey.ts`) under its OWN header
12
+ * `typ`, so the two can never stand in for each other: a request proof fails here with `bad_typ`, and a
13
+ * receipt fails the request verifier the same way. The proof's `ath` commits to the SHA-256 of the
14
+ * canonical receipt body, its `htu` names the dispatch, and the pin decides which key may sign it.
15
+ *
16
+ * Verification is deliberately strict and returns a reason, never a boolean:
17
+ * - a pin is REQUIRED (a receipt with no key to compare against has no authority);
18
+ * - the algorithm admitted is the algorithm of the pin, never a list, so a receipt cannot choose a
19
+ * weaker algorithm than the device enrolled with;
20
+ * - the body is a closed shape with bounded fields;
21
+ * - every field the caller expects (the dispatch the server issued) must match exactly;
22
+ * - a descendant that survived is `not_quiescent`, not a valid receipt with a footnote;
23
+ * - erasure is claimed only for the one scope the owner allowed, `session_temp`.
24
+ *
25
+ * Freshness is the caller's decision (`maxAgeSeconds`). A receipt can legitimately arrive hours after it
26
+ * was signed, replayed from the daemon's response WAL after a reconnect, so it has no short window of its
27
+ * own. Replay protection is the dispatch binding: the server accepts one receipt per `dispatchId`.
28
+ */
29
+ import { algForJwk, isDevicePublicJwk, jwkThumbprint, signProofWith, tokenHash, verifyDevicePublicKeyProof, } from './deviceKey.js';
30
+ /** Header `typ` of an executor stop receipt. Distinct from `DEVICE_PROOF_TYP` on purpose. */
31
+ export const EXECUTOR_STOP_RECEIPT_TYP = 'skrr-executor-stop-receipt+jwt';
32
+ /** What the daemon may claim to have erased. One scope, decided by the owner (2026-11-01). */
33
+ export const EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES = ['session_temp'];
34
+ /** Default ceiling on a receipt's age, for a caller that does not decide: one day. */
35
+ export const EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS = 24 * 60 * 60;
36
+ const MAX_ID_LENGTH = 160;
37
+ const BODY_KEYS = [
38
+ 'version',
39
+ 'daemonId',
40
+ 'sourceId',
41
+ 'sessionId',
42
+ 'requestId',
43
+ 'dispatchId',
44
+ 'status',
45
+ 'stoppedAt',
46
+ 'descendants',
47
+ 'erasure',
48
+ ];
49
+ const BINDING_KEYS = ['daemonId', 'sourceId', 'sessionId', 'requestId', 'dispatchId'];
50
+ const isPlainObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
51
+ const hasExactKeys = (value, keys) => Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
52
+ const isId = (value) => typeof value === 'string' && value.length > 0 && value.length <= MAX_ID_LENGTH;
53
+ const isCount = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
54
+ /**
55
+ * Stable JSON: object keys sorted at every level, no whitespace. The signer and the verifier must
56
+ * produce the same bytes from the same body, whatever order a transport reordered its keys into.
57
+ */
58
+ export function canonicalReceiptJson(value) {
59
+ if (value === null || typeof value !== 'object')
60
+ return JSON.stringify(value);
61
+ if (Array.isArray(value))
62
+ return `[${value.map(canonicalReceiptJson).join(',')}]`;
63
+ const record = value;
64
+ return `{${Object.keys(record)
65
+ .sort()
66
+ .map((key) => `${JSON.stringify(key)}:${canonicalReceiptJson(record[key])}`)
67
+ .join(',')}}`;
68
+ }
69
+ /** The URI a receipt's proof names. Reserved `.invalid` host: it can never be a real request target. */
70
+ export function executorStopReceiptHtu(dispatchId) {
71
+ return `https://executor-stop-receipt.skrr.invalid/v1/${encodeURIComponent(dispatchId)}`;
72
+ }
73
+ /** Shape check only. Returns the body as typed, or null when it is not exactly a v1 receipt body. */
74
+ export function parseExecutorStopReceiptBody(value) {
75
+ if (!isPlainObject(value) || !hasExactKeys(value, BODY_KEYS) || value.version !== 1)
76
+ return null;
77
+ if (!BINDING_KEYS.every((key) => isId(value[key])))
78
+ return null;
79
+ if (value.status !== 'completed' && value.status !== 'failed' && value.status !== 'cancelled')
80
+ return null;
81
+ if (typeof value.stoppedAt !== 'string' || Number.isNaN(Date.parse(value.stoppedAt)))
82
+ return null;
83
+ const d = value.descendants;
84
+ if (!isPlainObject(d) || !hasExactKeys(d, ['observed', 'stopped', 'survivors']))
85
+ return null;
86
+ if (!isCount(d.observed) || !isCount(d.stopped) || !isCount(d.survivors))
87
+ return null;
88
+ if (d.observed !== d.stopped + d.survivors)
89
+ return null;
90
+ const e = value.erasure;
91
+ if (!isPlainObject(e))
92
+ return null;
93
+ if (e.performed === false) {
94
+ if (!hasExactKeys(e, ['performed']))
95
+ return null;
96
+ }
97
+ else if (e.performed === true) {
98
+ if (!hasExactKeys(e, ['performed', 'scope', 'artifacts', 'bytes']))
99
+ return null;
100
+ if (!EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES.includes(e.scope))
101
+ return null;
102
+ if (!isCount(e.artifacts) || !isCount(e.bytes))
103
+ return null;
104
+ }
105
+ else {
106
+ return null;
107
+ }
108
+ return value;
109
+ }
110
+ /**
111
+ * Sign a receipt with any `DeviceKeySigner` (a software key, or a hardware handle). Throws on a body
112
+ * that is not exactly a v1 receipt body, so a daemon bug cannot produce a signed receipt the server
113
+ * would only reject later.
114
+ */
115
+ export async function signExecutorStopReceipt(signer, body, options = {}) {
116
+ const parsed = parseExecutorStopReceiptBody(body);
117
+ if (!parsed)
118
+ throw new Error('signExecutorStopReceipt: body is not a v1 executor stop receipt body');
119
+ const proof = await signProofWith(signer, {
120
+ method: 'POST',
121
+ url: executorStopReceiptHtu(parsed.dispatchId),
122
+ // `accessToken` is hashed inline into the `ath` claim: the commitment to the body.
123
+ accessToken: canonicalReceiptJson(parsed),
124
+ ...(options.iat !== undefined ? { iat: options.iat } : {}),
125
+ ...(options.jti !== undefined ? { jti: options.jti } : {}),
126
+ }, EXECUTOR_STOP_RECEIPT_TYP);
127
+ return { body: parsed, proof };
128
+ }
129
+ /**
130
+ * Verify a receipt against the pinned key. Order matters: shape, then signature and pin, then the
131
+ * binding to the dispatch, then what the body claims. A forged receipt is reported as the forgery it
132
+ * is (`sig_invalid`, `pin_mismatch`, `ath_mismatch`), not as a quiescence failure.
133
+ */
134
+ export function verifyExecutorStopReceipt(receipt, options) {
135
+ if (!isPlainObject(receipt) || !hasExactKeys(receipt, ['body', 'proof'])) {
136
+ return { valid: false, reason: 'bad_receipt' };
137
+ }
138
+ const body = parseExecutorStopReceiptBody(receipt.body);
139
+ if (!body || typeof receipt.proof !== 'string')
140
+ return { valid: false, reason: 'bad_receipt' };
141
+ const pin = options.pinnedPublicJwk;
142
+ if (!pin || !isDevicePublicJwk(pin))
143
+ return { valid: false, reason: 'no_pin' };
144
+ const result = verifyDevicePublicKeyProof(receipt.proof, {
145
+ expectedHtm: 'POST',
146
+ expectedHtu: executorStopReceiptHtu(body.dispatchId),
147
+ expectedAth: tokenHash(canonicalReceiptJson(body)),
148
+ pinnedPublicJwk: pin,
149
+ // The algorithm of the PIN, never a list: a receipt cannot pick a weaker one.
150
+ allowedAlgs: [algForJwk(pin)],
151
+ maxAgeSeconds: options.maxAgeSeconds ?? EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS,
152
+ ...(options.clockSkewSeconds !== undefined
153
+ ? { clockSkewSeconds: options.clockSkewSeconds }
154
+ : {}),
155
+ ...(options.now !== undefined ? { now: options.now } : {}),
156
+ }, EXECUTOR_STOP_RECEIPT_TYP);
157
+ if (!result.valid)
158
+ return { valid: false, reason: result.reason };
159
+ for (const key of BINDING_KEYS) {
160
+ const want = options.expected?.[key];
161
+ if (want !== undefined && body[key] !== want)
162
+ return { valid: false, reason: 'binding_mismatch' };
163
+ }
164
+ if (body.descendants.survivors > 0)
165
+ return { valid: false, reason: 'not_quiescent' };
166
+ return {
167
+ valid: true,
168
+ body,
169
+ alg: result.alg,
170
+ thumbprint: jwkThumbprint(result.publicJwk),
171
+ signedAtSeconds: result.claims.iat,
172
+ };
173
+ }
@@ -28,6 +28,7 @@ export { buildCredentialAad, decrypt as credEnvelopeDecrypt, deserialize as cred
28
28
  export { __setKekStrategyForTest, getKekStrategy, InMemoryKek, UnavailableKek, KekUnavailableError, classifyDaemonKeychainService, isMasterKekKeychainService, isSessionKeychainService, isUserOwnedSecretKeychainService, LinuxKek, MacosKek, MASTER_KEK_KEYCHAIN_ACCOUNT, MASTER_KEK_KEYCHAIN_SERVICE, registerZeroizeHook, WindowsKek, zeroizeKekCaches, type DaemonKeychainServiceClass, type KekStrategy, } from './kek/index.js';
29
29
  export { ALLOW_LIVE_STATE_ENV, __setAccountHomeForTest, DAEMON_BYOK_KEYCHAIN_SERVICE_BASE, DAEMON_KEYCHAIN_SERVICE_BASE, HERMETIC_HOME_ENV, LEGACY_DAEMON_KEYCHAIN_SERVICE, LIVE_DEFAULT_PROFILE, LIVE_STATE_REFUSED_CODE, LiveStateRefusedError, accountHomeDir, assertMayMutateLiveKeychain, assertMayMutateLiveState, isLiveKeychainService, isLiveStateRefusal, isTestProcess, keychainSuffixForDir, liveKeychainServices, liveStateRoot, refusesLiveKeychainMutation, refusesLiveStateInTest, rethrowIfLiveStateRefusal, testProcessSignal, type LiveKeychainProbe, type LiveStateProbe, } from './liveState.js';
30
30
  export { algForJwk, buildProof as buildDeviceProof, DEVICE_KEY_ALGS, generateDeviceKeyPair, isDevicePublicJwk, jwkThumbprint, normalizeHtu, p256PublicJwkFromRaw, proofSigningInput, publicJwkFromX, signProofWith, tokenHash, verifyProof as verifyDeviceProof, type DeviceKeyAlg, type DeviceKeyPair, type DeviceKeySigner, type DevicePublicJwk, type DeviceProofClaims, type DeviceProofHeader, type Ed25519PrivateJwk, type Ed25519PublicJwk, type P256PublicJwk, type VerifyFailureReason as DeviceProofVerifyFailureReason, type VerifyFailure as DeviceProofVerifyFailure, type VerifyOptions as DeviceProofVerifyOptions, type VerifySuccess as DeviceProofVerifySuccess, } from './deviceKey.js';
31
+ export { canonicalReceiptJson, EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS, EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES, EXECUTOR_STOP_RECEIPT_TYP, executorStopReceiptHtu, parseExecutorStopReceiptBody, signExecutorStopReceipt, verifyExecutorStopReceipt, type ExecutorStopReceipt, type ExecutorStopReceiptBody, type ExecutorStopReceiptErasureScope, type ExecutorStopReceiptFailureReason, type ExecutorStopReceiptStatus, type SignExecutorStopReceiptOptions, type VerifyExecutorStopReceiptOptions, type VerifyExecutorStopReceiptResult, } from './executorStopReceipt.js';
31
32
  export { softwareEd25519Handle, type DeviceKeyCustody, type DeviceKeyHandle } from './deviceKeyHandle.js';
32
33
  export { AUTH_SERVER_METADATA_PATH, readServerDeviceKeyAlgs, SERVER_DEVICE_KEY_ALGS_FIELD, } from './deviceKeyServerSupport.js';
33
34
  export { configureHardwareDeviceKeyHelper, HARDWARE_KEY_HELPER_NAME, HardwareDeviceKeyError, probeHardwareDeviceKey, resolveHardwareDeviceKeyHelper, type HardwareDeviceKeyErrorCode, type HardwareKeyCapability, } from './deviceKeyHardware.js';
@@ -109,6 +109,8 @@ export { ALLOW_LIVE_STATE_ENV, __setAccountHomeForTest, DAEMON_BYOK_KEYCHAIN_SER
109
109
  // persistence + signing lives in `daemon/src/deviceIdentity.ts`.
110
110
  // Server-side verification middleware is a later PR.
111
111
  export { algForJwk, buildProof as buildDeviceProof, DEVICE_KEY_ALGS, generateDeviceKeyPair, isDevicePublicJwk, jwkThumbprint, normalizeHtu, p256PublicJwkFromRaw, proofSigningInput, publicJwkFromX, signProofWith, tokenHash, verifyProof as verifyDeviceProof, } from './deviceKey.js';
112
+ // OSK-20777 Gate 1 — a daemon-signed proof that a scheduled session stopped, under its own proof `typ`.
113
+ export { canonicalReceiptJson, EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS, EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES, EXECUTOR_STOP_RECEIPT_TYP, executorStopReceiptHtu, parseExecutorStopReceiptBody, signExecutorStopReceipt, verifyExecutorStopReceipt, } from './executorStopReceipt.js';
112
114
  // The handle every signing site holds instead of a private key, and the
113
115
  // hardware-bound backend behind it (docs/architecture/device-key-custody-2026-10-29.md).
114
116
  export { softwareEd25519Handle } from './deviceKeyHandle.js';
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skrr-ai/auth-core",
3
- "version": "0.4.33",
3
+ "version": "0.4.34",
4
4
  "main": "dist/cjs/index.js",
5
5
  "types": "dist/esm/index.d.ts",
6
6
  "exports": {