@skrr-ai/cli 0.1.119 → 0.1.120
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 +106 -4
- 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/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/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/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/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/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/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/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/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/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/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/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/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 +20 -0
- package/dist/node_modules/@skrr-ai/data-provider/index.js +27085 -20250
- package/oclif.manifest.json +10566 -7247
- package/package.json +20 -2
|
@@ -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;
|
|
@@ -44,6 +44,13 @@ export const TOOL_APPROVAL_SIGNED_CAPABILITY = 'tool_approval_signed_v1';
|
|
|
44
44
|
*/
|
|
45
45
|
const ATTESTATION_SIGNING_DOMAIN = 'skrr-tool-approval-v2\n';
|
|
46
46
|
const KEYSET_SIGNING_DOMAIN = 'skrr-tool-approval-keyset-v1\n';
|
|
47
|
+
/**
|
|
48
|
+
* A distinct domain for the local personal-data executor. A tool approval is
|
|
49
|
+
* a user's decision about one action; an executor lease is evidence that the
|
|
50
|
+
* platform authenticated one particular trusted local process. Neither
|
|
51
|
+
* signature can be substituted for the other.
|
|
52
|
+
*/
|
|
53
|
+
const PERSONAL_DATA_EXECUTOR_LEASE_SIGNING_DOMAIN = 'skrr-personal-data-executor-lease-v1\n';
|
|
47
54
|
/** A key id: short, printable, and safe to put in a log line. */
|
|
48
55
|
const KID_PATTERN = /^[A-Za-z0-9._-]{1,64}$/;
|
|
49
56
|
/** Bounds on untrusted wire input, so a hostile document cannot cost much. */
|
|
@@ -51,6 +58,11 @@ const MAX_PAYLOAD_BYTES = 4096;
|
|
|
51
58
|
const MAX_KEYSET_BYTES = 16 * 1024;
|
|
52
59
|
const MAX_KEYSET_KEYS = 16;
|
|
53
60
|
const MAX_KEYSET_SIGNATURES = 16;
|
|
61
|
+
const MAX_EXECUTOR_LEASE_CAPABILITIES = 8;
|
|
62
|
+
const MAX_EXECUTOR_LEASE_TTL_MS = 5 * 60 * 1000;
|
|
63
|
+
const EXECUTOR_LEASE_CLOCK_SKEW_MS = 30_000;
|
|
64
|
+
const EXECUTOR_LEASE_ID_PATTERN = /^[A-Za-z0-9_-]{16,128}$/;
|
|
65
|
+
const EXECUTOR_CAPABILITY_PATTERN = /^[a-z0-9_.:-]{1,128}$/;
|
|
54
66
|
/** Derive the wire public key from a private (or public) Ed25519 KeyObject. */
|
|
55
67
|
export function approvalPublicKeyFromKeyObject(kid, key) {
|
|
56
68
|
const publicKey = key.type === 'private' ? createPublicKey(key) : key;
|
|
@@ -86,6 +98,24 @@ function verifySignature(domain, message, sigB64, key) {
|
|
|
86
98
|
function signMessage(domain, message, privateKey) {
|
|
87
99
|
return signEd25519(null, Buffer.from(domain + message, 'utf8'), privateKey).toString('base64');
|
|
88
100
|
}
|
|
101
|
+
function isExecutorLeaseId(value) {
|
|
102
|
+
return typeof value === 'string' && EXECUTOR_LEASE_ID_PATTERN.test(value);
|
|
103
|
+
}
|
|
104
|
+
function isExecutorLeaseText(value, max = 128) {
|
|
105
|
+
return typeof value === 'string' && value.length > 0 && value.length <= max && !/[\u0000-\u001f]/.test(value);
|
|
106
|
+
}
|
|
107
|
+
function canonicalExecutorCapabilities(value) {
|
|
108
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_EXECUTOR_LEASE_CAPABILITIES) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const capabilities = value.map((entry) => typeof entry === 'string' ? entry : '');
|
|
112
|
+
if (capabilities.some((entry) => !EXECUTOR_CAPABILITY_PATTERN.test(entry)))
|
|
113
|
+
return null;
|
|
114
|
+
const sorted = [...capabilities].sort();
|
|
115
|
+
if (new Set(sorted).size !== sorted.length)
|
|
116
|
+
return null;
|
|
117
|
+
return sorted;
|
|
118
|
+
}
|
|
89
119
|
/** Code-unit order: the same on every runtime, unlike `localeCompare`. */
|
|
90
120
|
function compareKid(a, b) {
|
|
91
121
|
if (a === b)
|
|
@@ -255,6 +285,114 @@ export function signToolApproval(args) {
|
|
|
255
285
|
sig: signMessage(ATTESTATION_SIGNING_DOMAIN, text, args.signer.privateKey),
|
|
256
286
|
};
|
|
257
287
|
}
|
|
288
|
+
/** Server-side mint. The caller must authorize the requesting user separately. */
|
|
289
|
+
export function signPersonalDataExecutorLease(args) {
|
|
290
|
+
const capabilities = canonicalExecutorCapabilities(args.capabilities);
|
|
291
|
+
const ttlMs = args.ttlMs ?? MAX_EXECUTOR_LEASE_TTL_MS;
|
|
292
|
+
const now = args.now ?? Date.now();
|
|
293
|
+
if (!isExecutorLeaseId(args.leaseId) ||
|
|
294
|
+
!isExecutorLeaseText(args.daemonId) ||
|
|
295
|
+
!isExecutorLeaseId(args.daemonInstanceId) ||
|
|
296
|
+
!isExecutorLeaseText(args.ownerUserId) ||
|
|
297
|
+
!capabilities ||
|
|
298
|
+
!Number.isSafeInteger(now) ||
|
|
299
|
+
!Number.isSafeInteger(ttlMs) ||
|
|
300
|
+
ttlMs <= 0 ||
|
|
301
|
+
ttlMs > MAX_EXECUTOR_LEASE_TTL_MS) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
const payload = {
|
|
305
|
+
v: 1,
|
|
306
|
+
leaseId: args.leaseId,
|
|
307
|
+
daemonId: args.daemonId,
|
|
308
|
+
daemonInstanceId: args.daemonInstanceId,
|
|
309
|
+
ownerUserId: args.ownerUserId,
|
|
310
|
+
capabilities,
|
|
311
|
+
issuedAt: now,
|
|
312
|
+
expiresAt: now + ttlMs,
|
|
313
|
+
};
|
|
314
|
+
const text = canonicalApprovalJson(payload);
|
|
315
|
+
return {
|
|
316
|
+
v: 1,
|
|
317
|
+
payload: text,
|
|
318
|
+
kid: args.signer.kid,
|
|
319
|
+
sig: signMessage(PERSONAL_DATA_EXECUTOR_LEASE_SIGNING_DOMAIN, text, args.signer.privateKey),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Daemon-side verifier. It trusts the same independently pinned platform
|
|
324
|
+
* keyset as v2 tool approvals, but a separate signature domain and schema.
|
|
325
|
+
*/
|
|
326
|
+
export function verifyPersonalDataExecutorLease(lease, expected, keyset) {
|
|
327
|
+
if (lease === undefined || lease === null)
|
|
328
|
+
return { ok: false, reason: 'absent' };
|
|
329
|
+
if (!lease || typeof lease !== 'object' || Array.isArray(lease)) {
|
|
330
|
+
return { ok: false, reason: 'malformed' };
|
|
331
|
+
}
|
|
332
|
+
const wire = lease;
|
|
333
|
+
if (wire.v !== 1 ||
|
|
334
|
+
typeof wire.payload !== 'string' ||
|
|
335
|
+
wire.payload.length === 0 ||
|
|
336
|
+
wire.payload.length > MAX_PAYLOAD_BYTES ||
|
|
337
|
+
typeof wire.kid !== 'string' ||
|
|
338
|
+
typeof wire.sig !== 'string') {
|
|
339
|
+
return { ok: false, reason: 'malformed' };
|
|
340
|
+
}
|
|
341
|
+
const pinnedKey = keyset.keys.find((key) => key.kid === wire.kid);
|
|
342
|
+
const key = pinnedKey ? ed25519PublicKey(pinnedKey.publicKeySpkiB64) : null;
|
|
343
|
+
if (!key)
|
|
344
|
+
return { ok: false, reason: 'unknown_key' };
|
|
345
|
+
if (!verifySignature(PERSONAL_DATA_EXECUTOR_LEASE_SIGNING_DOMAIN, wire.payload, wire.sig, key)) {
|
|
346
|
+
return { ok: false, reason: 'signature' };
|
|
347
|
+
}
|
|
348
|
+
let parsed;
|
|
349
|
+
try {
|
|
350
|
+
parsed = JSON.parse(wire.payload);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return { ok: false, reason: 'malformed' };
|
|
354
|
+
}
|
|
355
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
356
|
+
return { ok: false, reason: 'malformed' };
|
|
357
|
+
}
|
|
358
|
+
const payload = parsed;
|
|
359
|
+
const issuedAt = payload.issuedAt;
|
|
360
|
+
const expiresAt = payload.expiresAt;
|
|
361
|
+
const capabilities = canonicalExecutorCapabilities(payload.capabilities);
|
|
362
|
+
if (payload.v !== 1 ||
|
|
363
|
+
!isExecutorLeaseId(payload.leaseId) ||
|
|
364
|
+
!isExecutorLeaseText(payload.daemonId) ||
|
|
365
|
+
!isExecutorLeaseId(payload.daemonInstanceId) ||
|
|
366
|
+
!isExecutorLeaseText(payload.ownerUserId) ||
|
|
367
|
+
!capabilities ||
|
|
368
|
+
typeof issuedAt !== 'number' ||
|
|
369
|
+
typeof expiresAt !== 'number' ||
|
|
370
|
+
!Number.isSafeInteger(issuedAt) ||
|
|
371
|
+
!Number.isSafeInteger(expiresAt)) {
|
|
372
|
+
return { ok: false, reason: 'malformed' };
|
|
373
|
+
}
|
|
374
|
+
const expectedCapabilities = canonicalExecutorCapabilities(expected.capabilities);
|
|
375
|
+
if (!expectedCapabilities || capabilities.join('\u0000') !== expectedCapabilities.join('\u0000')) {
|
|
376
|
+
return { ok: false, reason: 'capabilities' };
|
|
377
|
+
}
|
|
378
|
+
if (payload.daemonId !== expected.daemonId || payload.daemonInstanceId !== expected.daemonInstanceId) {
|
|
379
|
+
return { ok: false, reason: 'daemon_binding' };
|
|
380
|
+
}
|
|
381
|
+
const now = expected.now ?? Date.now();
|
|
382
|
+
if (expiresAt <= issuedAt ||
|
|
383
|
+
expiresAt - issuedAt > MAX_EXECUTOR_LEASE_TTL_MS ||
|
|
384
|
+
issuedAt > now + EXECUTOR_LEASE_CLOCK_SKEW_MS) {
|
|
385
|
+
return { ok: false, reason: 'window' };
|
|
386
|
+
}
|
|
387
|
+
if (expiresAt <= now)
|
|
388
|
+
return { ok: false, reason: 'expired' };
|
|
389
|
+
return {
|
|
390
|
+
ok: true,
|
|
391
|
+
leaseId: payload.leaseId,
|
|
392
|
+
ownerUserId: payload.ownerUserId,
|
|
393
|
+
expiresAt,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
258
396
|
/**
|
|
259
397
|
* Daemon side: verify a v2 attestation against the pinned keyset and the call.
|
|
260
398
|
* Order matters: nothing in the payload is READ until the signature verified
|
|
@@ -114,6 +114,16 @@
|
|
|
114
114
|
"default": "./dist/cjs/dedicatedRuntimeBusyWire.js"
|
|
115
115
|
}
|
|
116
116
|
},
|
|
117
|
+
"./maintenance-report-wire": {
|
|
118
|
+
"import": {
|
|
119
|
+
"types": "./dist/esm/maintenanceReportWire.d.ts",
|
|
120
|
+
"default": "./dist/esm/maintenanceReportWire.js"
|
|
121
|
+
},
|
|
122
|
+
"require": {
|
|
123
|
+
"types": "./dist/cjs/maintenanceReportWire.d.ts",
|
|
124
|
+
"default": "./dist/cjs/maintenanceReportWire.js"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
117
127
|
"./computer-wire": {
|
|
118
128
|
"import": {
|
|
119
129
|
"types": "./dist/esm/computerWire.d.ts",
|
|
@@ -264,6 +274,16 @@
|
|
|
264
274
|
"default": "./dist/cjs/restartArtifact.js"
|
|
265
275
|
}
|
|
266
276
|
},
|
|
277
|
+
"./guided-update": {
|
|
278
|
+
"import": {
|
|
279
|
+
"types": "./dist/esm/guidedUpdate.d.ts",
|
|
280
|
+
"default": "./dist/esm/guidedUpdate.js"
|
|
281
|
+
},
|
|
282
|
+
"require": {
|
|
283
|
+
"types": "./dist/cjs/guidedUpdate.d.ts",
|
|
284
|
+
"default": "./dist/cjs/guidedUpdate.js"
|
|
285
|
+
}
|
|
286
|
+
},
|
|
267
287
|
"./tool-operation-outcome": {
|
|
268
288
|
"import": {
|
|
269
289
|
"types": "./dist/esm/toolOperationOutcome.d.ts",
|