@skrr-ai/cli 0.1.119 → 0.1.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-command.d.ts +31 -0
- package/dist/base-command.js +117 -6
- package/dist/commands/agent/message/context.d.ts +1 -0
- package/dist/commands/agent/message/context.js +3 -2
- package/dist/commands/commands.js +11 -0
- package/dist/commands/commitments/action-proposal.d.ts +13 -0
- package/dist/commands/commitments/action-proposal.js +39 -0
- package/dist/commands/commitments/action-proposals/decide.js +8 -1
- package/dist/commands/commitments/action-proposals/execute.js +11 -1
- package/dist/commands/commitments/create.js +2 -2
- package/dist/commands/commitments/doctor.d.ts +8 -0
- package/dist/commands/commitments/doctor.js +22 -0
- package/dist/commands/commitments/execute-remediation.js +12 -1
- package/dist/commands/commitments/observations.js +2 -2
- package/dist/commands/commitments/pack/bind.d.ts +18 -1
- package/dist/commands/commitments/pack/bind.js +66 -8
- package/dist/commands/commitments/pack/plan.d.ts +4 -1
- package/dist/commands/commitments/pack/plan.js +30 -2
- package/dist/commands/commitments/update.js +8 -2
- package/dist/commands/completion/index.js +1 -1
- package/dist/commands/completion/values.js +1 -1
- package/dist/commands/computer-use/audio.d.ts +1 -0
- package/dist/commands/computer-use/audio.js +1 -0
- package/dist/commands/computer-use/browser.d.ts +1 -0
- package/dist/commands/computer-use/browser.js +1 -0
- package/dist/commands/computer-use/disable.d.ts +1 -0
- package/dist/commands/computer-use/disable.js +1 -0
- package/dist/commands/computer-use/enable.d.ts +1 -0
- package/dist/commands/computer-use/enable.js +1 -0
- package/dist/commands/computer-use/index.d.ts +1 -0
- package/dist/commands/computer-use/index.js +1 -0
- package/dist/commands/computer-use/install.d.ts +1 -0
- package/dist/commands/computer-use/install.js +1 -0
- package/dist/commands/computer-use/recover.d.ts +1 -0
- package/dist/commands/computer-use/recover.js +1 -0
- package/dist/commands/computer-use/status.d.ts +1 -0
- package/dist/commands/computer-use/status.js +1 -0
- package/dist/commands/computer-use/stop.d.ts +1 -0
- package/dist/commands/computer-use/stop.js +1 -0
- package/dist/commands/context.js +16 -11
- package/dist/commands/convos/recovery.js +1 -1
- package/dist/commands/daemon/diagnostics.d.ts +9 -0
- package/dist/commands/daemon/diagnostics.js +53 -0
- package/dist/commands/daemon/index.js +2 -0
- package/dist/commands/daemon/maintenance/attest.d.ts +15 -0
- package/dist/commands/daemon/maintenance/attest.js +52 -0
- package/dist/commands/daemon/maintenance/hold.d.ts +17 -0
- package/dist/commands/daemon/maintenance/hold.js +70 -0
- package/dist/commands/daemon/maintenance/index.d.ts +12 -0
- package/dist/commands/daemon/maintenance/index.js +30 -0
- package/dist/commands/daemon/maintenance/resume.d.ts +13 -0
- package/dist/commands/daemon/maintenance/resume.js +36 -0
- package/dist/commands/daemon/maintenance/status.d.ts +13 -0
- package/dist/commands/daemon/maintenance/status.js +36 -0
- package/dist/commands/doctor.d.ts +4 -1
- package/dist/commands/doctor.js +50 -6
- package/dist/commands/followups/plans/cancel-batch.js +6 -3
- package/dist/commands/followups/plans/capacity.js +7 -3
- package/dist/commands/followups/plans/observability.js +11 -3
- package/dist/commands/followups/plans/preferences/set.js +26 -8
- package/dist/commands/followups/plans/preferences.js +1 -1
- package/dist/commands/followups/plans/show.js +1 -1
- package/dist/commands/goals/key-results/assess.d.ts +36 -0
- package/dist/commands/goals/key-results/assess.js +110 -0
- package/dist/commands/goals/key-results/history.d.ts +30 -0
- package/dist/commands/goals/key-results/history.js +104 -0
- package/dist/commands/goals/key-results/list.d.ts +5 -4
- package/dist/commands/goals/key-results/list.js +12 -6
- package/dist/commands/goals/key-results/show.d.ts +4 -2
- package/dist/commands/goals/key-results/show.js +25 -4
- package/dist/commands/goals/key-results/update.d.ts +4 -0
- package/dist/commands/goals/key-results/update.js +18 -24
- package/dist/commands/goals/plan-now.js +12 -1
- package/dist/commands/goals/planner-compare.d.ts +30 -0
- package/dist/commands/goals/planner-compare.js +136 -0
- package/dist/commands/machines/dedicated/service/exec.d.ts +3 -0
- package/dist/commands/machines/dedicated/service/exec.js +70 -11
- package/dist/commands/machines/hosted/destroy.d.ts +1 -0
- package/dist/commands/machines/hosted/destroy.js +11 -1
- package/dist/commands/machines/hosted/exec.d.ts +1 -0
- package/dist/commands/machines/hosted/exec.js +12 -1
- package/dist/commands/machines/hosted/index.js +1 -0
- package/dist/commands/machines/hosted/operation.d.ts +14 -0
- package/dist/commands/machines/hosted/operation.js +51 -0
- package/dist/commands/machines/hosted/pull.js +1 -1
- package/dist/commands/machines/hosted/start.js +6 -2
- package/dist/commands/search/agents.js +5 -1
- package/dist/commands/search/all.js +74 -22
- package/dist/commands/search/skills.d.ts +1 -0
- package/dist/commands/search/skills.js +12 -1
- package/dist/commands/search/spaces.d.ts +8 -1
- package/dist/commands/search/spaces.js +50 -12
- package/dist/commands/search/tasks.d.ts +13 -2
- package/dist/commands/search/tasks.js +78 -14
- package/dist/commands/search/teams.js +5 -1
- package/dist/commands/search/workspace.d.ts +19 -0
- package/dist/commands/search/workspace.js +126 -0
- package/dist/commands/skills/browse.d.ts +1 -0
- package/dist/commands/skills/browse.js +8 -1
- package/dist/commands/skills/search.d.ts +1 -0
- package/dist/commands/skills/search.js +12 -1
- package/dist/commands/spaces/auto-executor/compare.d.ts +18 -0
- package/dist/commands/spaces/auto-executor/compare.js +139 -0
- package/dist/commands/spaces/auto-executor/runs.d.ts +3 -0
- package/dist/commands/spaces/auto-executor/runs.js +13 -1
- package/dist/commands/spaces/charter/create-from-description.d.ts +21 -0
- package/dist/commands/spaces/charter/create-from-description.js +77 -0
- package/dist/commands/spaces/charter/show.d.ts +22 -0
- package/dist/commands/spaces/charter/show.js +61 -0
- package/dist/commands/spaces/heartbeats/list.d.ts +1 -16
- package/dist/commands/spaces/heartbeats/list.js +13 -0
- package/dist/commands/spaces/install-template.js +3 -0
- package/dist/commands/spaces/list.d.ts +1 -0
- package/dist/commands/spaces/list.js +12 -3
- package/dist/commands/spaces/loops/migrate.d.ts +32 -0
- package/dist/commands/spaces/loops/migrate.js +103 -0
- package/dist/commands/spaces/loops/release.d.ts +14 -0
- package/dist/commands/spaces/loops/release.js +59 -0
- package/dist/commands/spaces/notebook.d.ts +25 -0
- package/dist/commands/spaces/notebook.js +75 -0
- package/dist/commands/spaces/proposals/discard.d.ts +17 -0
- package/dist/commands/spaces/proposals/discard.js +17 -0
- package/dist/commands/spaces/proposals/keep.d.ts +17 -0
- package/dist/commands/spaces/proposals/keep.js +17 -0
- package/dist/commands/spaces/proposals/list.d.ts +24 -0
- package/dist/commands/spaces/proposals/list.js +89 -0
- package/dist/commands/spaces/search.d.ts +1 -0
- package/dist/commands/spaces/search.js +9 -0
- package/dist/commands/spaces/skills/propose.d.ts +29 -0
- package/dist/commands/spaces/skills/propose.js +82 -0
- package/dist/commands/spaces/templates/fork.js +3 -0
- package/dist/commands/spaces/triggers/show.js +6 -0
- package/dist/commands/spaces/workflows/retry.d.ts +1 -0
- package/dist/commands/spaces/workflows/retry.js +8 -1
- package/dist/commands/spaces/workflows/triggers/create.js +3 -2
- package/dist/commands/spaces/workflows/triggers/fire.js +4 -0
- package/dist/commands/spaces/workflows/triggers/update.js +2 -1
- package/dist/commands/spaces/write-policy/set.d.ts +22 -0
- package/dist/commands/spaces/write-policy/set.js +82 -0
- package/dist/commands/spaces/write-policy/show.d.ts +19 -0
- package/dist/commands/spaces/write-policy/show.js +49 -0
- package/dist/commands/store/artifacts/receipt.d.ts +14 -0
- package/dist/commands/store/artifacts/receipt.js +34 -0
- package/dist/commands/store/artifacts/release.d.ts +2 -0
- package/dist/commands/store/artifacts/release.js +11 -0
- package/dist/commands/store/governance/publish-release.d.ts +2 -0
- package/dist/commands/store/governance/publish-release.js +30 -1
- package/dist/commands/store/governance/release-receipt.d.ts +12 -0
- package/dist/commands/store/governance/release-receipt.js +32 -0
- package/dist/commands/tasks/approvals/list.d.ts +11 -5
- package/dist/commands/tasks/approvals/list.js +13 -6
- package/dist/commands/tasks/attachments/facts.d.ts +12 -0
- package/dist/commands/tasks/attachments/facts.js +29 -0
- package/dist/commands/tasks/attachments/upload.d.ts +1 -0
- package/dist/commands/tasks/attachments/upload.js +16 -0
- package/dist/commands/tasks/bulk-update.js +35 -1
- package/dist/commands/tasks/context.d.ts +15 -0
- package/dist/commands/tasks/context.js +69 -0
- package/dist/commands/tasks/copy.d.ts +18 -0
- package/dist/commands/tasks/copy.js +85 -0
- package/dist/commands/tasks/create-tree.js +97 -23
- package/dist/commands/tasks/create.js +9 -1
- package/dist/commands/tasks/deliverable/add.js +20 -10
- package/dist/commands/tasks/move.js +8 -1
- package/dist/commands/tasks/operations/show.d.ts +16 -0
- package/dist/commands/tasks/operations/show.js +98 -0
- package/dist/commands/tasks/operations/wait.d.ts +22 -0
- package/dist/commands/tasks/operations/wait.js +114 -0
- package/dist/commands/tasks/preflight.d.ts +1 -0
- package/dist/commands/tasks/preflight.js +11 -1
- package/dist/commands/tasks/rearm-recurrence.js +4 -0
- package/dist/commands/tasks/result/show.js +22 -0
- package/dist/commands/tasks/result/submit.d.ts +1 -0
- package/dist/commands/tasks/result/submit.js +3 -0
- package/dist/commands/tasks/retry-receipt.d.ts +15 -0
- package/dist/commands/tasks/retry-receipt.js +89 -0
- package/dist/commands/tasks/retry-review.d.ts +16 -0
- package/dist/commands/tasks/retry-review.js +66 -0
- package/dist/commands/tasks/retry.d.ts +4 -8
- package/dist/commands/tasks/retry.js +34 -68
- package/dist/commands/tasks/runs.d.ts +21 -0
- package/dist/commands/tasks/runs.js +95 -0
- package/dist/commands/tasks/search.d.ts +1 -0
- package/dist/commands/tasks/search.js +9 -0
- package/dist/commands/tasks/show.d.ts +2 -0
- package/dist/commands/tasks/show.js +15 -1
- package/dist/commands/tasks/start-receipt.d.ts +15 -0
- package/dist/commands/tasks/start-receipt.js +95 -0
- package/dist/commands/tasks/start.d.ts +4 -0
- package/dist/commands/tasks/start.js +49 -3
- package/dist/commands/tasks/update.js +3 -0
- package/dist/commands/tasks/upsert.js +17 -3
- package/dist/commands/tasks/watch.d.ts +3 -0
- package/dist/commands/tasks/watch.js +85 -20
- package/dist/commands/wiki/history.d.ts +33 -0
- package/dist/commands/wiki/history.js +134 -0
- package/dist/commands/wiki/ls.d.ts +2 -0
- package/dist/commands/wiki/ls.js +8 -0
- package/dist/commands/wiki/update.d.ts +3 -1
- package/dist/commands/wiki/update.js +24 -2
- package/dist/commands/wiki/write.d.ts +22 -0
- package/dist/commands/wiki/write.js +144 -6
- package/dist/generated/agent-guides.json +81 -81
- package/dist/generated/managed-instruction-guides.json +1 -1
- package/dist/lib/agent-guides.d.ts +9 -0
- package/dist/lib/agent-guides.js +23 -1
- package/dist/lib/agentic-stream.d.ts +5 -0
- package/dist/lib/agentic-stream.js +122 -3
- package/dist/lib/api-error-reason.d.ts +2 -0
- package/dist/lib/api-error-reason.js +14 -0
- package/dist/lib/api-fetch.js +3 -0
- package/dist/lib/auth-diagnostics.d.ts +39 -0
- package/dist/lib/auth-diagnostics.js +71 -0
- package/dist/lib/cli-client-identity.d.ts +6 -0
- package/dist/lib/cli-client-identity.js +16 -0
- package/dist/lib/cli-installation-diagnostics.d.ts +52 -0
- package/dist/lib/cli-installation-diagnostics.js +254 -0
- package/dist/lib/command-hygiene.d.ts +45 -0
- package/dist/lib/command-hygiene.js +413 -0
- package/dist/lib/commitments.d.ts +64 -3
- package/dist/lib/commitments.js +34 -6
- package/dist/lib/completion-index.d.ts +16 -4
- package/dist/lib/completion-index.js +50 -14
- package/dist/lib/config.js +7 -1
- package/dist/lib/daemon-maintenance-command.d.ts +22 -0
- package/dist/lib/daemon-maintenance-command.js +39 -0
- package/dist/lib/daemon-maintenance.d.ts +275 -0
- package/dist/lib/daemon-maintenance.js +1035 -0
- package/dist/lib/dedicated-service.d.ts +3 -2
- package/dist/lib/dedicated-service.js +11 -3
- package/dist/lib/governed-store.d.ts +8 -0
- package/dist/lib/governed-store.js +5 -0
- package/dist/lib/hosted-machines.d.ts +33 -2
- package/dist/lib/hosted-machines.js +19 -1
- package/dist/lib/json-output.d.ts +1 -1
- package/dist/lib/machines.d.ts +9 -1
- package/dist/lib/machines.js +15 -1
- package/dist/lib/node-adapter.js +13 -0
- package/dist/lib/record-output.d.ts +22 -0
- package/dist/lib/record-output.js +78 -0
- package/dist/lib/roles.d.ts +3 -3
- package/dist/lib/scoped-memory-command.d.ts +18 -0
- package/dist/lib/scoped-memory-command.js +53 -2
- package/dist/lib/search-display.d.ts +7 -0
- package/dist/lib/search-display.js +31 -0
- package/dist/lib/skill-store-pagination.d.ts +8 -0
- package/dist/lib/skill-store-pagination.js +35 -0
- package/dist/lib/space-charter.d.ts +49 -0
- package/dist/lib/space-charter.js +78 -0
- package/dist/lib/space-loop-migration.d.ts +18 -0
- package/dist/lib/space-loop-migration.js +128 -0
- package/dist/lib/space-notebook.d.ts +22 -0
- package/dist/lib/space-notebook.js +140 -0
- package/dist/lib/space-proposal-decision-command.d.ts +36 -0
- package/dist/lib/space-proposal-decision-command.js +108 -0
- package/dist/lib/space-proposals.d.ts +44 -0
- package/dist/lib/space-proposals.js +186 -0
- package/dist/lib/space-templates.d.ts +8 -0
- package/dist/lib/space-templates.js +17 -0
- package/dist/lib/space-write-policy.d.ts +24 -0
- package/dist/lib/space-write-policy.js +103 -0
- package/dist/lib/task-agent-context-capabilities.d.ts +10 -0
- package/dist/lib/task-agent-context-capabilities.js +22 -0
- package/dist/lib/task-agent-context-compatibility.d.ts +9 -0
- package/dist/lib/task-agent-context-compatibility.js +30 -0
- package/dist/lib/task-asset-upload.d.ts +22 -0
- package/dist/lib/task-asset-upload.js +257 -29
- package/dist/lib/task-batch.d.ts +15 -1
- package/dist/lib/task-batch.js +35 -2
- package/dist/lib/task-operation-watch.d.ts +66 -0
- package/dist/lib/task-operation-watch.js +159 -0
- package/dist/lib/task-resolver.d.ts +1 -1
- package/dist/lib/task-resolver.js +4 -3
- package/dist/lib/task-work.d.ts +1 -1
- package/dist/lib/task-work.js +35 -1
- package/dist/lib/task-write-attribution.d.ts +14 -0
- package/dist/lib/task-write-attribution.js +35 -2
- package/dist/lib/triggers.d.ts +11 -0
- package/dist/lib/triggers.js +7 -0
- package/dist/lib/wiki-update.d.ts +1 -0
- package/dist/lib/wiki-update.js +10 -1
- package/dist/lib/wiki.js +4 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.d.ts +9 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.js +31 -15
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +10 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +147 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonConsentProof.d.ts +20 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonConsentProof.js +72 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +13 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/desktopDaemonProof.d.ts +21 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/desktopDaemonProof.js +96 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +2 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +102 -20
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/guidedUpdate.d.ts +370 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/guidedUpdate.js +315 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.d.ts +27 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.js +161 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineScopes.d.ts +26 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineScopes.js +41 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +15 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.js +20 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/maintenanceReportWire.d.ts +178 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/maintenanceReportWire.js +262 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/toolApprovalSignature.d.ts +51 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/toolApprovalSignature.js +106 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.d.ts +9 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.js +32 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +10 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +172 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonConsentProof.d.ts +20 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonConsentProof.js +43 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +33 -14
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/desktopDaemonProof.d.ts +21 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/desktopDaemonProof.js +62 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +2 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +133 -25
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/guidedUpdate.d.ts +370 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/guidedUpdate.js +404 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.d.ts +27 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.js +132 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineScopes.d.ts +26 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineScopes.js +15 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.js +28 -15
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/maintenanceReportWire.d.ts +178 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/maintenanceReportWire.js +342 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/toolApprovalSignature.d.ts +51 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/toolApprovalSignature.js +138 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +51 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +27181 -20293
- package/oclif.manifest.json +5745 -1813
- package/package.json +20 -2
|
@@ -272,17 +272,9 @@ async function readKekFromKeychain(service, account) {
|
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
/**
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* wins outcome because (a) the daemon is single-tenant per profile and
|
|
279
|
-
* (b) all writers are generating the same shape of secret.
|
|
280
|
-
*
|
|
281
|
-
* Race note: if two daemon processes both miss and both store within the
|
|
282
|
-
* same TOCTOU window, the second overwrite wins and any DEKs already
|
|
283
|
-
* wrapped under the first KEK become permanently unrecoverable. The
|
|
284
|
-
* envelope path's caller (`credEnvelope.ts:initCredEnvelope`) handles
|
|
285
|
-
* this gracefully — `unwrap` failure → log + treat as first enrollment.
|
|
275
|
+
* Create a master KEK without replacing an existing item. This key is shared
|
|
276
|
+
* by profiles, so even a first-use race in a different process must not
|
|
277
|
+
* overwrite the key protecting already wrapped credentials.
|
|
286
278
|
*
|
|
287
279
|
* The `service` / `account` parameters are threaded from the calling
|
|
288
280
|
* `MacosKek` instance so each instance keeps its own keychain entry.
|
|
@@ -312,7 +304,6 @@ async function writeKekToKeychain(kek, service, account) {
|
|
|
312
304
|
account,
|
|
313
305
|
'-w',
|
|
314
306
|
b64,
|
|
315
|
-
'-U',
|
|
316
307
|
keychainPath,
|
|
317
308
|
]);
|
|
318
309
|
}
|
|
@@ -410,9 +401,31 @@ async function resolveOrCreateKek(service, account) {
|
|
|
410
401
|
return existing;
|
|
411
402
|
}
|
|
412
403
|
const fresh = crypto.randomBytes(KEY_LEN);
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
404
|
+
try {
|
|
405
|
+
try {
|
|
406
|
+
await writeKekToKeychain(fresh, service, account);
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
// A concurrent creator may have won. Only a successful read of its
|
|
410
|
+
// existing item makes the failed create recoverable; all other errors
|
|
411
|
+
// remain failures and must not cause an update or a second write.
|
|
412
|
+
const winner = await readKekFromKeychain(service, account).catch(() => null);
|
|
413
|
+
if (!winner)
|
|
414
|
+
throw error;
|
|
415
|
+
_kekCacheBySvc.set(cacheKey, winner);
|
|
416
|
+
return winner;
|
|
417
|
+
}
|
|
418
|
+
const persisted = await readKekFromKeychain(service, account);
|
|
419
|
+
if (!persisted || !persisted.equals(fresh)) {
|
|
420
|
+
persisted?.fill(0);
|
|
421
|
+
throw new KekUnavailableError(KIND_SHELLOUT, 'created master KEK could not be verified');
|
|
422
|
+
}
|
|
423
|
+
_kekCacheBySvc.set(cacheKey, persisted);
|
|
424
|
+
return persisted;
|
|
425
|
+
}
|
|
426
|
+
finally {
|
|
427
|
+
fresh.fill(0);
|
|
428
|
+
}
|
|
416
429
|
}
|
|
417
430
|
/** Resolve an already-persisted master KEK without creating one. */
|
|
418
431
|
async function resolveExistingKek(service, account) {
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The maintenance report wire contract (Machine Maintenance, OSK-19107, task M24;
|
|
3
|
+
* `docs/architecture/machine-maintenance/implementation-plan-2026-10-24.md` §3.5,
|
|
4
|
+
* `phase0/m04-quiescence-evidence.md` §2.5 and §2.6).
|
|
5
|
+
*
|
|
6
|
+
* Declared ONCE, here. The daemon is published and cannot import the API, so the
|
|
7
|
+
* capability name, the payload shape and its bounds live in this package, which
|
|
8
|
+
* every end can import (`@skrr-ai/auth-core/maintenance-report-wire`).
|
|
9
|
+
* `maintenanceReportWire.test.ts` fails on a second spelling of the capability
|
|
10
|
+
* anywhere else in the repo. The shape follows `dedicatedRuntimeBusyWire.ts`, the
|
|
11
|
+
* precedent for a positively announced, fail-closed report, and differs from it
|
|
12
|
+
* only where the owner's pause needs more.
|
|
13
|
+
*
|
|
14
|
+
* WHAT THE REPORT SAYS
|
|
15
|
+
* ---------------------------------------------------------------------------
|
|
16
|
+
* "What does this daemon process hold right now that an update would take from
|
|
17
|
+
* the owner, and has anything been admitted since the last time you asked?" It is
|
|
18
|
+
* a LOCAL read summarised into counts, the handoff's own restart blockers, and
|
|
19
|
+
* the daemon's own update handoff phase.
|
|
20
|
+
*
|
|
21
|
+
* It is EVIDENCE, never authority. The fence does not depend on it: a daemon that
|
|
22
|
+
* lies, or one that sends nothing, cannot make a held machine accept work. Only
|
|
23
|
+
* this report can grant `ready` (a server-side source can only veto it), and a
|
|
24
|
+
* daemon that never announces the capability can only reach `ready` by the
|
|
25
|
+
* owner's attestation.
|
|
26
|
+
*
|
|
27
|
+
* FAIL CLOSED, in three places
|
|
28
|
+
* ---------------------------------------------------------------------------
|
|
29
|
+
* 1. A count is `0` only when the daemon positively counted. A source that could
|
|
30
|
+
* not be read is `complete: 'unknown'`; a daemon that is leaving is
|
|
31
|
+
* `complete: false`. A reader MUST treat both as busy, whatever the counts
|
|
32
|
+
* say (`maintenanceReportIsQuiet` is the one place that says so).
|
|
33
|
+
* 2. A report that is malformed in ANY field is `null` from
|
|
34
|
+
* `parseMaintenanceReport`, and the caller treats the machine as not quiet.
|
|
35
|
+
* A count that is missing, negative, fractional or not a safe integer is not
|
|
36
|
+
* "zero": it is a different, broken report. A version this parser does not
|
|
37
|
+
* know is `null` too: a newer shape needs its own announced capability.
|
|
38
|
+
* 3. Bounds. The blocker list and every string are clamped, so one report can
|
|
39
|
+
* never be large enough to matter, and a field that is NOT in the contract is
|
|
40
|
+
* never copied into what the parser returns (the result is built from a
|
|
41
|
+
* fresh object, so `__proto__` and its kin cannot ride along).
|
|
42
|
+
*
|
|
43
|
+
* `admittedTotal`
|
|
44
|
+
* ---------------------------------------------------------------------------
|
|
45
|
+
* A MONOTONIC counter of admissions since this process started. The server proves
|
|
46
|
+
* "nothing was admitted between two reports" by comparing two values, without
|
|
47
|
+
* trusting the daemon's clock. It counts at every place the daemon admits work
|
|
48
|
+
* and OVER-counts on purpose (one admission may be noted at the gate and again
|
|
49
|
+
* where the work registers): a second increment can only make the proof harder,
|
|
50
|
+
* never easier. A path that the daemon cannot count must say so in
|
|
51
|
+
* `daemon/src/maintenance-report.ts`, not hide in a zero.
|
|
52
|
+
*/
|
|
53
|
+
export declare const MAINTENANCE_REPORT_CAPABILITY = "maintenance_report_v1";
|
|
54
|
+
/** The heartbeat (and register) key the report rides under. Sent only by a daemon that announced the capability. */
|
|
55
|
+
export declare const MAINTENANCE_REPORT_HEARTBEAT_KEY = "maintenanceReport";
|
|
56
|
+
export declare const MAINTENANCE_REPORT_VERSION = 1;
|
|
57
|
+
/**
|
|
58
|
+
* The daemon's own update handoff phases (`daemon/src/update/handoff.ts`), and the
|
|
59
|
+
* members of `DaemonMaintenanceHandoff.phase` in `@skrr-ai/data-provider`. A phase
|
|
60
|
+
* outside this list is a newer protocol and makes the whole report `null`.
|
|
61
|
+
*/
|
|
62
|
+
export declare const MAINTENANCE_REPORT_HANDOFF_PHASES: readonly ["ready", "deferred", "reconnecting", "verified", "failed", "rolled-back"];
|
|
63
|
+
export type MaintenanceReportHandoffPhase = (typeof MAINTENANCE_REPORT_HANDOFF_PHASES)[number];
|
|
64
|
+
export declare const MAINTENANCE_REPORT_LIMITS: {
|
|
65
|
+
/** The handoff's blockers carried for the "waiting on" text; the counts, not this list, decide busy. */
|
|
66
|
+
readonly maxBlockers: 32;
|
|
67
|
+
readonly maxKindLength: 32;
|
|
68
|
+
readonly maxIdLength: 128;
|
|
69
|
+
readonly maxReasonLength: 240;
|
|
70
|
+
readonly maxHandoffIdLength: 64;
|
|
71
|
+
readonly maxHandoffReasonLength: 64;
|
|
72
|
+
readonly maxProcessStartIdLength: 64;
|
|
73
|
+
/** Hard ceiling on the serialized report. The clamps above keep a valid one well under it. */
|
|
74
|
+
readonly maxSerializedBytes: 20000;
|
|
75
|
+
};
|
|
76
|
+
/** What `complete` can be. Anything but `true` means the counts prove nothing. */
|
|
77
|
+
export type MaintenanceReportComplete = boolean | 'unknown';
|
|
78
|
+
/** The handoff's own `RestartBlocker` shape (`daemon/src/update/handoff.ts`). Reason text is the handoff's, never re-spelled. */
|
|
79
|
+
export interface MaintenanceReportBlocker {
|
|
80
|
+
kind: string;
|
|
81
|
+
id?: string;
|
|
82
|
+
reason: string;
|
|
83
|
+
}
|
|
84
|
+
export interface MaintenanceReportInflight {
|
|
85
|
+
/** Provider sessions and their wrapper processes: in-process turns, preparing dispatches, live wrappers. */
|
|
86
|
+
sessions: number;
|
|
87
|
+
/** Tool calls running, plus background commands still running. */
|
|
88
|
+
tools: number;
|
|
89
|
+
/** Terminals the daemon holds, preservable or not. A person's open shell is work an update would interrupt. */
|
|
90
|
+
terminals: number;
|
|
91
|
+
/** Provider turns that still depend on credentials, permissions, a browser or a model connection the service owns. */
|
|
92
|
+
providerDependencies: number;
|
|
93
|
+
/** Write-ahead entries whose durability the daemon has not confirmed. */
|
|
94
|
+
walUnconfirmed: number;
|
|
95
|
+
}
|
|
96
|
+
export interface MaintenanceReportHandoff {
|
|
97
|
+
phase: MaintenanceReportHandoffPhase;
|
|
98
|
+
handoffId: string;
|
|
99
|
+
reason?: string;
|
|
100
|
+
/** ISO time the daemon's lease on this phase ends. */
|
|
101
|
+
expiresAt?: string;
|
|
102
|
+
}
|
|
103
|
+
export interface MaintenanceReport {
|
|
104
|
+
version: typeof MAINTENANCE_REPORT_VERSION;
|
|
105
|
+
/** The daemon process that read this. The server drops a report that is not its registered process's. */
|
|
106
|
+
processStartId: string;
|
|
107
|
+
/** ISO time of the daemon's own read. Informational: freshness is the server's receipt time. */
|
|
108
|
+
readAt: string;
|
|
109
|
+
/** `true`: every source was read. `'unknown'`: one could not be. `false`: the process is leaving. */
|
|
110
|
+
complete: MaintenanceReportComplete;
|
|
111
|
+
/** Monotonic count of admissions since this process started. */
|
|
112
|
+
admittedTotal: number;
|
|
113
|
+
inflight: MaintenanceReportInflight;
|
|
114
|
+
blockers: MaintenanceReportBlocker[];
|
|
115
|
+
/** True when the daemon had more blockers than it sent. */
|
|
116
|
+
blockersTruncated: boolean;
|
|
117
|
+
/** The daemon's own update handoff, only while it is live or unresolved; otherwise `null`. */
|
|
118
|
+
handoff: MaintenanceReportHandoff | null;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The reader's view of a daemon's heartbeat section. Anything malformed, from a
|
|
122
|
+
* daemon that did not announce the capability, an unknown version, or oversized
|
|
123
|
+
* is `null`, and the caller must then treat the machine as NOT quiet. Never
|
|
124
|
+
* trusts a field it did not validate and never copies one it does not know.
|
|
125
|
+
*/
|
|
126
|
+
export declare function parseMaintenanceReport(raw: unknown): MaintenanceReport | null;
|
|
127
|
+
/**
|
|
128
|
+
* A report as it was stored (JSON text on the presence row). Anything that does
|
|
129
|
+
* not parse, or does not validate, is `null`: a stored value is an input like any
|
|
130
|
+
* other and is read with the same suspicion.
|
|
131
|
+
*/
|
|
132
|
+
export declare function parseStoredMaintenanceReport(stored: unknown): MaintenanceReport | null;
|
|
133
|
+
/**
|
|
134
|
+
* The one definition of a POSITIVE ZERO: the daemon read every source and found
|
|
135
|
+
* nothing. `complete: 'unknown'` and `complete: false` are never zero whatever
|
|
136
|
+
* the counts say, because the counts were not (all) read.
|
|
137
|
+
*
|
|
138
|
+
* A blocker that is not work (a missing restart owner, say) does not make a
|
|
139
|
+
* report non-quiet; readiness is "nothing is running", not "the daemon can
|
|
140
|
+
* restart itself".
|
|
141
|
+
*/
|
|
142
|
+
export declare function maintenanceReportIsQuiet(report: MaintenanceReport | null): boolean;
|
|
143
|
+
export interface BuildMaintenanceReportInput {
|
|
144
|
+
processStartId: string;
|
|
145
|
+
/** Epoch ms of the daemon's own read. */
|
|
146
|
+
readAtMs: number;
|
|
147
|
+
complete: MaintenanceReportComplete;
|
|
148
|
+
admittedTotal: number;
|
|
149
|
+
inflight: MaintenanceReportInflight;
|
|
150
|
+
blockers: ReadonlyArray<{
|
|
151
|
+
kind: string;
|
|
152
|
+
id?: string;
|
|
153
|
+
reason: string;
|
|
154
|
+
}>;
|
|
155
|
+
/** `expiresAtMs` is epoch ms; the wire carries ISO. */
|
|
156
|
+
handoff: {
|
|
157
|
+
phase: MaintenanceReportHandoffPhase;
|
|
158
|
+
handoffId: string;
|
|
159
|
+
reason?: string;
|
|
160
|
+
expiresAtMs?: number;
|
|
161
|
+
} | null;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* A report that says nothing was counted. The shape the daemon sends when it
|
|
165
|
+
* could not read an inventory source at all, and the shape `buildMaintenanceReport`
|
|
166
|
+
* falls back to when it was handed something the contract cannot carry.
|
|
167
|
+
*/
|
|
168
|
+
export declare function unknownMaintenanceReport(processStartId: string, readAtMs: number, admittedTotal?: number): MaintenanceReport;
|
|
169
|
+
/**
|
|
170
|
+
* Build the wire report from what the daemon read. Input the contract cannot
|
|
171
|
+
* carry (a negative count, a blocker with no kind, a time that is not a time)
|
|
172
|
+
* becomes an `unknown` report rather than a throw or a report that parses to
|
|
173
|
+
* something else, so the result parses whenever `processStartId` is a real id (the
|
|
174
|
+
* daemon's always is). A builder that threw would turn a bad read into silence,
|
|
175
|
+
* and silence is the absence of a report, which the server reads the same way,
|
|
176
|
+
* but later.
|
|
177
|
+
*/
|
|
178
|
+
export declare function buildMaintenanceReport(input: BuildMaintenanceReportInput): MaintenanceReport;
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The maintenance report wire contract (Machine Maintenance, OSK-19107, task M24;
|
|
3
|
+
* `docs/architecture/machine-maintenance/implementation-plan-2026-10-24.md` §3.5,
|
|
4
|
+
* `phase0/m04-quiescence-evidence.md` §2.5 and §2.6).
|
|
5
|
+
*
|
|
6
|
+
* Declared ONCE, here. The daemon is published and cannot import the API, so the
|
|
7
|
+
* capability name, the payload shape and its bounds live in this package, which
|
|
8
|
+
* every end can import (`@skrr-ai/auth-core/maintenance-report-wire`).
|
|
9
|
+
* `maintenanceReportWire.test.ts` fails on a second spelling of the capability
|
|
10
|
+
* anywhere else in the repo. The shape follows `dedicatedRuntimeBusyWire.ts`, the
|
|
11
|
+
* precedent for a positively announced, fail-closed report, and differs from it
|
|
12
|
+
* only where the owner's pause needs more.
|
|
13
|
+
*
|
|
14
|
+
* WHAT THE REPORT SAYS
|
|
15
|
+
* ---------------------------------------------------------------------------
|
|
16
|
+
* "What does this daemon process hold right now that an update would take from
|
|
17
|
+
* the owner, and has anything been admitted since the last time you asked?" It is
|
|
18
|
+
* a LOCAL read summarised into counts, the handoff's own restart blockers, and
|
|
19
|
+
* the daemon's own update handoff phase.
|
|
20
|
+
*
|
|
21
|
+
* It is EVIDENCE, never authority. The fence does not depend on it: a daemon that
|
|
22
|
+
* lies, or one that sends nothing, cannot make a held machine accept work. Only
|
|
23
|
+
* this report can grant `ready` (a server-side source can only veto it), and a
|
|
24
|
+
* daemon that never announces the capability can only reach `ready` by the
|
|
25
|
+
* owner's attestation.
|
|
26
|
+
*
|
|
27
|
+
* FAIL CLOSED, in three places
|
|
28
|
+
* ---------------------------------------------------------------------------
|
|
29
|
+
* 1. A count is `0` only when the daemon positively counted. A source that could
|
|
30
|
+
* not be read is `complete: 'unknown'`; a daemon that is leaving is
|
|
31
|
+
* `complete: false`. A reader MUST treat both as busy, whatever the counts
|
|
32
|
+
* say (`maintenanceReportIsQuiet` is the one place that says so).
|
|
33
|
+
* 2. A report that is malformed in ANY field is `null` from
|
|
34
|
+
* `parseMaintenanceReport`, and the caller treats the machine as not quiet.
|
|
35
|
+
* A count that is missing, negative, fractional or not a safe integer is not
|
|
36
|
+
* "zero": it is a different, broken report. A version this parser does not
|
|
37
|
+
* know is `null` too: a newer shape needs its own announced capability.
|
|
38
|
+
* 3. Bounds. The blocker list and every string are clamped, so one report can
|
|
39
|
+
* never be large enough to matter, and a field that is NOT in the contract is
|
|
40
|
+
* never copied into what the parser returns (the result is built from a
|
|
41
|
+
* fresh object, so `__proto__` and its kin cannot ride along).
|
|
42
|
+
*
|
|
43
|
+
* `admittedTotal`
|
|
44
|
+
* ---------------------------------------------------------------------------
|
|
45
|
+
* A MONOTONIC counter of admissions since this process started. The server proves
|
|
46
|
+
* "nothing was admitted between two reports" by comparing two values, without
|
|
47
|
+
* trusting the daemon's clock. It counts at every place the daemon admits work
|
|
48
|
+
* and OVER-counts on purpose (one admission may be noted at the gate and again
|
|
49
|
+
* where the work registers): a second increment can only make the proof harder,
|
|
50
|
+
* never easier. A path that the daemon cannot count must say so in
|
|
51
|
+
* `daemon/src/maintenance-report.ts`, not hide in a zero.
|
|
52
|
+
*/
|
|
53
|
+
export const MAINTENANCE_REPORT_CAPABILITY = 'maintenance_report_v1';
|
|
54
|
+
/** The heartbeat (and register) key the report rides under. Sent only by a daemon that announced the capability. */
|
|
55
|
+
export const MAINTENANCE_REPORT_HEARTBEAT_KEY = 'maintenanceReport';
|
|
56
|
+
export const MAINTENANCE_REPORT_VERSION = 1;
|
|
57
|
+
/**
|
|
58
|
+
* The daemon's own update handoff phases (`daemon/src/update/handoff.ts`), and the
|
|
59
|
+
* members of `DaemonMaintenanceHandoff.phase` in `@skrr-ai/data-provider`. A phase
|
|
60
|
+
* outside this list is a newer protocol and makes the whole report `null`.
|
|
61
|
+
*/
|
|
62
|
+
export const MAINTENANCE_REPORT_HANDOFF_PHASES = [
|
|
63
|
+
'ready',
|
|
64
|
+
'deferred',
|
|
65
|
+
'reconnecting',
|
|
66
|
+
'verified',
|
|
67
|
+
'failed',
|
|
68
|
+
'rolled-back',
|
|
69
|
+
];
|
|
70
|
+
export const MAINTENANCE_REPORT_LIMITS = {
|
|
71
|
+
/** The handoff's blockers carried for the "waiting on" text; the counts, not this list, decide busy. */
|
|
72
|
+
maxBlockers: 32,
|
|
73
|
+
maxKindLength: 32,
|
|
74
|
+
maxIdLength: 128,
|
|
75
|
+
maxReasonLength: 240,
|
|
76
|
+
maxHandoffIdLength: 64,
|
|
77
|
+
maxHandoffReasonLength: 64,
|
|
78
|
+
maxProcessStartIdLength: 64,
|
|
79
|
+
/** Hard ceiling on the serialized report. The clamps above keep a valid one well under it. */
|
|
80
|
+
maxSerializedBytes: 20_000,
|
|
81
|
+
};
|
|
82
|
+
const PROCESS_START_ID_PATTERN = /^[A-Za-z0-9_.:-]+$/;
|
|
83
|
+
// C0 controls and DEL. A blocker's text is shown to an owner and logged.
|
|
84
|
+
// eslint-disable-next-line no-control-regex
|
|
85
|
+
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/g;
|
|
86
|
+
function isRecord(value) {
|
|
87
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* An OWN property, or `undefined`. A key the sender did not put on the object
|
|
91
|
+
* itself is never read from its prototype chain, so a polluted `Object.prototype`
|
|
92
|
+
* (or a sender that smuggles a `__proto__`) cannot supply a field the report
|
|
93
|
+
* did not carry.
|
|
94
|
+
*/
|
|
95
|
+
function field(record, key) {
|
|
96
|
+
return Object.prototype.hasOwnProperty.call(record, key) ? record[key] : undefined;
|
|
97
|
+
}
|
|
98
|
+
/** ISO text for an epoch-ms value, or `''` when the value is not a representable time. */
|
|
99
|
+
function isoFromMs(ms) {
|
|
100
|
+
if (typeof ms !== 'number' || !Number.isFinite(ms))
|
|
101
|
+
return '';
|
|
102
|
+
const date = new Date(ms);
|
|
103
|
+
return Number.isNaN(date.getTime()) ? '' : date.toISOString();
|
|
104
|
+
}
|
|
105
|
+
function count(value) {
|
|
106
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
107
|
+
}
|
|
108
|
+
function clampText(value, max) {
|
|
109
|
+
if (typeof value !== 'string')
|
|
110
|
+
return null;
|
|
111
|
+
const clean = value.replace(CONTROL_CHARACTERS, ' ');
|
|
112
|
+
return clean.length > max ? clean.slice(0, max) : clean;
|
|
113
|
+
}
|
|
114
|
+
function isoOf(value) {
|
|
115
|
+
if (typeof value !== 'string')
|
|
116
|
+
return null;
|
|
117
|
+
const ms = Date.parse(value);
|
|
118
|
+
if (!Number.isFinite(ms))
|
|
119
|
+
return null;
|
|
120
|
+
try {
|
|
121
|
+
return new Date(ms).toISOString();
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function completeOf(value) {
|
|
128
|
+
return value === true || value === false || value === 'unknown' ? value : undefined;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The reader's view of a daemon's heartbeat section. Anything malformed, from a
|
|
132
|
+
* daemon that did not announce the capability, an unknown version, or oversized
|
|
133
|
+
* is `null`, and the caller must then treat the machine as NOT quiet. Never
|
|
134
|
+
* trusts a field it did not validate and never copies one it does not know.
|
|
135
|
+
*/
|
|
136
|
+
export function parseMaintenanceReport(raw) {
|
|
137
|
+
if (!isRecord(raw))
|
|
138
|
+
return null;
|
|
139
|
+
if (field(raw, 'version') !== MAINTENANCE_REPORT_VERSION)
|
|
140
|
+
return null;
|
|
141
|
+
const processStartId = field(raw, 'processStartId');
|
|
142
|
+
if (typeof processStartId !== 'string' ||
|
|
143
|
+
processStartId.length < 1 ||
|
|
144
|
+
processStartId.length > MAINTENANCE_REPORT_LIMITS.maxProcessStartIdLength ||
|
|
145
|
+
!PROCESS_START_ID_PATTERN.test(processStartId)) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const readAt = isoOf(field(raw, 'readAt'));
|
|
149
|
+
if (readAt === null)
|
|
150
|
+
return null;
|
|
151
|
+
const complete = completeOf(field(raw, 'complete'));
|
|
152
|
+
if (complete === undefined)
|
|
153
|
+
return null;
|
|
154
|
+
const admittedTotal = count(field(raw, 'admittedTotal'));
|
|
155
|
+
if (admittedTotal === null)
|
|
156
|
+
return null;
|
|
157
|
+
const rawInflight = field(raw, 'inflight');
|
|
158
|
+
if (!isRecord(rawInflight))
|
|
159
|
+
return null;
|
|
160
|
+
const sessions = count(field(rawInflight, 'sessions'));
|
|
161
|
+
const tools = count(field(rawInflight, 'tools'));
|
|
162
|
+
const terminals = count(field(rawInflight, 'terminals'));
|
|
163
|
+
const providerDependencies = count(field(rawInflight, 'providerDependencies'));
|
|
164
|
+
const walUnconfirmed = count(field(rawInflight, 'walUnconfirmed'));
|
|
165
|
+
if (sessions === null ||
|
|
166
|
+
tools === null ||
|
|
167
|
+
terminals === null ||
|
|
168
|
+
providerDependencies === null ||
|
|
169
|
+
walUnconfirmed === null) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const rawBlockers = field(raw, 'blockers');
|
|
173
|
+
if (!Array.isArray(rawBlockers))
|
|
174
|
+
return null;
|
|
175
|
+
const rawTruncated = field(raw, 'blockersTruncated');
|
|
176
|
+
if (rawTruncated !== undefined && typeof rawTruncated !== 'boolean')
|
|
177
|
+
return null;
|
|
178
|
+
const limit = MAINTENANCE_REPORT_LIMITS.maxBlockers;
|
|
179
|
+
const blockers = [];
|
|
180
|
+
// Only the first `limit` elements are looked at: the work is bounded by the
|
|
181
|
+
// contract, not by what the sender chose to send.
|
|
182
|
+
for (let index = 0; index < rawBlockers.length && blockers.length < limit; index += 1) {
|
|
183
|
+
const element = rawBlockers[index];
|
|
184
|
+
if (!isRecord(element))
|
|
185
|
+
return null;
|
|
186
|
+
const kind = clampText(field(element, 'kind'), MAINTENANCE_REPORT_LIMITS.maxKindLength);
|
|
187
|
+
const reason = clampText(field(element, 'reason'), MAINTENANCE_REPORT_LIMITS.maxReasonLength);
|
|
188
|
+
if (kind === null || kind === '' || reason === null)
|
|
189
|
+
return null;
|
|
190
|
+
const blocker = { kind, reason };
|
|
191
|
+
const rawId = field(element, 'id');
|
|
192
|
+
if (rawId !== undefined) {
|
|
193
|
+
const id = clampText(rawId, MAINTENANCE_REPORT_LIMITS.maxIdLength);
|
|
194
|
+
if (id === null)
|
|
195
|
+
return null;
|
|
196
|
+
blocker.id = id;
|
|
197
|
+
}
|
|
198
|
+
blockers.push(blocker);
|
|
199
|
+
}
|
|
200
|
+
const blockersTruncated = rawBlockers.length > limit || rawTruncated === true;
|
|
201
|
+
// The key is part of the contract: `null` says "no handoff", and an absent key
|
|
202
|
+
// says nothing, which is not a way to claim there is none.
|
|
203
|
+
const rawHandoff = field(raw, 'handoff');
|
|
204
|
+
if (rawHandoff === undefined)
|
|
205
|
+
return null;
|
|
206
|
+
let handoff = null;
|
|
207
|
+
if (rawHandoff !== null) {
|
|
208
|
+
if (!isRecord(rawHandoff))
|
|
209
|
+
return null;
|
|
210
|
+
const phase = field(rawHandoff, 'phase');
|
|
211
|
+
if (!MAINTENANCE_REPORT_HANDOFF_PHASES.includes(phase))
|
|
212
|
+
return null;
|
|
213
|
+
const handoffId = clampText(field(rawHandoff, 'handoffId'), MAINTENANCE_REPORT_LIMITS.maxHandoffIdLength);
|
|
214
|
+
if (handoffId === null || handoffId === '')
|
|
215
|
+
return null;
|
|
216
|
+
handoff = { phase: phase, handoffId };
|
|
217
|
+
const rawReason = field(rawHandoff, 'reason');
|
|
218
|
+
if (rawReason !== undefined) {
|
|
219
|
+
const reason = clampText(rawReason, MAINTENANCE_REPORT_LIMITS.maxHandoffReasonLength);
|
|
220
|
+
if (reason === null)
|
|
221
|
+
return null;
|
|
222
|
+
handoff.reason = reason;
|
|
223
|
+
}
|
|
224
|
+
const rawExpiresAt = field(rawHandoff, 'expiresAt');
|
|
225
|
+
if (rawExpiresAt !== undefined) {
|
|
226
|
+
const expiresAt = isoOf(rawExpiresAt);
|
|
227
|
+
if (expiresAt === null)
|
|
228
|
+
return null;
|
|
229
|
+
handoff.expiresAt = expiresAt;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const report = {
|
|
233
|
+
version: MAINTENANCE_REPORT_VERSION,
|
|
234
|
+
processStartId,
|
|
235
|
+
readAt,
|
|
236
|
+
complete,
|
|
237
|
+
admittedTotal,
|
|
238
|
+
inflight: { sessions, tools, terminals, providerDependencies, walUnconfirmed },
|
|
239
|
+
blockers,
|
|
240
|
+
blockersTruncated,
|
|
241
|
+
handoff,
|
|
242
|
+
};
|
|
243
|
+
if (JSON.stringify(report).length > MAINTENANCE_REPORT_LIMITS.maxSerializedBytes)
|
|
244
|
+
return null;
|
|
245
|
+
return report;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* A report as it was stored (JSON text on the presence row). Anything that does
|
|
249
|
+
* not parse, or does not validate, is `null`: a stored value is an input like any
|
|
250
|
+
* other and is read with the same suspicion.
|
|
251
|
+
*/
|
|
252
|
+
export function parseStoredMaintenanceReport(stored) {
|
|
253
|
+
if (typeof stored !== 'string' || stored === '')
|
|
254
|
+
return null;
|
|
255
|
+
if (stored.length > MAINTENANCE_REPORT_LIMITS.maxSerializedBytes)
|
|
256
|
+
return null;
|
|
257
|
+
try {
|
|
258
|
+
return parseMaintenanceReport(JSON.parse(stored));
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The one definition of a POSITIVE ZERO: the daemon read every source and found
|
|
266
|
+
* nothing. `complete: 'unknown'` and `complete: false` are never zero whatever
|
|
267
|
+
* the counts say, because the counts were not (all) read.
|
|
268
|
+
*
|
|
269
|
+
* A blocker that is not work (a missing restart owner, say) does not make a
|
|
270
|
+
* report non-quiet; readiness is "nothing is running", not "the daemon can
|
|
271
|
+
* restart itself".
|
|
272
|
+
*/
|
|
273
|
+
export function maintenanceReportIsQuiet(report) {
|
|
274
|
+
if (!report)
|
|
275
|
+
return false;
|
|
276
|
+
if (report.complete !== true)
|
|
277
|
+
return false;
|
|
278
|
+
const { sessions, tools, terminals, providerDependencies, walUnconfirmed } = report.inflight;
|
|
279
|
+
return (sessions === 0 &&
|
|
280
|
+
tools === 0 &&
|
|
281
|
+
terminals === 0 &&
|
|
282
|
+
providerDependencies === 0 &&
|
|
283
|
+
walUnconfirmed === 0);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* A report that says nothing was counted. The shape the daemon sends when it
|
|
287
|
+
* could not read an inventory source at all, and the shape `buildMaintenanceReport`
|
|
288
|
+
* falls back to when it was handed something the contract cannot carry.
|
|
289
|
+
*/
|
|
290
|
+
export function unknownMaintenanceReport(processStartId, readAtMs, admittedTotal = 0) {
|
|
291
|
+
return {
|
|
292
|
+
version: MAINTENANCE_REPORT_VERSION,
|
|
293
|
+
processStartId,
|
|
294
|
+
readAt: isoFromMs(readAtMs) || new Date(0).toISOString(),
|
|
295
|
+
complete: 'unknown',
|
|
296
|
+
admittedTotal: count(admittedTotal) ?? 0,
|
|
297
|
+
inflight: { sessions: 0, tools: 0, terminals: 0, providerDependencies: 0, walUnconfirmed: 0 },
|
|
298
|
+
blockers: [],
|
|
299
|
+
blockersTruncated: false,
|
|
300
|
+
handoff: null,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Build the wire report from what the daemon read. Input the contract cannot
|
|
305
|
+
* carry (a negative count, a blocker with no kind, a time that is not a time)
|
|
306
|
+
* becomes an `unknown` report rather than a throw or a report that parses to
|
|
307
|
+
* something else, so the result parses whenever `processStartId` is a real id (the
|
|
308
|
+
* daemon's always is). A builder that threw would turn a bad read into silence,
|
|
309
|
+
* and silence is the absence of a report, which the server reads the same way,
|
|
310
|
+
* but later.
|
|
311
|
+
*/
|
|
312
|
+
export function buildMaintenanceReport(input) {
|
|
313
|
+
const blockers = input.blockers
|
|
314
|
+
.slice(0, MAINTENANCE_REPORT_LIMITS.maxBlockers)
|
|
315
|
+
.map((blocker) => ({
|
|
316
|
+
kind: blocker.kind,
|
|
317
|
+
...(blocker.id !== undefined ? { id: blocker.id } : {}),
|
|
318
|
+
reason: blocker.reason,
|
|
319
|
+
}));
|
|
320
|
+
const candidate = {
|
|
321
|
+
version: MAINTENANCE_REPORT_VERSION,
|
|
322
|
+
processStartId: input.processStartId,
|
|
323
|
+
readAt: isoFromMs(input.readAtMs),
|
|
324
|
+
complete: input.complete,
|
|
325
|
+
admittedTotal: input.admittedTotal,
|
|
326
|
+
inflight: { ...input.inflight },
|
|
327
|
+
blockers,
|
|
328
|
+
blockersTruncated: input.blockers.length > MAINTENANCE_REPORT_LIMITS.maxBlockers,
|
|
329
|
+
handoff: input.handoff
|
|
330
|
+
? {
|
|
331
|
+
phase: input.handoff.phase,
|
|
332
|
+
handoffId: input.handoff.handoffId,
|
|
333
|
+
...(input.handoff.reason !== undefined ? { reason: input.handoff.reason } : {}),
|
|
334
|
+
...(input.handoff.expiresAtMs !== undefined
|
|
335
|
+
? { expiresAt: isoFromMs(input.handoff.expiresAtMs) }
|
|
336
|
+
: {}),
|
|
337
|
+
}
|
|
338
|
+
: null,
|
|
339
|
+
};
|
|
340
|
+
return (parseMaintenanceReport(candidate) ??
|
|
341
|
+
unknownMaintenanceReport(input.processStartId, input.readAtMs, input.admittedTotal));
|
|
342
|
+
}
|
|
@@ -177,6 +177,57 @@ export declare function signToolApproval(args: {
|
|
|
177
177
|
now?: number;
|
|
178
178
|
ttlMs?: number;
|
|
179
179
|
}): SignedToolApproval;
|
|
180
|
+
/** A short-lived platform-signed assertion for exactly one local executor. */
|
|
181
|
+
export interface PersonalDataExecutorLeasePayload {
|
|
182
|
+
v: 1;
|
|
183
|
+
leaseId: string;
|
|
184
|
+
/** The daemon identity registered for this machine. */
|
|
185
|
+
daemonId: string;
|
|
186
|
+
/** Fresh random daemon-start nonce; restart invalidates every prior lease. */
|
|
187
|
+
daemonInstanceId: string;
|
|
188
|
+
/** Account whose interactive session requested this local executor. */
|
|
189
|
+
ownerUserId: string;
|
|
190
|
+
/** Fixed, sorted set of operations this executor may make available. */
|
|
191
|
+
capabilities: string[];
|
|
192
|
+
issuedAt: number;
|
|
193
|
+
expiresAt: number;
|
|
194
|
+
}
|
|
195
|
+
export interface SignedPersonalDataExecutorLease {
|
|
196
|
+
v: 1;
|
|
197
|
+
payload: string;
|
|
198
|
+
kid: string;
|
|
199
|
+
sig: string;
|
|
200
|
+
}
|
|
201
|
+
export type PersonalDataExecutorLeaseVerdict = {
|
|
202
|
+
ok: true;
|
|
203
|
+
leaseId: string;
|
|
204
|
+
ownerUserId: string;
|
|
205
|
+
expiresAt: number;
|
|
206
|
+
} | {
|
|
207
|
+
ok: false;
|
|
208
|
+
reason: 'absent' | 'malformed' | 'unknown_key' | 'signature' | 'daemon_binding' | 'capabilities' | 'expired' | 'window';
|
|
209
|
+
};
|
|
210
|
+
/** Server-side mint. The caller must authorize the requesting user separately. */
|
|
211
|
+
export declare function signPersonalDataExecutorLease(args: {
|
|
212
|
+
leaseId: string;
|
|
213
|
+
daemonId: string;
|
|
214
|
+
daemonInstanceId: string;
|
|
215
|
+
ownerUserId: string;
|
|
216
|
+
capabilities: readonly string[];
|
|
217
|
+
signer: ApprovalSigner;
|
|
218
|
+
now?: number;
|
|
219
|
+
ttlMs?: number;
|
|
220
|
+
}): SignedPersonalDataExecutorLease | null;
|
|
221
|
+
/**
|
|
222
|
+
* Daemon-side verifier. It trusts the same independently pinned platform
|
|
223
|
+
* keyset as v2 tool approvals, but a separate signature domain and schema.
|
|
224
|
+
*/
|
|
225
|
+
export declare function verifyPersonalDataExecutorLease(lease: unknown, expected: {
|
|
226
|
+
daemonId: string;
|
|
227
|
+
daemonInstanceId: string;
|
|
228
|
+
capabilities: readonly string[];
|
|
229
|
+
now?: number;
|
|
230
|
+
}, keyset: ApprovalKeyset): PersonalDataExecutorLeaseVerdict;
|
|
180
231
|
export interface SignedToolApprovalCall {
|
|
181
232
|
toolUseId: string;
|
|
182
233
|
toolName: string;
|