@qodeca/xezar 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -15
- package/dist/agent-config/account-identity.d.ts +2 -8
- package/dist/agent-config/account-identity.js +54 -10
- package/dist/agent-config/account-identity.js.map +1 -1
- package/dist/agent-config/catalog.d.ts +49 -7
- package/dist/agent-config/catalog.js +107 -0
- package/dist/agent-config/catalog.js.map +1 -1
- package/dist/agent-config/model-settings/pi.d.ts +12 -6
- package/dist/agent-config/model-settings/pi.js +12 -6
- package/dist/agent-config/model-settings/pi.js.map +1 -1
- package/dist/agent-config/seed.js +10 -0
- package/dist/agent-config/seed.js.map +1 -1
- package/dist/contract/github.d.ts +15 -0
- package/dist/contract/index.d.ts +9 -0
- package/dist/contract/index.js +1527 -895
- package/dist/contract/mcp-api-reference.d.ts +174 -0
- package/dist/contract/mcp-audit.d.ts +103 -0
- package/dist/contract/mcp-discovery.d.ts +257 -0
- package/dist/contract/mcp-event-catalog.d.ts +121 -0
- package/dist/contract/mcp-event-catalog.test.d.ts +1 -0
- package/dist/contract/mcp-idempotency.d.ts +200 -0
- package/dist/contract/mcp-journal.d.ts +268 -0
- package/dist/contract/mcp-leader.d.ts +88 -0
- package/dist/contract/mcp-ownership.d.ts +78 -0
- package/dist/contract/mcp-versioning.d.ts +139 -0
- package/dist/contract/runs.d.ts +81 -0
- package/dist/core/agent-profiles.d.ts +15 -4
- package/dist/core/agent-profiles.js +22 -8
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/opencode-server-runner.js +71 -11
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/secret-redaction.d.ts +7 -1
- package/dist/core/secret-redaction.js +44 -10
- package/dist/core/secret-redaction.js.map +1 -1
- package/dist/index.js +138 -27
- package/dist/index.js.map +1 -1
- package/dist/machine-identity.d.ts +66 -0
- package/dist/machine-identity.js +96 -0
- package/dist/machine-identity.js.map +1 -0
- package/dist/mcp/adapters/claude-code.d.ts +41 -0
- package/dist/mcp/adapters/claude-code.js +40 -0
- package/dist/mcp/adapters/claude-code.js.map +1 -0
- package/dist/mcp/adapters/codex.d.ts +140 -0
- package/dist/mcp/adapters/codex.js +323 -0
- package/dist/mcp/adapters/codex.js.map +1 -0
- package/dist/mcp/adapters/opencode.d.ts +148 -0
- package/dist/mcp/adapters/opencode.js +528 -0
- package/dist/mcp/adapters/opencode.js.map +1 -0
- package/dist/mcp/adapters/pi-link.d.ts +102 -0
- package/dist/mcp/adapters/pi-link.js +235 -0
- package/dist/mcp/adapters/pi-link.js.map +1 -0
- package/dist/mcp/adapters/pi.d.ts +188 -0
- package/dist/mcp/adapters/pi.js +418 -0
- package/dist/mcp/adapters/pi.js.map +1 -0
- package/dist/mcp/api-reference.d.ts +26 -0
- package/dist/mcp/api-reference.js +133 -0
- package/dist/mcp/api-reference.js.map +1 -0
- package/dist/mcp/audit-trail.d.ts +162 -0
- package/dist/mcp/audit-trail.js +306 -0
- package/dist/mcp/audit-trail.js.map +1 -0
- package/dist/mcp/bridge.d.ts +81 -0
- package/dist/mcp/bridge.js +407 -0
- package/dist/mcp/bridge.js.map +1 -0
- package/dist/mcp/connection-file.d.ts +49 -0
- package/dist/mcp/connection-file.js +63 -0
- package/dist/mcp/connection-file.js.map +1 -0
- package/dist/mcp/echo-guard.d.ts +94 -0
- package/dist/mcp/echo-guard.js +160 -0
- package/dist/mcp/echo-guard.js.map +1 -0
- package/dist/mcp/event-catalog.d.ts +120 -0
- package/dist/mcp/event-catalog.js +306 -0
- package/dist/mcp/event-catalog.js.map +1 -0
- package/dist/mcp/event-controller.d.ts +230 -0
- package/dist/mcp/event-controller.js +596 -0
- package/dist/mcp/event-controller.js.map +1 -0
- package/dist/mcp/event-journal.d.ts +57 -0
- package/dist/mcp/event-journal.js +423 -0
- package/dist/mcp/event-journal.js.map +1 -0
- package/dist/mcp/index.d.ts +63 -0
- package/dist/mcp/index.js +386 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/ipc.d.ts +202 -0
- package/dist/mcp/ipc.js +206 -0
- package/dist/mcp/ipc.js.map +1 -0
- package/dist/mcp/leader-delivery.d.ts +119 -0
- package/dist/mcp/leader-delivery.js +450 -0
- package/dist/mcp/leader-delivery.js.map +1 -0
- package/dist/mcp/operation-receipts.d.ts +210 -0
- package/dist/mcp/operation-receipts.js +723 -0
- package/dist/mcp/operation-receipts.js.map +1 -0
- package/dist/mcp/project-catalogs.d.ts +23 -0
- package/dist/mcp/project-catalogs.js +25 -0
- package/dist/mcp/project-catalogs.js.map +1 -0
- package/dist/mcp/project-leaders.d.ts +26 -0
- package/dist/mcp/project-leaders.js +17 -0
- package/dist/mcp/project-leaders.js.map +1 -0
- package/dist/mcp/protocol.d.ts +50 -0
- package/dist/mcp/protocol.js +47 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/reconnect.d.ts +297 -0
- package/dist/mcp/reconnect.js +465 -0
- package/dist/mcp/reconnect.js.map +1 -0
- package/dist/mcp/resource-ownership.d.ts +228 -0
- package/dist/mcp/resource-ownership.js +390 -0
- package/dist/mcp/resource-ownership.js.map +1 -0
- package/dist/mcp/service-adapter.d.ts +16488 -0
- package/dist/mcp/service-adapter.js +148 -0
- package/dist/mcp/service-adapter.js.map +1 -0
- package/dist/mcp/service.d.ts +83 -0
- package/dist/mcp/service.js +233 -0
- package/dist/mcp/service.js.map +1 -0
- package/dist/mcp/session-binding.d.ts +105 -0
- package/dist/mcp/session-binding.js +163 -0
- package/dist/mcp/session-binding.js.map +1 -0
- package/dist/mcp/stale-write.d.ts +145 -0
- package/dist/mcp/stale-write.js +219 -0
- package/dist/mcp/stale-write.js.map +1 -0
- package/dist/mcp/tool.d.ts +48 -0
- package/dist/mcp/tool.js +24 -0
- package/dist/mcp/tool.js.map +1 -0
- package/dist/mcp/tools/discovery.d.ts +40 -0
- package/dist/mcp/tools/discovery.js +232 -0
- package/dist/mcp/tools/discovery.js.map +1 -0
- package/dist/mcp/tools/execution-control.d.ts +171 -0
- package/dist/mcp/tools/execution-control.js +513 -0
- package/dist/mcp/tools/execution-control.js.map +1 -0
- package/dist/mcp/tools/handoff-git.d.ts +26 -0
- package/dist/mcp/tools/handoff-git.js +544 -0
- package/dist/mcp/tools/handoff-git.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +2 -0
- package/dist/mcp/tools/index.js +23 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/leader-events.d.ts +46 -0
- package/dist/mcp/tools/leader-events.js +126 -0
- package/dist/mcp/tools/leader-events.js.map +1 -0
- package/dist/mcp/tools/local-handoff.d.ts +102 -0
- package/dist/mcp/tools/local-handoff.js +306 -0
- package/dist/mcp/tools/local-handoff.js.map +1 -0
- package/dist/mcp/tools/project-config.d.ts +340 -0
- package/dist/mcp/tools/project-config.js +1075 -0
- package/dist/mcp/tools/project-config.js.map +1 -0
- package/dist/mcp/tools/results-evidence.d.ts +112 -0
- package/dist/mcp/tools/results-evidence.js +1045 -0
- package/dist/mcp/tools/results-evidence.js.map +1 -0
- package/dist/mcp/tools/task-create.d.ts +70 -0
- package/dist/mcp/tools/task-create.js +473 -0
- package/dist/mcp/tools/task-create.js.map +1 -0
- package/dist/mcp/tools/task-reads.d.ts +102 -0
- package/dist/mcp/tools/task-reads.js +689 -0
- package/dist/mcp/tools/task-reads.js.map +1 -0
- package/dist/mcp/tools/work-organisation.d.ts +2 -0
- package/dist/mcp/tools/work-organisation.js +502 -0
- package/dist/mcp/tools/work-organisation.js.map +1 -0
- package/dist/paths.d.ts +17 -7
- package/dist/paths.js +18 -8
- package/dist/paths.js.map +1 -1
- package/dist/runs/project-writer.js +76 -11
- package/dist/runs/project-writer.js.map +1 -1
- package/dist/runs/retention.d.ts +24 -1
- package/dist/runs/retention.js +62 -1
- package/dist/runs/retention.js.map +1 -1
- package/dist/server/forge/github.js +44 -0
- package/dist/server/forge/github.js.map +1 -1
- package/dist/server/forge/types.d.ts +16 -0
- package/dist/server/project-context.d.ts +81 -8
- package/dist/server/project-context.js +143 -35
- package/dist/server/project-context.js.map +1 -1
- package/dist/server/server.d.ts +1050 -53
- package/dist/server/server.js +326 -120
- package/dist/server/server.js.map +1 -1
- package/dist/server/validators.d.ts +26 -6
- package/dist/server/validators.js +17 -0
- package/dist/server/validators.js.map +1 -1
- package/dist/skills-remote.js +64 -6
- package/dist/skills-remote.js.map +1 -1
- package/dist/workflows/run.d.ts +277 -12
- package/dist/workflows/run.js +554 -59
- package/dist/workflows/run.js.map +1 -1
- package/dist/workspace/agent-profiles.d.ts +30 -3
- package/dist/workspace/agent-profiles.js +39 -4
- package/dist/workspace/agent-profiles.js.map +1 -1
- package/dist/workspace/config.js +25 -4
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/project-owner.d.ts +210 -0
- package/dist/workspace/project-owner.js +518 -0
- package/dist/workspace/project-owner.js.map +1 -0
- package/package.json +5 -3
- package/scripts/pi-leader-extension.ts +420 -0
- package/scripts/sync-readme.mjs +83 -2
- package/web/dist/assets/{alert-dialog-BVsVcbFy.js → alert-dialog-D6rP6d39.js} +1 -1
- package/web/dist/assets/arrow-down-DMOfFIzW.js +1 -0
- package/web/dist/assets/arrow-left-C4Ec2BUg.js +1 -0
- package/web/dist/assets/{centered-state-Djgly6Ni.js → centered-state-B0ZqrPaa.js} +12 -12
- package/web/dist/assets/chevron-right-mHJ-crvn.js +1 -0
- package/web/dist/assets/{collapsible-DZ4D-DHG.js → collapsible-DchEHgUM.js} +1 -1
- package/web/dist/assets/{commit-list-B-z1JL9G.js → commit-list-BpMim1Rv.js} +1 -1
- package/web/dist/assets/{compare-variants-D62m4j3B.js → compare-variants-D5ItQbAq.js} +1 -1
- package/web/dist/assets/{diff-BKew9rQX.js → diff-HqqZ_WDR.js} +2 -2
- package/web/dist/assets/{diff-stat-D0mc5V6Z.js → diff-stat-NsU-3ecr.js} +1 -1
- package/web/dist/assets/{diff-view-xpIcuLIC.js → diff-view-B7c11Xux.js} +1 -1
- package/web/dist/assets/{dropdown-menu-CEQziH7X.js → dropdown-menu-CtTpnyod.js} +1 -1
- package/web/dist/assets/{editable-title-BCX3T2BY.js → editable-title-BXtuGQry.js} +1 -1
- package/web/dist/assets/{ellipsis-vertical-ByVdDc4z.js → ellipsis-vertical-MHtGZSH6.js} +1 -1
- package/web/dist/assets/{file-xBSB5hXO.js → file-DKLw5SOb.js} +1 -1
- package/web/dist/assets/{folder-CldgbZ5A.js → folder-Drw6-ufH.js} +1 -1
- package/web/dist/assets/{git-toolbar-DJANqg7Y.js → git-toolbar-BQH8bORV.js} +1 -1
- package/web/dist/assets/github-ynnpKFCJ.js +1 -0
- package/web/dist/assets/{image-preview-Cv5mZr0n.js → image-preview-DLhMN_R9.js} +1 -1
- package/web/dist/assets/index-BEsrNdXt.js +29 -0
- package/web/dist/assets/index-Da8DOBau.css +2 -0
- package/web/dist/assets/{markdown-CgEEE2ND.js → markdown-q365eN6Y.js} +1 -1
- package/web/dist/assets/{new-task-form-1r2bgsdD.js → new-task-form-CW-hNIWQ.js} +1 -1
- package/web/dist/assets/{pill-q-x22_ej.js → pill-B_TFulXa.js} +1 -1
- package/web/dist/assets/{project-router-VDN4jQM8.js → project-router-CLPc-ImZ.js} +1 -1
- package/web/dist/assets/{prompt-templates-BmZtCe-L.js → prompt-templates-CLc4RxyO.js} +1 -1
- package/web/dist/assets/{repo-git-btG0sdbO.js → repo-git-Is7R6-Tj.js} +1 -1
- package/web/dist/assets/{run-diff-vguEMJzh.js → run-diff-DHMvDlVo.js} +2 -2
- package/web/dist/assets/run-header-DvZJbQ8I.js +1 -0
- package/web/dist/assets/{search-x-QmhaAbg5.js → search-x-Ce2MMetu.js} +1 -1
- package/web/dist/assets/{skill-empty-hint-BrzUwByZ.js → skill-empty-hint-C_xCSvzB.js} +1 -1
- package/web/dist/assets/skills-CivqD4si.js +1 -0
- package/web/dist/assets/{sparkles-CP8QDOgX.js → sparkles-BCqPoj5g.js} +1 -1
- package/web/dist/assets/{square-terminal-CGypshSD.js → square-terminal-ikj5g-oK.js} +1 -1
- package/web/dist/assets/{tab-link-D4lFzzI1.js → tab-link-VsEjFV1Y.js} +1 -1
- package/web/dist/assets/{task-changes-BtZId14z.js → task-changes-CH-E81-_.js} +1 -1
- package/web/dist/assets/{task-commits-BoeyJXrv.js → task-commits-DqfkMFxp.js} +1 -1
- package/web/dist/assets/{task-files-DIy2oUnj.js → task-files-D_nkHZ_a.js} +2 -2
- package/web/dist/assets/task-thread-bxQompxJ.js +9 -0
- package/web/dist/assets/{textarea-CyBCyKIk.js → textarea-xajuW1L_.js} +1 -1
- package/web/dist/assets/{thread-loading-Cuq2jchC.js → thread-loading-C3JQZbIE.js} +1 -1
- package/web/dist/assets/{trash-2-B6Dp3ZNs.js → trash-2-hJrBhpUD.js} +1 -1
- package/web/dist/assets/{triangle-alert-xBya8FUp.js → triangle-alert-D1u82y1a.js} +1 -1
- package/web/dist/assets/{upload-D6MXEB9p.js → upload-Bn6FBZ59.js} +1 -1
- package/web/dist/assets/{use-desktop-CPBwzQ7M.js → use-desktop-DOIMfHfY.js} +1 -1
- package/web/dist/assets/{use-submit-shortcut-JTpNzU-u.js → use-submit-shortcut-Dj4DHTdE.js} +1 -1
- package/web/dist/assets/utils-YwBpOoRN.js +64 -0
- package/web/dist/assets/{workflows-DG115scU.js → workflows-CTaAKz2m.js} +3 -3
- package/web/dist/assets/{zoomable-image-GTYwLtkS.js → zoomable-image-C1-6P3tg.js} +1 -1
- package/web/dist/index.html +23 -23
- package/web/dist/assets/arrow-down-CYgAlZtW.js +0 -1
- package/web/dist/assets/arrow-left-BOqE0a3k.js +0 -1
- package/web/dist/assets/chevron-right-Cc9BWnbN.js +0 -1
- package/web/dist/assets/github-D6vK0jFn.js +0 -1
- package/web/dist/assets/index-CkmR6h7s.css +0 -2
- package/web/dist/assets/index-DVDqF4bM.js +0 -9
- package/web/dist/assets/refresh-cw-DcNCj2nu.js +0 -1
- package/web/dist/assets/run-header-DM7MiKj9.js +0 -1
- package/web/dist/assets/skills-DMfxQtrD.js +0 -1
- package/web/dist/assets/task-thread-CbMPmrWq.js +0 -9
- package/web/dist/assets/utils--z9hRtl3.js +0 -64
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type { AutomationStore } from '../automations/store.ts';
|
|
2
|
+
import type { AutomationDefinition, AutomationReceipt } from '../automations/types.ts';
|
|
3
|
+
import type { QueuedMessage, RunRecord, RunStore } from '../runs/store.ts';
|
|
4
|
+
import type { WorkflowDef } from '../workflows/types.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Per-resource ownership validation for the MCP project leader (#88).
|
|
7
|
+
*
|
|
8
|
+
* F-02: every identifier a leader supplies — a task, a message nested inside a task, a variant
|
|
9
|
+
* group and every run in it, a file path, a worktree, a workflow, an automation and its results,
|
|
10
|
+
* a pagination cursor, and every element of a bulk list — is proved to belong to the BOUND
|
|
11
|
+
* project before anything reads or changes it. A foreign identifier causes no read and no side
|
|
12
|
+
* effect.
|
|
13
|
+
*
|
|
14
|
+
* How ownership is proved. Each project has its own `RunStore` and `AutomationStore`, opened on
|
|
15
|
+
* its own `.local/xezar`, and the scope below holds exactly one project's pair. So "is this id
|
|
16
|
+
* A's?" is answered by A's own services (N-02: no JSON or NDJSON is read here, and nothing is
|
|
17
|
+
* written), and B's stores are simply not reachable from A's scope. Three things a store lookup
|
|
18
|
+
* alone does NOT prove, and which this module checks explicitly:
|
|
19
|
+
*
|
|
20
|
+
* - A record's FILESYSTEM reach. A run record carries an absolute `worktreePath`; the pick and
|
|
21
|
+
* reclaim flows delete that directory and the file and diff reads open it. The only path xezar
|
|
22
|
+
* ever creates for a run is `<root>/.local/xezar/worktrees/<runId>` (`worktreePathFor`,
|
|
23
|
+
* `createWorktree`), so a record naming anything else — another project's worktree, a moved
|
|
24
|
+
* root, a hand-edited index — is refused, and a worktree directory that is itself a symlink is
|
|
25
|
+
* refused too.
|
|
26
|
+
* - A manual automation check (`GET /automation-checks/:checkId`) lives in a WORKSPACE-level map
|
|
27
|
+
* that records no project (M-18). Its owner is stamped beside it with `stampOwner`; an
|
|
28
|
+
* unstamped check has no provable owner and is refused.
|
|
29
|
+
* - A cursor carries no project. MCP cursors are sealed with `sealCursor`, which binds them to
|
|
30
|
+
* the project AND the resource they page; any other cursor is refused.
|
|
31
|
+
*
|
|
32
|
+
* Refusals never name the resource (N-01). A foreign id and an id that does not exist anywhere
|
|
33
|
+
* answer the same `not_found`, with one fixed message that never interpolates caller input — an
|
|
34
|
+
* error that echoed an absolute path or an id would name the other project's resource back.
|
|
35
|
+
* Every refusal is also reported to the optional `audit` sink, the leader-visible log, as
|
|
36
|
+
* `{ check, code, index? }` only: the entry type has no field that could carry an identifier.
|
|
37
|
+
*
|
|
38
|
+
* Degradation is the caller's policy only for git/worktree HELPERS (AGENTS.md); an ownership
|
|
39
|
+
* check never inherits one. Every check here fails CLOSED: anything that cannot be proved —
|
|
40
|
+
* an unreadable directory, a missing owner stamp, a malformed id — is a refusal, never a pass.
|
|
41
|
+
*
|
|
42
|
+
* ## Partial-success policy (A-03)
|
|
43
|
+
*
|
|
44
|
+
* This is the documented outcome of a list that mixes this project's resources with foreign or
|
|
45
|
+
* unknown ones. It is a deliverable, not an implementation detail; the tests assert it exactly.
|
|
46
|
+
*
|
|
47
|
+
* 1. **Caller-supplied id lists** (`partitionOwned`). Every element is validated before ANY side
|
|
48
|
+
* effect, and the caller acts only afterwards, and only on `allowed`.
|
|
49
|
+
* - Owned elements PROCEED — this is a partial success, not an all-or-nothing batch.
|
|
50
|
+
* - Refused elements are reported by their input POSITION (`index`) with a code. The id itself
|
|
51
|
+
* is never echoed, and a foreign id is indistinguishable from a nonexistent one.
|
|
52
|
+
* - `outcome` states the case: `all` (every element owned), `partial` (some owned, some
|
|
53
|
+
* refused), `none` (elements were supplied and every one was refused — the caller performs
|
|
54
|
+
* no side effect and reports a refusal) and `empty` (nothing was supplied — which must not
|
|
55
|
+
* read the same as "every element was refused").
|
|
56
|
+
* - A repeated element is acted on once: its later positions are refused with `duplicate`
|
|
57
|
+
* when the first was allowed, and with the first position's own code when it was refused.
|
|
58
|
+
* 2. **A variant group is ONE resource, not a bulk list** (M-07). If any run in the group fails
|
|
59
|
+
* ownership, the whole group is refused — reading or picking a group means reading or
|
|
60
|
+
* changing every member, so a group with one foreign member is not this project's group.
|
|
61
|
+
* 3. **Server-computed sweeps** (`ownSweep`: archive-finished, read-all, reclaim). The candidate
|
|
62
|
+
* set comes only from the bound project's own store, so no caller id is involved. Archive and
|
|
63
|
+
* read-all change only store records and are owned by construction. Reclaim DELETES worktree
|
|
64
|
+
* directories, so `ownSweep` refuses the whole sweep unless every worktree-bearing record is
|
|
65
|
+
* contained. No production path calls that branch (#288): the enforcer itself
|
|
66
|
+
* (`reclaimWorktrees` in `runs/retention.ts`) now leaves out, per record, every worktree it
|
|
67
|
+
* cannot prove with `ownRun` / `ownWorktree`, for the route and the unattended sweeps alike.
|
|
68
|
+
*/
|
|
69
|
+
/** Why a resource was refused. Deliberately coarse: a finer code would be an existence oracle. */
|
|
70
|
+
export type OwnershipCode = 'not_found' | 'forbidden_path' | 'invalid_cursor' | 'duplicate';
|
|
71
|
+
/** Which check refused — the only other thing the leader-visible log learns about a refusal. */
|
|
72
|
+
export type OwnershipCheck = 'run' | 'queued-message' | 'group' | 'group-member' | 'worktree' | 'file' | 'automation' | 'automation-receipt' | 'automation-check' | 'workflow' | 'cursor' | 'sweep';
|
|
73
|
+
/** One leader-visible log entry. No field can hold an identifier, a path or a name, by type. */
|
|
74
|
+
export interface OwnershipAuditEntry {
|
|
75
|
+
check: OwnershipCheck;
|
|
76
|
+
code: OwnershipCode;
|
|
77
|
+
/** Position in a caller-supplied list, for bulk refusals only. */
|
|
78
|
+
index?: number;
|
|
79
|
+
}
|
|
80
|
+
export interface OwnershipRefusal {
|
|
81
|
+
ok: false;
|
|
82
|
+
code: OwnershipCode;
|
|
83
|
+
/** One fixed sentence per code. Never built from caller input or from a resource. */
|
|
84
|
+
message: string;
|
|
85
|
+
}
|
|
86
|
+
export type Owned<T> = {
|
|
87
|
+
ok: true;
|
|
88
|
+
value: T;
|
|
89
|
+
} | OwnershipRefusal;
|
|
90
|
+
/**
|
|
91
|
+
* The bound project, as far as ownership needs it. Built from the project's context
|
|
92
|
+
* (`ProjectContext` in `server/project-context.ts` satisfies it structurally) — never from a
|
|
93
|
+
* caller-supplied project id, which E-BIND forbids.
|
|
94
|
+
*/
|
|
95
|
+
export interface OwnershipProject {
|
|
96
|
+
root: string;
|
|
97
|
+
store: Pick<RunStore, 'getRun' | 'listRuns'>;
|
|
98
|
+
automationStore: Pick<AutomationStore, 'get' | 'latestReceipts'>;
|
|
99
|
+
}
|
|
100
|
+
export interface OwnershipScope {
|
|
101
|
+
/** Canonical (realpath'd) project root — the project's identity, free of the `default` alias. */
|
|
102
|
+
readonly root: string;
|
|
103
|
+
readonly store: OwnershipProject['store'];
|
|
104
|
+
readonly automationStore: OwnershipProject['automationStore'];
|
|
105
|
+
readonly audit: (entry: OwnershipAuditEntry) => void;
|
|
106
|
+
}
|
|
107
|
+
export declare function ownershipScope(project: OwnershipProject, audit?: (entry: OwnershipAuditEntry) => void): OwnershipScope;
|
|
108
|
+
/**
|
|
109
|
+
* A run of the bound project. Refused as `not_found` when the id is not in this project's store
|
|
110
|
+
* OR when the record's worktree path is not this project's — such a record would name another
|
|
111
|
+
* tree in every response and drive deletes into it.
|
|
112
|
+
*/
|
|
113
|
+
export declare function ownRun(scope: OwnershipScope, runId: unknown): Owned<RunRecord>;
|
|
114
|
+
/** A queued message (#472), proved to sit in the stack of a run of this project. */
|
|
115
|
+
export declare function ownQueuedMessage(scope: OwnershipScope, runId: unknown, msgId: unknown): Owned<{
|
|
116
|
+
run: RunRecord;
|
|
117
|
+
message: QueuedMessage;
|
|
118
|
+
}>;
|
|
119
|
+
/**
|
|
120
|
+
* A variant group, with EVERY member proved to belong (M-07) — the same members, in the same
|
|
121
|
+
* order, `groupRuns` in `server.ts` feeds the read and the pick.
|
|
122
|
+
*/
|
|
123
|
+
export declare function ownGroup(scope: OwnershipScope, groupId: unknown): Promise<Owned<{
|
|
124
|
+
groupId: string;
|
|
125
|
+
runs: RunRecord[];
|
|
126
|
+
}>>;
|
|
127
|
+
/** The pick's winner: the group is owned as a whole AND the named run is one of its members. */
|
|
128
|
+
export declare function ownGroupMember(scope: OwnershipScope, groupId: unknown, runId: unknown): Promise<Owned<{
|
|
129
|
+
groupId: string;
|
|
130
|
+
runs: RunRecord[];
|
|
131
|
+
run: RunRecord;
|
|
132
|
+
}>>;
|
|
133
|
+
/** A run's existing worktree directory, proved to be this project's (delete one worktree, diffs). */
|
|
134
|
+
export declare function ownWorktree(scope: OwnershipScope, runId: unknown): Promise<Owned<{
|
|
135
|
+
run: RunRecord;
|
|
136
|
+
path: string;
|
|
137
|
+
}>>;
|
|
138
|
+
/**
|
|
139
|
+
* The directory a run's files are read from — its worktree, or the project root for a
|
|
140
|
+
* `worktree: false` run — mirroring `workingDirectoryOf` in `server.ts`.
|
|
141
|
+
*/
|
|
142
|
+
export declare function ownWorkingDirectory(scope: OwnershipScope, runId: unknown): Promise<Owned<{
|
|
143
|
+
run: RunRecord;
|
|
144
|
+
directory: string;
|
|
145
|
+
}>>;
|
|
146
|
+
/**
|
|
147
|
+
* A path inside a run's working directory (A-04). Refused BEFORE any content is read when it is
|
|
148
|
+
* absolute, contains a `..` segment, a backslash, a NUL or a `.git` segment, starts in the
|
|
149
|
+
* engine's `.local/` runtime folder, or when ANY
|
|
150
|
+
* component on the way to it is a symlink — each component is `lstat`ed in order, so the walk
|
|
151
|
+
* never follows a link into another tree. A missing component is `not_found`.
|
|
152
|
+
*
|
|
153
|
+
* This runs IN FRONT of `readWorktreePath`, never instead of it: that reader keeps its own
|
|
154
|
+
* traversal, symlink, size-cap and `.git` checks, and the raw route keeps its image allowlist,
|
|
155
|
+
* so the file surface is checked twice and weakened nowhere. The returned `path` is normalized
|
|
156
|
+
* (`/`-separated, no empty or `.` segments) and is what the caller passes on.
|
|
157
|
+
*/
|
|
158
|
+
export declare function ownWorktreeFile(scope: OwnershipScope, runId: unknown, relPath: unknown): Promise<Owned<{
|
|
159
|
+
run: RunRecord;
|
|
160
|
+
directory: string;
|
|
161
|
+
path: string;
|
|
162
|
+
}>>;
|
|
163
|
+
/** An automation of this project. */
|
|
164
|
+
export declare function ownAutomation(scope: OwnershipScope, automationId: unknown): Owned<AutomationDefinition>;
|
|
165
|
+
/**
|
|
166
|
+
* An automation RESULT — a launch receipt (`POST /automation-log/:receiptId/retry`) — from this
|
|
167
|
+
* project's own receipt journal, whose automation is still this project's.
|
|
168
|
+
*/
|
|
169
|
+
export declare function ownAutomationReceipt(scope: OwnershipScope, receiptId: unknown): Owned<AutomationReceipt>;
|
|
170
|
+
/**
|
|
171
|
+
* Record which project created `record`. NO production code calls this, nor `ownAutomationCheck`
|
|
172
|
+
* below (#288): the only reader of a check, `GET /automation-checks/:checkId`, is workspace-level
|
|
173
|
+
* and has no bound project to compare a stamp against. Nothing is protected by this pair today —
|
|
174
|
+
* the MCP `get_automation_check` action proves a check's owner another way, by looking its
|
|
175
|
+
* automation up in the bound project.
|
|
176
|
+
*/
|
|
177
|
+
export declare function stampOwner(record: object, project: Pick<OwnershipProject, 'root'>): void;
|
|
178
|
+
/**
|
|
179
|
+
* A manual automation check (M-18: "checks and logs need a project owner even behind an
|
|
180
|
+
* unscoped endpoint"). The check must carry this project's owner stamp AND name an automation
|
|
181
|
+
* this project still has. An unstamped check has no provable owner and is refused.
|
|
182
|
+
*/
|
|
183
|
+
export declare function ownAutomationCheck<T extends {
|
|
184
|
+
automationId: string;
|
|
185
|
+
}>(scope: OwnershipScope, checkId: unknown, checks: ReadonlyMap<string, T>): Owned<T>;
|
|
186
|
+
/** A workflow from this project's own catalog (built-ins plus `.xezar/workflows`), by name. */
|
|
187
|
+
export declare function ownWorkflow(scope: OwnershipScope, name: unknown): Promise<Owned<WorkflowDef>>;
|
|
188
|
+
/** D-09 B-04: every MCP cursor is at most 2 048 bytes — `MAX_CURSOR_BYTES` in
|
|
189
|
+
* `runs/event-history.ts`, which that module does not export. */
|
|
190
|
+
export declare const MCP_CURSOR_MAX_BYTES = 2048;
|
|
191
|
+
/**
|
|
192
|
+
* Wrap a page cursor so it is valid only for this project AND this resource (`resource` is the
|
|
193
|
+
* caller's own stable key, e.g. `run:<id>:history`). The project is bound through the tag, never
|
|
194
|
+
* written into the cursor, so a cursor names nothing. Throws when the result would exceed
|
|
195
|
+
* B-04 — a server-produced inner cursor that large is a bug, and B-03 forbids cutting it.
|
|
196
|
+
*/
|
|
197
|
+
export declare function sealCursor(scope: OwnershipScope, resource: string, inner: string): string;
|
|
198
|
+
/**
|
|
199
|
+
* The inner cursor, when `cursor` was sealed for this project and this resource. A cursor from
|
|
200
|
+
* another project, for another resource, tampered with, malformed or oversized is one answer:
|
|
201
|
+
* `invalid_cursor`.
|
|
202
|
+
*/
|
|
203
|
+
export declare function openCursor(scope: OwnershipScope, resource: string, cursor: unknown): Owned<string>;
|
|
204
|
+
export interface BulkOwnership<T> {
|
|
205
|
+
/** See "Partial-success policy" in the module comment. */
|
|
206
|
+
outcome: 'all' | 'partial' | 'none' | 'empty';
|
|
207
|
+
allowed: Array<{
|
|
208
|
+
index: number;
|
|
209
|
+
value: T;
|
|
210
|
+
}>;
|
|
211
|
+
refused: Array<{
|
|
212
|
+
index: number;
|
|
213
|
+
code: OwnershipCode;
|
|
214
|
+
}>;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Validate EVERY element of a caller-supplied list before any side effect, and partition it per
|
|
218
|
+
* the documented policy. `own` is one of the single-resource checks above; its own refusal audit
|
|
219
|
+
* is replaced by one entry per refused position, so the log carries the index and no id.
|
|
220
|
+
*/
|
|
221
|
+
export declare function partitionOwned<T>(scope: OwnershipScope, check: OwnershipCheck, ids: readonly unknown[], own: (scope: OwnershipScope, id: unknown) => Owned<T> | Promise<Owned<T>>): Promise<BulkOwnership<T>>;
|
|
222
|
+
export type SweepKind = 'archive-finished' | 'read-all' | 'reclaim-worktrees';
|
|
223
|
+
/**
|
|
224
|
+
* A server-computed sweep over the bound project's own store (policy point 3). Reclaim is refused
|
|
225
|
+
* as a whole when any record's worktree cannot be proved this project's, because its service
|
|
226
|
+
* deletes every candidate directory and cannot be told to skip one.
|
|
227
|
+
*/
|
|
228
|
+
export declare function ownSweep(scope: OwnershipScope, kind: SweepKind): Promise<Owned<SweepKind>>;
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { lstat, realpath } from 'node:fs/promises';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { projectDataDir } from '../project-data-paths.js';
|
|
7
|
+
import { loadWorkflows } from '../workflows/load.js';
|
|
8
|
+
const MESSAGES = {
|
|
9
|
+
not_found: 'not found in this project',
|
|
10
|
+
forbidden_path: 'path not allowed — use a relative path inside the task worktree, without "..", symlinks or .git',
|
|
11
|
+
invalid_cursor: 'invalid cursor — request the first page again',
|
|
12
|
+
duplicate: 'listed more than once — acted on once',
|
|
13
|
+
};
|
|
14
|
+
export function ownershipScope(project, audit) {
|
|
15
|
+
return {
|
|
16
|
+
root: canonical(project.root),
|
|
17
|
+
store: project.store,
|
|
18
|
+
automationStore: project.automationStore,
|
|
19
|
+
audit: audit ?? (() => { }),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function canonical(path) {
|
|
23
|
+
try {
|
|
24
|
+
return realpathSync(path);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// A root that cannot be resolved stays lexical; every containment check below then compares
|
|
28
|
+
// realpaths against it and fails closed, which is the right answer for a vanished project.
|
|
29
|
+
return resolve(path);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function refuse(scope, check, code, index) {
|
|
33
|
+
try {
|
|
34
|
+
scope.audit(index === undefined ? { check, code } : { check, code, index });
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// The log is the sink's problem; a throwing sink must not turn a refusal into a pass.
|
|
38
|
+
}
|
|
39
|
+
return { ok: false, code, message: MESSAGES[code] };
|
|
40
|
+
}
|
|
41
|
+
const MAX_ID_LENGTH = 256;
|
|
42
|
+
/** A caller-supplied id worth looking up at all. Anything else is simply not found. */
|
|
43
|
+
function isId(value) {
|
|
44
|
+
return typeof value === 'string' && value.length > 0 && value.length <= MAX_ID_LENGTH && !value.includes('\0');
|
|
45
|
+
}
|
|
46
|
+
/** A run id that is safe as ONE path segment — the precondition for deriving its worktree path. */
|
|
47
|
+
const SAFE_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
48
|
+
/** The one worktree path xezar creates for a run of this project. */
|
|
49
|
+
function expectedWorktreePath(scope, runId) {
|
|
50
|
+
if (!SAFE_SEGMENT.test(runId) || runId.length > MAX_ID_LENGTH)
|
|
51
|
+
return null;
|
|
52
|
+
return join(projectDataDir(scope.root), 'worktrees', runId);
|
|
53
|
+
}
|
|
54
|
+
/** Lexical half of worktree containment: synchronous, touches no filesystem. */
|
|
55
|
+
function worktreePathIsOurs(scope, run) {
|
|
56
|
+
if (run.worktreePath === undefined)
|
|
57
|
+
return true;
|
|
58
|
+
const expected = expectedWorktreePath(scope, run.id);
|
|
59
|
+
return expected !== null && resolve(run.worktreePath) === expected;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Filesystem half: an EXISTING worktree directory must be a real directory at exactly the
|
|
63
|
+
* expected path — not a symlink, and with no symlinked component between the root and it
|
|
64
|
+
* (`realpath` would differ). An absent directory (reclaimed) has nothing to reach and passes.
|
|
65
|
+
* Only `lstat`/`realpath` of this project's own paths run here: no content is read.
|
|
66
|
+
*/
|
|
67
|
+
async function worktreeDirIsOurs(scope, run) {
|
|
68
|
+
if (!worktreePathIsOurs(scope, run))
|
|
69
|
+
return false;
|
|
70
|
+
if (run.worktreePath === undefined)
|
|
71
|
+
return true;
|
|
72
|
+
const expected = resolve(run.worktreePath);
|
|
73
|
+
let info;
|
|
74
|
+
try {
|
|
75
|
+
info = await lstat(expected);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
return err.code === 'ENOENT';
|
|
79
|
+
}
|
|
80
|
+
if (info.isSymbolicLink() || !info.isDirectory())
|
|
81
|
+
return false;
|
|
82
|
+
try {
|
|
83
|
+
return (await realpath(expected)) === expected;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A run of the bound project. Refused as `not_found` when the id is not in this project's store
|
|
91
|
+
* OR when the record's worktree path is not this project's — such a record would name another
|
|
92
|
+
* tree in every response and drive deletes into it.
|
|
93
|
+
*/
|
|
94
|
+
export function ownRun(scope, runId) {
|
|
95
|
+
if (!isId(runId))
|
|
96
|
+
return refuse(scope, 'run', 'not_found');
|
|
97
|
+
const run = scope.store.getRun(runId);
|
|
98
|
+
if (!run || run.id !== runId || !worktreePathIsOurs(scope, run))
|
|
99
|
+
return refuse(scope, 'run', 'not_found');
|
|
100
|
+
return { ok: true, value: run };
|
|
101
|
+
}
|
|
102
|
+
/** A queued message (#472), proved to sit in the stack of a run of this project. */
|
|
103
|
+
export function ownQueuedMessage(scope, runId, msgId) {
|
|
104
|
+
const run = ownRun(scope, runId);
|
|
105
|
+
if (!run.ok)
|
|
106
|
+
return run;
|
|
107
|
+
const message = isId(msgId) ? (run.value.queuedMessages ?? []).find((m) => m.id === msgId) : undefined;
|
|
108
|
+
if (!message)
|
|
109
|
+
return refuse(scope, 'queued-message', 'not_found');
|
|
110
|
+
return { ok: true, value: { run: run.value, message } };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A variant group, with EVERY member proved to belong (M-07) — the same members, in the same
|
|
114
|
+
* order, `groupRuns` in `server.ts` feeds the read and the pick.
|
|
115
|
+
*/
|
|
116
|
+
export async function ownGroup(scope, groupId) {
|
|
117
|
+
if (!isId(groupId))
|
|
118
|
+
return refuse(scope, 'group', 'not_found');
|
|
119
|
+
const runs = scope.store
|
|
120
|
+
.listRuns()
|
|
121
|
+
.filter((r) => r.groupId === groupId)
|
|
122
|
+
.sort((a, b) => (a.variant ?? '').localeCompare(b.variant ?? ''));
|
|
123
|
+
if (runs.length === 0)
|
|
124
|
+
return refuse(scope, 'group', 'not_found');
|
|
125
|
+
for (const run of runs) {
|
|
126
|
+
if (scope.store.getRun(run.id) !== run || !(await worktreeDirIsOurs(scope, run))) {
|
|
127
|
+
return refuse(scope, 'group', 'not_found');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { ok: true, value: { groupId, runs } };
|
|
131
|
+
}
|
|
132
|
+
/** The pick's winner: the group is owned as a whole AND the named run is one of its members. */
|
|
133
|
+
export async function ownGroupMember(scope, groupId, runId) {
|
|
134
|
+
const group = await ownGroup(scope, groupId);
|
|
135
|
+
if (!group.ok)
|
|
136
|
+
return group;
|
|
137
|
+
const run = isId(runId) ? group.value.runs.find((r) => r.id === runId) : undefined;
|
|
138
|
+
if (!run)
|
|
139
|
+
return refuse(scope, 'group-member', 'not_found');
|
|
140
|
+
return { ok: true, value: { ...group.value, run } };
|
|
141
|
+
}
|
|
142
|
+
/** A run's existing worktree directory, proved to be this project's (delete one worktree, diffs). */
|
|
143
|
+
export async function ownWorktree(scope, runId) {
|
|
144
|
+
const run = ownRun(scope, runId);
|
|
145
|
+
if (!run.ok)
|
|
146
|
+
return run;
|
|
147
|
+
const path = run.value.worktreePath;
|
|
148
|
+
if (path === undefined || !(await worktreeDirIsOurs(scope, run.value)))
|
|
149
|
+
return refuse(scope, 'worktree', 'not_found');
|
|
150
|
+
try {
|
|
151
|
+
if (!(await lstat(path)).isDirectory())
|
|
152
|
+
return refuse(scope, 'worktree', 'not_found');
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return refuse(scope, 'worktree', 'not_found');
|
|
156
|
+
}
|
|
157
|
+
return { ok: true, value: { run: run.value, path: resolve(path) } };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The directory a run's files are read from — its worktree, or the project root for a
|
|
161
|
+
* `worktree: false` run — mirroring `workingDirectoryOf` in `server.ts`.
|
|
162
|
+
*/
|
|
163
|
+
export async function ownWorkingDirectory(scope, runId) {
|
|
164
|
+
const run = ownRun(scope, runId);
|
|
165
|
+
if (!run.ok)
|
|
166
|
+
return run;
|
|
167
|
+
if (run.value.worktree === false)
|
|
168
|
+
return { ok: true, value: { run: run.value, directory: scope.root } };
|
|
169
|
+
const worktree = await ownWorktree(scope, runId);
|
|
170
|
+
if (!worktree.ok)
|
|
171
|
+
return worktree;
|
|
172
|
+
return { ok: true, value: { run: run.value, directory: worktree.value.path } };
|
|
173
|
+
}
|
|
174
|
+
const MAX_PATH_LENGTH = 4_096;
|
|
175
|
+
/**
|
|
176
|
+
* A path inside a run's working directory (A-04). Refused BEFORE any content is read when it is
|
|
177
|
+
* absolute, contains a `..` segment, a backslash, a NUL or a `.git` segment, starts in the
|
|
178
|
+
* engine's `.local/` runtime folder, or when ANY
|
|
179
|
+
* component on the way to it is a symlink — each component is `lstat`ed in order, so the walk
|
|
180
|
+
* never follows a link into another tree. A missing component is `not_found`.
|
|
181
|
+
*
|
|
182
|
+
* This runs IN FRONT of `readWorktreePath`, never instead of it: that reader keeps its own
|
|
183
|
+
* traversal, symlink, size-cap and `.git` checks, and the raw route keeps its image allowlist,
|
|
184
|
+
* so the file surface is checked twice and weakened nowhere. The returned `path` is normalized
|
|
185
|
+
* (`/`-separated, no empty or `.` segments) and is what the caller passes on.
|
|
186
|
+
*/
|
|
187
|
+
export async function ownWorktreeFile(scope, runId, relPath) {
|
|
188
|
+
const dir = await ownWorkingDirectory(scope, runId);
|
|
189
|
+
if (!dir.ok)
|
|
190
|
+
return dir;
|
|
191
|
+
const raw = relPath ?? '';
|
|
192
|
+
if (typeof raw !== 'string' || raw.length > MAX_PATH_LENGTH)
|
|
193
|
+
return refuse(scope, 'file', 'forbidden_path');
|
|
194
|
+
if (raw.includes('\0') || raw.includes('\\') || raw.startsWith('/') || /^[A-Za-z]:/.test(raw)) {
|
|
195
|
+
return refuse(scope, 'file', 'forbidden_path');
|
|
196
|
+
}
|
|
197
|
+
const segments = raw.split('/').filter((s) => s !== '' && s !== '.');
|
|
198
|
+
// `.git` compared case-insensitively: on a case-insensitive filesystem `.GIT` IS `.git`.
|
|
199
|
+
if (segments.some((s) => s === '..' || s.toLowerCase() === '.git'))
|
|
200
|
+
return refuse(scope, 'file', 'forbidden_path');
|
|
201
|
+
// A `worktree: false` run reads from the project root, where `.local/` holds the engine's
|
|
202
|
+
// runtime state — every task's transcript and the launch key, which F-15 forbids returning.
|
|
203
|
+
if (segments[0]?.toLowerCase() === '.local')
|
|
204
|
+
return refuse(scope, 'file', 'forbidden_path');
|
|
205
|
+
let current = dir.value.directory;
|
|
206
|
+
for (const [i, segment] of segments.entries()) {
|
|
207
|
+
current = join(current, segment);
|
|
208
|
+
let info;
|
|
209
|
+
try {
|
|
210
|
+
info = await lstat(current);
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
return err.code === 'ENOENT' || err.code === 'ENOTDIR'
|
|
214
|
+
? refuse(scope, 'file', 'not_found')
|
|
215
|
+
: refuse(scope, 'file', 'forbidden_path');
|
|
216
|
+
}
|
|
217
|
+
if (info.isSymbolicLink())
|
|
218
|
+
return refuse(scope, 'file', 'forbidden_path');
|
|
219
|
+
if (i < segments.length - 1 && !info.isDirectory())
|
|
220
|
+
return refuse(scope, 'file', 'not_found');
|
|
221
|
+
}
|
|
222
|
+
// No component is a link, so the realpath can only differ through something else entirely
|
|
223
|
+
// (a bind mount, a race): fail closed rather than reason about it.
|
|
224
|
+
try {
|
|
225
|
+
if ((await realpath(current)) !== current)
|
|
226
|
+
return refuse(scope, 'file', 'forbidden_path');
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return refuse(scope, 'file', 'not_found');
|
|
230
|
+
}
|
|
231
|
+
return { ok: true, value: { run: dir.value.run, directory: dir.value.directory, path: segments.join('/') } };
|
|
232
|
+
}
|
|
233
|
+
/** An automation of this project. */
|
|
234
|
+
export function ownAutomation(scope, automationId) {
|
|
235
|
+
const automation = isId(automationId) ? scope.automationStore.get(automationId) : undefined;
|
|
236
|
+
if (!automation || automation.id !== automationId)
|
|
237
|
+
return refuse(scope, 'automation', 'not_found');
|
|
238
|
+
return { ok: true, value: automation };
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* An automation RESULT — a launch receipt (`POST /automation-log/:receiptId/retry`) — from this
|
|
242
|
+
* project's own receipt journal, whose automation is still this project's.
|
|
243
|
+
*/
|
|
244
|
+
export function ownAutomationReceipt(scope, receiptId) {
|
|
245
|
+
if (!isId(receiptId))
|
|
246
|
+
return refuse(scope, 'automation-receipt', 'not_found');
|
|
247
|
+
const receipt = [...scope.automationStore.latestReceipts().values()].find((row) => row.receiptId === receiptId);
|
|
248
|
+
if (!receipt || scope.automationStore.get(receipt.automationId) === undefined) {
|
|
249
|
+
return refuse(scope, 'automation-receipt', 'not_found');
|
|
250
|
+
}
|
|
251
|
+
return { ok: true, value: receipt };
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Owners of records that live OUTSIDE any project's store — today the manual automation checks,
|
|
255
|
+
* which sit in one workspace-level map. Held beside the record rather than on it, so the record's
|
|
256
|
+
* wire shape (`automationCheckSchema`) is unchanged and no project identity is ever serialized.
|
|
257
|
+
* Weak, so an evicted check takes its owner with it.
|
|
258
|
+
*/
|
|
259
|
+
const owners = new WeakMap();
|
|
260
|
+
/**
|
|
261
|
+
* Record which project created `record`. NO production code calls this, nor `ownAutomationCheck`
|
|
262
|
+
* below (#288): the only reader of a check, `GET /automation-checks/:checkId`, is workspace-level
|
|
263
|
+
* and has no bound project to compare a stamp against. Nothing is protected by this pair today —
|
|
264
|
+
* the MCP `get_automation_check` action proves a check's owner another way, by looking its
|
|
265
|
+
* automation up in the bound project.
|
|
266
|
+
*/
|
|
267
|
+
export function stampOwner(record, project) {
|
|
268
|
+
owners.set(record, canonical(project.root));
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* A manual automation check (M-18: "checks and logs need a project owner even behind an
|
|
272
|
+
* unscoped endpoint"). The check must carry this project's owner stamp AND name an automation
|
|
273
|
+
* this project still has. An unstamped check has no provable owner and is refused.
|
|
274
|
+
*/
|
|
275
|
+
export function ownAutomationCheck(scope, checkId, checks) {
|
|
276
|
+
const check = isId(checkId) ? checks.get(checkId) : undefined;
|
|
277
|
+
if (!check || owners.get(check) !== scope.root || scope.automationStore.get(check.automationId) === undefined) {
|
|
278
|
+
return refuse(scope, 'automation-check', 'not_found');
|
|
279
|
+
}
|
|
280
|
+
return { ok: true, value: check };
|
|
281
|
+
}
|
|
282
|
+
/** A workflow from this project's own catalog (built-ins plus `.xezar/workflows`), by name. */
|
|
283
|
+
export async function ownWorkflow(scope, name) {
|
|
284
|
+
if (!isId(name))
|
|
285
|
+
return refuse(scope, 'workflow', 'not_found');
|
|
286
|
+
const { workflows } = await loadWorkflows(scope.root);
|
|
287
|
+
const workflow = workflows.find((w) => w.name === name);
|
|
288
|
+
if (!workflow)
|
|
289
|
+
return refuse(scope, 'workflow', 'not_found');
|
|
290
|
+
return { ok: true, value: workflow };
|
|
291
|
+
}
|
|
292
|
+
// ---- cursors ---------------------------------------------------------------
|
|
293
|
+
/** D-09 B-04: every MCP cursor is at most 2 048 bytes — `MAX_CURSOR_BYTES` in
|
|
294
|
+
* `runs/event-history.ts`, which that module does not export. */
|
|
295
|
+
export const MCP_CURSOR_MAX_BYTES = 2_048;
|
|
296
|
+
/**
|
|
297
|
+
* Per-process key. Zero config: nothing to store or rotate. The cost is that a cursor does not
|
|
298
|
+
* survive a service restart — it then answers `invalid_cursor`, and the leader re-reads the first
|
|
299
|
+
* page, the same recovery an expired history cursor already asks for.
|
|
300
|
+
*/
|
|
301
|
+
const CURSOR_KEY = randomBytes(32);
|
|
302
|
+
const sealedCursorSchema = z.object({ v: z.literal(1), c: z.string(), t: z.string() }).strict();
|
|
303
|
+
function cursorTag(scope, resource, inner) {
|
|
304
|
+
return createHmac('sha256', CURSOR_KEY).update(`${scope.root}\0${resource}\0${inner}`).digest().subarray(0, 16);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Wrap a page cursor so it is valid only for this project AND this resource (`resource` is the
|
|
308
|
+
* caller's own stable key, e.g. `run:<id>:history`). The project is bound through the tag, never
|
|
309
|
+
* written into the cursor, so a cursor names nothing. Throws when the result would exceed
|
|
310
|
+
* B-04 — a server-produced inner cursor that large is a bug, and B-03 forbids cutting it.
|
|
311
|
+
*/
|
|
312
|
+
export function sealCursor(scope, resource, inner) {
|
|
313
|
+
const cursor = Buffer.from(JSON.stringify({ v: 1, c: inner, t: cursorTag(scope, resource, inner).toString('base64url') }), 'utf8').toString('base64url');
|
|
314
|
+
if (cursor.length > MCP_CURSOR_MAX_BYTES)
|
|
315
|
+
throw new RangeError(`sealed cursor exceeds ${MCP_CURSOR_MAX_BYTES} bytes`);
|
|
316
|
+
return cursor;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* The inner cursor, when `cursor` was sealed for this project and this resource. A cursor from
|
|
320
|
+
* another project, for another resource, tampered with, malformed or oversized is one answer:
|
|
321
|
+
* `invalid_cursor`.
|
|
322
|
+
*/
|
|
323
|
+
export function openCursor(scope, resource, cursor) {
|
|
324
|
+
if (typeof cursor !== 'string' || cursor.length === 0 || cursor.length > MCP_CURSOR_MAX_BYTES) {
|
|
325
|
+
return refuse(scope, 'cursor', 'invalid_cursor');
|
|
326
|
+
}
|
|
327
|
+
let parsed;
|
|
328
|
+
try {
|
|
329
|
+
parsed = sealedCursorSchema.safeParse(JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')));
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
return refuse(scope, 'cursor', 'invalid_cursor');
|
|
333
|
+
}
|
|
334
|
+
if (!parsed.success)
|
|
335
|
+
return refuse(scope, 'cursor', 'invalid_cursor');
|
|
336
|
+
const expected = cursorTag(scope, resource, parsed.data.c);
|
|
337
|
+
const given = Buffer.from(parsed.data.t, 'base64url');
|
|
338
|
+
if (given.length !== expected.length || !timingSafeEqual(given, expected)) {
|
|
339
|
+
return refuse(scope, 'cursor', 'invalid_cursor');
|
|
340
|
+
}
|
|
341
|
+
return { ok: true, value: parsed.data.c };
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Validate EVERY element of a caller-supplied list before any side effect, and partition it per
|
|
345
|
+
* the documented policy. `own` is one of the single-resource checks above; its own refusal audit
|
|
346
|
+
* is replaced by one entry per refused position, so the log carries the index and no id.
|
|
347
|
+
*/
|
|
348
|
+
export async function partitionOwned(scope, check, ids, own) {
|
|
349
|
+
const quiet = { ...scope, audit: () => { } };
|
|
350
|
+
const allowed = [];
|
|
351
|
+
const refused = [];
|
|
352
|
+
const firstSeen = new Map();
|
|
353
|
+
for (const [index, id] of ids.entries()) {
|
|
354
|
+
const seen = firstSeen.get(id);
|
|
355
|
+
if (seen) {
|
|
356
|
+
const code = seen.code ?? 'duplicate';
|
|
357
|
+
refused.push({ index, code });
|
|
358
|
+
refuse(scope, check, code, index);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
const result = await own(quiet, id);
|
|
362
|
+
if (result.ok) {
|
|
363
|
+
allowed.push({ index, value: result.value });
|
|
364
|
+
firstSeen.set(id, { index });
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
refused.push({ index, code: result.code });
|
|
368
|
+
firstSeen.set(id, { index, code: result.code });
|
|
369
|
+
refuse(scope, check, result.code, index);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const outcome = ids.length === 0 ? 'empty' : refused.length === 0 ? 'all' : allowed.length === 0 ? 'none' : 'partial';
|
|
373
|
+
return { outcome, allowed, refused };
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* A server-computed sweep over the bound project's own store (policy point 3). Reclaim is refused
|
|
377
|
+
* as a whole when any record's worktree cannot be proved this project's, because its service
|
|
378
|
+
* deletes every candidate directory and cannot be told to skip one.
|
|
379
|
+
*/
|
|
380
|
+
export async function ownSweep(scope, kind) {
|
|
381
|
+
if (kind === 'reclaim-worktrees') {
|
|
382
|
+
for (const run of scope.store.listRuns()) {
|
|
383
|
+
if (run.worktreePath !== undefined && !(await worktreeDirIsOurs(scope, run))) {
|
|
384
|
+
return refuse(scope, 'sweep', 'not_found');
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return { ok: true, value: kind };
|
|
389
|
+
}
|
|
390
|
+
//# sourceMappingURL=resource-ownership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-ownership.js","sourceRoot":"","sources":["../../src/mcp/resource-ownership.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAuGrD,MAAM,QAAQ,GAAkC;IAC9C,SAAS,EAAE,2BAA2B;IACtC,cAAc,EACZ,iGAAiG;IACnG,cAAc,EAAE,+CAA+C;IAC/D,SAAS,EAAE,uCAAuC;CACnD,CAAC;AAqBF,MAAM,UAAU,cAAc,CAC5B,OAAyB,EACzB,KAA4C;IAE5C,OAAO;QACL,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;QAC7B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,KAAK,EAAE,KAAK,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;KAC3B,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,4FAA4F;QAC5F,2FAA2F;QAC3F,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CAAC,KAAqB,EAAE,KAAqB,EAAE,IAAmB,EAAE,KAAc;IAC/F,IAAI,CAAC;QACH,KAAK,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9E,CAAC;IAAC,MAAM,CAAC;QACP,sFAAsF;IACxF,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;AACtD,CAAC;AAED,MAAM,aAAa,GAAG,GAAG,CAAC;AAE1B,uFAAuF;AACvF,SAAS,IAAI,CAAC,KAAc;IAC1B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,aAAa,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACjH,CAAC;AAED,mGAAmG;AACnG,MAAM,YAAY,GAAG,8BAA8B,CAAC;AAEpD,qEAAqE;AACrE,SAAS,oBAAoB,CAAC,KAAqB,EAAE,KAAa;IAChE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,aAAa;QAAE,OAAO,IAAI,CAAC;IAC3E,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;AAC9D,CAAC;AAED,gFAAgF;AAChF,SAAS,kBAAkB,CAAC,KAAqB,EAAE,GAAc;IAC/D,IAAI,GAAG,CAAC,YAAY,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAChD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IACrD,OAAO,QAAQ,KAAK,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC;AACrE,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,iBAAiB,CAAC,KAAqB,EAAE,GAAc;IACpE,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,IAAI,GAAG,CAAC,YAAY,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAChD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC3C,IAAI,IAAI,CAAC;IACT,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAQ,GAA6B,CAAC,IAAI,KAAK,QAAQ,CAAC;IAC1D,CAAC;IACD,IAAI,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;QAAE,OAAO,KAAK,CAAC;IAC/D,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC,KAAK,QAAQ,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAC,KAAqB,EAAE,KAAc;IAC1D,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACtC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IAC1G,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AAClC,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,gBAAgB,CAC9B,KAAqB,EACrB,KAAc,EACd,KAAc;IAEd,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACjC,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,OAAO,GAAG,CAAC;IACxB,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACvG,IAAI,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,gBAAgB,EAAE,WAAW,CAAC,CAAC;IAClE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,KAAqB,EACrB,OAAgB;IAEhB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;IAC/D,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK;SACrB,QAAQ,EAAE;SACV,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC;SACpC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC;IACpE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;IAClE,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,MAAM,iBAAiB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;YACjF,OAAO,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;AAChD,CAAC;AAED,gGAAgG;AAChG,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,KAAqB,EACrB,OAAgB,EAChB,KAAc;IAEd,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC7C,IAAI,CAAC,KAAK,CAAC,EAAE;QAAE,OAAO,KAAK,CAAC;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACnF,IAAI,CAAC,GAAG;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,cAAc,EAAE,WAAW,CAAC,CAAC;IAC5D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC;AACtD,CAAC;AAED,qGAAqG;AACrG,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAqB,EAAE,KAAc;IACrE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACjC,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,OAAO,GAAG,CAAC;IACxB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC;IACpC,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,iBAAiB,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;IACtH,IAAI,CAAC;QACH,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE;YAAE,OAAO,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;IACxF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;AACtE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAqB,EACrB,KAAc;IAEd,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACjC,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,OAAO,GAAG,CAAC;IACxB,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,KAAK,KAAK;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;IACxG,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACjD,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,OAAO,QAAQ,CAAC;IAClC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;AACjF,CAAC;AAED,MAAM,eAAe,GAAG,KAAK,CAAC;AAE9B;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,KAAqB,EACrB,KAAc,EACd,OAAgB;IAEhB,MAAM,GAAG,GAAG,MAAM,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACpD,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,OAAO,GAAG,CAAC;IACxB,MAAM,GAAG,GAAG,OAAO,IAAI,EAAE,CAAC;IAC1B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,eAAe;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC5G,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9F,OAAO,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACjD,CAAC;IACD,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;IACrE,yFAAyF;IACzF,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACnH,0FAA0F;IAC1F,4FAA4F;IAC5F,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAE5F,IAAI,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC;IAClC,KAAK,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9C,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACjC,IAAI,IAAI,CAAC;QACT,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAQ,GAA6B,CAAC,IAAI,KAAK,QAAQ,IAAK,GAA6B,CAAC,IAAI,KAAK,SAAS;gBAC1G,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC;gBACpC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;QAC9C,CAAC;QACD,IAAI,IAAI,CAAC,cAAc,EAAE;YAAE,OAAO,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;QAC1E,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YAAE,OAAO,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAChG,CAAC;IACD,0FAA0F;IAC1F,mEAAmE;IACnE,IAAI,CAAC;QACH,IAAI,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,OAAO;YAAE,OAAO,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC5F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;AAC/G,CAAC;AAED,qCAAqC;AACrC,MAAM,UAAU,aAAa,CAAC,KAAqB,EAAE,YAAqB;IACxE,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5F,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,EAAE,KAAK,YAAY;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IACnG,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAqB,EAAE,SAAkB;IAC5E,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,oBAAoB,EAAE,WAAW,CAAC,CAAC;IAC9E,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,eAAe,CAAC,cAAc,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC;IAChH,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,SAAS,EAAE,CAAC;QAC9E,OAAO,MAAM,CAAC,KAAK,EAAE,oBAAoB,EAAE,WAAW,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACtC,CAAC;AAED;;;;;GAKG;AACH,MAAM,MAAM,GAAG,IAAI,OAAO,EAAkB,CAAC;AAE7C;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,OAAuC;IAChF,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAqB,EACrB,OAAgB,EAChB,MAA8B;IAE9B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9D,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,SAAS,EAAE,CAAC;QAC9G,OAAO,MAAM,CAAC,KAAK,EAAE,kBAAkB,EAAE,WAAW,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AACpC,CAAC;AAED,+FAA+F;AAC/F,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAqB,EAAE,IAAa;IACpE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;IAC/D,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACxD,IAAI,CAAC,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;IAC7D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AACvC,CAAC;AAED,+EAA+E;AAE/E;kEACkE;AAClE,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAE1C;;;;GAIG;AACH,MAAM,UAAU,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;AAEnC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;AAEhG,SAAS,SAAS,CAAC,KAAqB,EAAE,QAAgB,EAAE,KAAa;IACvE,OAAO,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,KAAqB,EAAE,QAAgB,EAAE,KAAa;IAC/E,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CACxB,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAC9F,MAAM,CACP,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACxB,IAAI,MAAM,CAAC,MAAM,GAAG,oBAAoB;QAAE,MAAM,IAAI,UAAU,CAAC,yBAAyB,oBAAoB,QAAQ,CAAC,CAAC;IACtH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,KAAqB,EAAE,QAAgB,EAAE,MAAe;IACjF,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,oBAAoB,EAAE,CAAC;QAC9F,OAAO,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IACtE,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IACtD,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC1E,OAAO,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;AAC5C,CAAC;AAWD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,KAAqB,EACrB,KAAqB,EACrB,GAAuB,EACvB,GAAyE;IAEzE,MAAM,KAAK,GAAmB,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC;IAC5D,MAAM,OAAO,GAAgC,EAAE,CAAC;IAChD,MAAM,OAAO,GAAgC,EAAE,CAAC;IAChD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoD,CAAC;IAC9E,KAAK,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/B,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC;YACtC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9B,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAClC,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACpC,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;YAC7C,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/B,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YAC3C,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YAChD,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GACX,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IACxG,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AACvC,CAAC;AAID;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,KAAqB,EAAE,IAAe;IACnE,IAAI,IAAI,KAAK,mBAAmB,EAAE,CAAC;QACjC,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC;YACzC,IAAI,GAAG,CAAC,YAAY,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,iBAAiB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;gBAC7E,OAAO,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACnC,CAAC"}
|