@qodeca/xezar 0.13.0 → 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 +72 -39
- 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 +25 -2
- package/dist/runs/retention.js +63 -2
- 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 +599 -79
- 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-CdS8r8E2.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-BYlnf4qf.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-C2On8SlI.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-6aopqAmV.js → task-changes-CH-E81-_.js} +1 -1
- package/web/dist/assets/{task-commits-u-dwaCyw.js → task-commits-DqfkMFxp.js} +1 -1
- package/web/dist/assets/{task-files-IMOZnIUK.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-fLDKEJoB.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-BY3KTkFw.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-CqGK62Lw.js +0 -1
- package/web/dist/assets/index-3nxVmdn5.css +0 -2
- package/web/dist/assets/index-qX7n11FX.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-Bj-uG_Hz.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,162 @@
|
|
|
1
|
+
import { type AuditEntry, type AuditOrigin, type AuditOutcome, type AuditResource } from '../contract/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The audit trail (#102) — D-06 § 10, requirement N-04. Built for MCP and cockpit operations;
|
|
4
|
+
* writing MCP operations today, and only those (see THE SHARED PATH below).
|
|
5
|
+
*
|
|
6
|
+
* WHAT. One append-only NDJSON file per project, `<project>/.local/xezar/mcp-audit.ndjson`, one
|
|
7
|
+
* `AuditEntry` (packages/contract/src/mcp-audit.ts) per settled operation: action, time, project,
|
|
8
|
+
* resource, outcome and ORIGIN, plus D-06's join fields. It is written, never required: a missing
|
|
9
|
+
* file reads as an empty trail, and deleting it discards history and nothing else. It lives under
|
|
10
|
+
* `.local/`, which `ensureProjectDataIgnored` already blanket-ignores (D-06 § 13.2).
|
|
11
|
+
*
|
|
12
|
+
* THE SHARED PATH, AND THE ONE DOOR THAT USES IT. A door stamps its origin through an
|
|
13
|
+
* `AuditChannel`: the entry point that owns the door asks `trail.channel(origin)` ONCE and hands
|
|
14
|
+
* operations to it. The origin is therefore derived from which door the call came through — an
|
|
15
|
+
* operation object has no origin and no project field, and a stray `origin` or `projectId` key on
|
|
16
|
+
* one is ignored (D-06 § 10.4 rule 1). That is also the marker #106 reads to tell the leader's own
|
|
17
|
+
* echoes from new events.
|
|
18
|
+
*
|
|
19
|
+
* **Exactly one door does this today, and it is the MCP one:** `mcp/index.ts:254` builds
|
|
20
|
+
* `.channel('mcp')`, and it is the only non-test construction of this class in `packages/`
|
|
21
|
+
* (grepped for the type and for `.channel(` at `87d9f0d`, 2026-09-12). The cockpit's HTTP routes,
|
|
22
|
+
* the automation scheduler and headless `xezar run` are the doors the design expects and they
|
|
23
|
+
* record NOTHING — so `ui` is written only by tests (`audit-trail.test.ts`, `echo-guard.test.ts`,
|
|
24
|
+
* `test/helpers/ab-fixture.ts`), and `automation` and `cli` are written nowhere at all. That is decided for 0.14.0, not an oversight (#266, D-06 § 10.6);
|
|
25
|
+
* wiring the other three is #364. `channel()` takes any `AuditOrigin` because those doors are the
|
|
26
|
+
* point, but do not read this comment as a description of four live writers.
|
|
27
|
+
*
|
|
28
|
+
* MUST vs SHOULD (D-06 § 10.2, N-04 "should"):
|
|
29
|
+
* - MUST: the project id comes from the trusted scope, the origin from the door, and no secret,
|
|
30
|
+
* free text, path or foreign identifier is written (§ 10.3, F-15, F-12, N-01).
|
|
31
|
+
* - MUST: the record grants nothing (§ 10.4 rule 3). Nothing here answers "may X do Y", and no
|
|
32
|
+
* method consults the trail before an effect — a caller's own permission check runs first and
|
|
33
|
+
* a refusal is simply recorded as `rejected`. An `origin: 'ui'` entry never widens MCP.
|
|
34
|
+
* - SHOULD: the entry itself. Recording is best-effort by design: a write that fails is warned
|
|
35
|
+
* about once and never fails the operation, which is the opposite of the receipt journal's
|
|
36
|
+
* refuse-before-effect rule (D-06 § 7.5) and deliberately so — idempotency is mandatory, audit
|
|
37
|
+
* is not.
|
|
38
|
+
*
|
|
39
|
+
* NO SECRETS, TWICE. First by construction: the schema has no free-text field, and payloads enter
|
|
40
|
+
* only as a SHA-256 digest (D-06 § 5.4). Second by value: every client-influenced identifier
|
|
41
|
+
* (`resource.id`, the operation id, the version token) is checked against the host's secret env
|
|
42
|
+
* values, the caller's known secrets (the MCP connection token, D-04) and the well-known token
|
|
43
|
+
* shapes, and a field that matches is DROPPED rather than masked. The check deliberately ignores
|
|
44
|
+
* `XEZ_REDACT_SECRETS=0`: that opt-out exists because masking can corrupt a transcript, and
|
|
45
|
+
* dropping an audit field corrupts nothing.
|
|
46
|
+
*
|
|
47
|
+
* RETENTION IS OPEN. N-04 keeps it open, D-06 § 10.5 does not close it, and D-09 B-23 fixes only
|
|
48
|
+
* the mechanism (count-based, never evicting an entry whose run is still kept) with the count
|
|
49
|
+
* UNRESOLVED (U-2). So nothing here evicts, and no period or count is asserted.
|
|
50
|
+
*/
|
|
51
|
+
/** File name under the project's data dir (`.local/xezar/`). D-06 § 10.5 puts it beside the receipts. */
|
|
52
|
+
export declare const AUDIT_TRAIL_FILE = "mcp-audit.ndjson";
|
|
53
|
+
export declare const auditTrailPath: (dataDir: string) => string;
|
|
54
|
+
/** The trusted scope a trail is bound to — a project context's own id and data dir. */
|
|
55
|
+
export interface AuditScope {
|
|
56
|
+
/** The resolved registry id. Never the `default` boot alias, which would split one history in two. */
|
|
57
|
+
projectId: string;
|
|
58
|
+
dataDir: string;
|
|
59
|
+
}
|
|
60
|
+
export interface AuditTrailOptions {
|
|
61
|
+
/** Clock, injectable for tests. */
|
|
62
|
+
now?: () => Date;
|
|
63
|
+
/** Secrets the caller knows about that are not in the env — the MCP connection token (D-04). */
|
|
64
|
+
secretValues?: () => readonly string[];
|
|
65
|
+
/** Where the one-time write/read warning goes. */
|
|
66
|
+
warn?: (message: string) => void;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* What an entry point may say about an operation. Deliberately no `origin` and no `projectId`:
|
|
70
|
+
* both are properties of the door and the scope, never of the operation.
|
|
71
|
+
*/
|
|
72
|
+
export interface AuditedOperation {
|
|
73
|
+
/** Dotted action id, e.g. `runs.create`. */
|
|
74
|
+
action: string;
|
|
75
|
+
/** The resource acted on, when known before the effect. */
|
|
76
|
+
resource?: AuditResource;
|
|
77
|
+
/** The zod-PARSED payload. Only its digest is kept; never the raw params (D-06 § 5.4 step 1). */
|
|
78
|
+
payload?: unknown;
|
|
79
|
+
/** The `expectedVersion` the caller sent (D-06 § 4.2). Kept only when it has the `rev1` shape. */
|
|
80
|
+
expectedVersion?: string;
|
|
81
|
+
/** MCP only — the client's `operationId` (D-06 § 5.2). */
|
|
82
|
+
operationId?: string;
|
|
83
|
+
/**
|
|
84
|
+
* MCP only — the D-02.3 fencing token the mutation arrived under, `<wall-clock ms>-<UUIDv4>`, as
|
|
85
|
+
* the receipt journal records it. Only the wall-clock prefix is kept: the full token passes the
|
|
86
|
+
* owner's equality fence, so it is authority and never enters the trail.
|
|
87
|
+
*/
|
|
88
|
+
ownerGeneration?: string;
|
|
89
|
+
}
|
|
90
|
+
export interface AuditSettlement {
|
|
91
|
+
outcome: AuditOutcome;
|
|
92
|
+
/** Short machine code for a non-`ok` outcome, e.g. `stale_version`. Never an error message. */
|
|
93
|
+
errorCode?: string;
|
|
94
|
+
/** The resource the effect produced (a created run), when it was unknown before. */
|
|
95
|
+
resource?: AuditResource;
|
|
96
|
+
}
|
|
97
|
+
/** Thrown by an effect to refuse BEFORE anything happened: validation, permission, stale version. */
|
|
98
|
+
export declare class AuditRejection extends Error {
|
|
99
|
+
readonly code: string;
|
|
100
|
+
constructor(code: string);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The outcome an HTTP status stands for, for callers that settle through a route (the cockpit's
|
|
104
|
+
* handlers, and the MCP adapter, which dispatches into the same routes). The routes validate as
|
|
105
|
+
* middleware and answer 4xx before any effect, so a 4xx is a refusal; a 5xx may have come after
|
|
106
|
+
* the effect started, so it is `unverified`, never `rejected`.
|
|
107
|
+
*/
|
|
108
|
+
export declare function settlementForStatus(status: number): AuditSettlement;
|
|
109
|
+
export interface AuditReadResult {
|
|
110
|
+
entries: AuditEntry[];
|
|
111
|
+
/** Lines that failed to parse or validate — skipped, never fatal (D-06 § 7.4's line-level rule). */
|
|
112
|
+
quarantined: number;
|
|
113
|
+
}
|
|
114
|
+
export declare class AuditTrail {
|
|
115
|
+
readonly scope: AuditScope;
|
|
116
|
+
private readonly now;
|
|
117
|
+
private readonly callerSecrets;
|
|
118
|
+
private readonly warn;
|
|
119
|
+
private warned;
|
|
120
|
+
constructor(scope: AuditScope, options?: AuditTrailOptions);
|
|
121
|
+
/**
|
|
122
|
+
* The recorder for one door. Call it where the door is, once; the origin is fixed from then on.
|
|
123
|
+
* One production caller: `mcp/index.ts:254`, with `'mcp'`. Every other origin is test-only (#364).
|
|
124
|
+
*/
|
|
125
|
+
channel(origin: AuditOrigin): AuditChannel;
|
|
126
|
+
/**
|
|
127
|
+
* Every entry of THIS project, oldest first. A scoped read (D-06 § 10.4 rule 2): it opens only
|
|
128
|
+
* this project's file and additionally drops any line naming another project, so a reader bound
|
|
129
|
+
* to B learns nothing about A — not its entries, and not how many there are (N-01).
|
|
130
|
+
*/
|
|
131
|
+
read(): AuditReadResult;
|
|
132
|
+
/** @internal — build, check and append one entry. Never throws. */
|
|
133
|
+
write(origin: AuditOrigin, op: AuditedOperation, settlement: AuditSettlement): AuditEntry | null;
|
|
134
|
+
private build;
|
|
135
|
+
private knownSecrets;
|
|
136
|
+
private warnOnce;
|
|
137
|
+
}
|
|
138
|
+
/** One door's recorder. The origin is fixed at construction and no operation can change it. */
|
|
139
|
+
export declare class AuditChannel {
|
|
140
|
+
private readonly trail;
|
|
141
|
+
readonly origin: AuditOrigin;
|
|
142
|
+
constructor(trail: AuditTrail, origin: AuditOrigin);
|
|
143
|
+
/** Record an operation that has already settled. */
|
|
144
|
+
record(op: AuditedOperation, settlement: AuditSettlement): AuditEntry | null;
|
|
145
|
+
/** Record an operation that settled through an HTTP route, by its status. */
|
|
146
|
+
recordStatus(op: AuditedOperation, status: number): AuditEntry | null;
|
|
147
|
+
/**
|
|
148
|
+
* Run `effect` and record how it settled. An `AuditRejection` means "refused before any effect"
|
|
149
|
+
* and records `rejected`; any other throw may have come after the effect began, so it records
|
|
150
|
+
* `unverified`. Either error is rethrown unchanged: auditing never changes the operation's own
|
|
151
|
+
* answer. `resourceOf` names a resource the effect created.
|
|
152
|
+
*/
|
|
153
|
+
run<T>(op: AuditedOperation, effect: () => T | Promise<T>, resourceOf?: (value: T) => AuditResource | undefined): Promise<T>;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* D-06 § 5.4's canonical form: object keys sorted by code unit at every level, arrays in order,
|
|
157
|
+
* `undefined` keys dropped, and a byte blob replaced by `{ sha256, bytes }` so the digest covers
|
|
158
|
+
* bytes, never a path. A non-finite number is refused (zod refuses it upstream).
|
|
159
|
+
*/
|
|
160
|
+
export declare function canonicalJson(value: unknown): string;
|
|
161
|
+
/** SHA-256, lowercase hex, of `canonicalJson(payload)`. */
|
|
162
|
+
export declare function payloadDigest(payload: unknown): string;
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { appendFileSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { auditEntrySchema, } from '../contract/index.js';
|
|
5
|
+
import { collectSecretValues, redactSecrets } from '../core/secret-redaction.js';
|
|
6
|
+
import { PROJECT_ID_RE } from '../workspace/config.js';
|
|
7
|
+
/**
|
|
8
|
+
* The audit trail (#102) — D-06 § 10, requirement N-04. Built for MCP and cockpit operations;
|
|
9
|
+
* writing MCP operations today, and only those (see THE SHARED PATH below).
|
|
10
|
+
*
|
|
11
|
+
* WHAT. One append-only NDJSON file per project, `<project>/.local/xezar/mcp-audit.ndjson`, one
|
|
12
|
+
* `AuditEntry` (packages/contract/src/mcp-audit.ts) per settled operation: action, time, project,
|
|
13
|
+
* resource, outcome and ORIGIN, plus D-06's join fields. It is written, never required: a missing
|
|
14
|
+
* file reads as an empty trail, and deleting it discards history and nothing else. It lives under
|
|
15
|
+
* `.local/`, which `ensureProjectDataIgnored` already blanket-ignores (D-06 § 13.2).
|
|
16
|
+
*
|
|
17
|
+
* THE SHARED PATH, AND THE ONE DOOR THAT USES IT. A door stamps its origin through an
|
|
18
|
+
* `AuditChannel`: the entry point that owns the door asks `trail.channel(origin)` ONCE and hands
|
|
19
|
+
* operations to it. The origin is therefore derived from which door the call came through — an
|
|
20
|
+
* operation object has no origin and no project field, and a stray `origin` or `projectId` key on
|
|
21
|
+
* one is ignored (D-06 § 10.4 rule 1). That is also the marker #106 reads to tell the leader's own
|
|
22
|
+
* echoes from new events.
|
|
23
|
+
*
|
|
24
|
+
* **Exactly one door does this today, and it is the MCP one:** `mcp/index.ts:254` builds
|
|
25
|
+
* `.channel('mcp')`, and it is the only non-test construction of this class in `packages/`
|
|
26
|
+
* (grepped for the type and for `.channel(` at `87d9f0d`, 2026-09-12). The cockpit's HTTP routes,
|
|
27
|
+
* the automation scheduler and headless `xezar run` are the doors the design expects and they
|
|
28
|
+
* record NOTHING — so `ui` is written only by tests (`audit-trail.test.ts`, `echo-guard.test.ts`,
|
|
29
|
+
* `test/helpers/ab-fixture.ts`), and `automation` and `cli` are written nowhere at all. That is decided for 0.14.0, not an oversight (#266, D-06 § 10.6);
|
|
30
|
+
* wiring the other three is #364. `channel()` takes any `AuditOrigin` because those doors are the
|
|
31
|
+
* point, but do not read this comment as a description of four live writers.
|
|
32
|
+
*
|
|
33
|
+
* MUST vs SHOULD (D-06 § 10.2, N-04 "should"):
|
|
34
|
+
* - MUST: the project id comes from the trusted scope, the origin from the door, and no secret,
|
|
35
|
+
* free text, path or foreign identifier is written (§ 10.3, F-15, F-12, N-01).
|
|
36
|
+
* - MUST: the record grants nothing (§ 10.4 rule 3). Nothing here answers "may X do Y", and no
|
|
37
|
+
* method consults the trail before an effect — a caller's own permission check runs first and
|
|
38
|
+
* a refusal is simply recorded as `rejected`. An `origin: 'ui'` entry never widens MCP.
|
|
39
|
+
* - SHOULD: the entry itself. Recording is best-effort by design: a write that fails is warned
|
|
40
|
+
* about once and never fails the operation, which is the opposite of the receipt journal's
|
|
41
|
+
* refuse-before-effect rule (D-06 § 7.5) and deliberately so — idempotency is mandatory, audit
|
|
42
|
+
* is not.
|
|
43
|
+
*
|
|
44
|
+
* NO SECRETS, TWICE. First by construction: the schema has no free-text field, and payloads enter
|
|
45
|
+
* only as a SHA-256 digest (D-06 § 5.4). Second by value: every client-influenced identifier
|
|
46
|
+
* (`resource.id`, the operation id, the version token) is checked against the host's secret env
|
|
47
|
+
* values, the caller's known secrets (the MCP connection token, D-04) and the well-known token
|
|
48
|
+
* shapes, and a field that matches is DROPPED rather than masked. The check deliberately ignores
|
|
49
|
+
* `XEZ_REDACT_SECRETS=0`: that opt-out exists because masking can corrupt a transcript, and
|
|
50
|
+
* dropping an audit field corrupts nothing.
|
|
51
|
+
*
|
|
52
|
+
* RETENTION IS OPEN. N-04 keeps it open, D-06 § 10.5 does not close it, and D-09 B-23 fixes only
|
|
53
|
+
* the mechanism (count-based, never evicting an entry whose run is still kept) with the count
|
|
54
|
+
* UNRESOLVED (U-2). So nothing here evicts, and no period or count is asserted.
|
|
55
|
+
*/
|
|
56
|
+
/** File name under the project's data dir (`.local/xezar/`). D-06 § 10.5 puts it beside the receipts. */
|
|
57
|
+
export const AUDIT_TRAIL_FILE = 'mcp-audit.ndjson';
|
|
58
|
+
export const auditTrailPath = (dataDir) => join(dataDir, AUDIT_TRAIL_FILE);
|
|
59
|
+
/** The same floor `secret-redaction.ts` applies to env values: below it a value is a common word. */
|
|
60
|
+
const MIN_KNOWN_SECRET_LEN = 12;
|
|
61
|
+
/** Thrown by an effect to refuse BEFORE anything happened: validation, permission, stale version. */
|
|
62
|
+
export class AuditRejection extends Error {
|
|
63
|
+
code;
|
|
64
|
+
constructor(code) {
|
|
65
|
+
super(code);
|
|
66
|
+
this.code = code;
|
|
67
|
+
this.name = 'AuditRejection';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The outcome an HTTP status stands for, for callers that settle through a route (the cockpit's
|
|
72
|
+
* handlers, and the MCP adapter, which dispatches into the same routes). The routes validate as
|
|
73
|
+
* middleware and answer 4xx before any effect, so a 4xx is a refusal; a 5xx may have come after
|
|
74
|
+
* the effect started, so it is `unverified`, never `rejected`.
|
|
75
|
+
*/
|
|
76
|
+
export function settlementForStatus(status) {
|
|
77
|
+
if (status >= 200 && status < 300)
|
|
78
|
+
return { outcome: 'ok' };
|
|
79
|
+
if (status >= 400 && status < 500)
|
|
80
|
+
return { outcome: 'rejected', errorCode: `http_${status}` };
|
|
81
|
+
return { outcome: 'unverified', errorCode: `http_${status}` };
|
|
82
|
+
}
|
|
83
|
+
export class AuditTrail {
|
|
84
|
+
scope;
|
|
85
|
+
now;
|
|
86
|
+
callerSecrets;
|
|
87
|
+
warn;
|
|
88
|
+
warned = false;
|
|
89
|
+
constructor(scope, options = {}) {
|
|
90
|
+
if (scope.projectId === 'default' || !PROJECT_ID_RE.test(scope.projectId)) {
|
|
91
|
+
throw new Error(`audit trail needs a resolved project id, got ${JSON.stringify(scope.projectId)}`);
|
|
92
|
+
}
|
|
93
|
+
this.scope = { projectId: scope.projectId, dataDir: scope.dataDir };
|
|
94
|
+
this.now = options.now ?? (() => new Date());
|
|
95
|
+
this.callerSecrets = options.secretValues ?? (() => []);
|
|
96
|
+
this.warn = options.warn ?? ((message) => console.warn(message));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The recorder for one door. Call it where the door is, once; the origin is fixed from then on.
|
|
100
|
+
* One production caller: `mcp/index.ts:254`, with `'mcp'`. Every other origin is test-only (#364).
|
|
101
|
+
*/
|
|
102
|
+
channel(origin) {
|
|
103
|
+
return new AuditChannel(this, origin);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Every entry of THIS project, oldest first. A scoped read (D-06 § 10.4 rule 2): it opens only
|
|
107
|
+
* this project's file and additionally drops any line naming another project, so a reader bound
|
|
108
|
+
* to B learns nothing about A — not its entries, and not how many there are (N-01).
|
|
109
|
+
*/
|
|
110
|
+
read() {
|
|
111
|
+
let raw;
|
|
112
|
+
try {
|
|
113
|
+
raw = readFileSync(auditTrailPath(this.scope.dataDir), 'utf8');
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (err.code !== 'ENOENT')
|
|
117
|
+
this.warnOnce('read', err);
|
|
118
|
+
return { entries: [], quarantined: 0 };
|
|
119
|
+
}
|
|
120
|
+
const entries = [];
|
|
121
|
+
let quarantined = 0;
|
|
122
|
+
for (const line of raw.split('\n')) {
|
|
123
|
+
if (line.trim() === '')
|
|
124
|
+
continue;
|
|
125
|
+
let json;
|
|
126
|
+
try {
|
|
127
|
+
json = JSON.parse(line);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
quarantined += 1;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const parsed = auditEntrySchema.safeParse(json);
|
|
134
|
+
if (!parsed.success) {
|
|
135
|
+
quarantined += 1;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (parsed.data.projectId !== this.scope.projectId)
|
|
139
|
+
continue;
|
|
140
|
+
entries.push(parsed.data);
|
|
141
|
+
}
|
|
142
|
+
return { entries, quarantined };
|
|
143
|
+
}
|
|
144
|
+
/** @internal — build, check and append one entry. Never throws. */
|
|
145
|
+
write(origin, op, settlement) {
|
|
146
|
+
try {
|
|
147
|
+
const entry = this.build(origin, op, settlement);
|
|
148
|
+
if (!entry)
|
|
149
|
+
return null;
|
|
150
|
+
appendFileSync(auditTrailPath(this.scope.dataDir), `${JSON.stringify(entry)}\n`, { mode: 0o600 });
|
|
151
|
+
return entry;
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
this.warnOnce('write', err);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
build(origin, op, settlement) {
|
|
159
|
+
const secrets = this.knownSecrets();
|
|
160
|
+
const clean = (value) => value !== undefined && redactSecrets(value, secrets) === value ? value : undefined;
|
|
161
|
+
const field = (key, value) => {
|
|
162
|
+
const parsed = auditEntrySchema.shape[key].safeParse(value);
|
|
163
|
+
return parsed.success ? parsed.data : undefined;
|
|
164
|
+
};
|
|
165
|
+
const resourceOf = (resource) => {
|
|
166
|
+
if (!resource || clean(resource.id) === undefined || clean(resource.kind) === undefined)
|
|
167
|
+
return undefined;
|
|
168
|
+
return field('resource', { kind: resource.kind, id: resource.id });
|
|
169
|
+
};
|
|
170
|
+
const candidate = {
|
|
171
|
+
v: 1,
|
|
172
|
+
ts: this.now().toISOString(),
|
|
173
|
+
projectId: this.scope.projectId,
|
|
174
|
+
action: op.action,
|
|
175
|
+
outcome: settlement.outcome,
|
|
176
|
+
origin,
|
|
177
|
+
};
|
|
178
|
+
const optional = {
|
|
179
|
+
resource: resourceOf(settlement.resource) ?? resourceOf(op.resource),
|
|
180
|
+
ownerGeneration: origin === 'mcp' ? field('ownerGeneration', fencingTokenMs(op.ownerGeneration)) : undefined,
|
|
181
|
+
operationKey: origin === 'mcp' && op.operationId !== undefined
|
|
182
|
+
? field('operationKey', clean(`${this.scope.projectId}/${op.operationId}`))
|
|
183
|
+
: undefined,
|
|
184
|
+
versionToken: field('versionToken', clean(op.expectedVersion)),
|
|
185
|
+
payloadDigest: op.payload === undefined ? undefined : field('payloadDigest', safeDigest(op.payload)),
|
|
186
|
+
errorCode: settlement.outcome === 'ok' ? undefined : field('errorCode', clean(settlement.errorCode)),
|
|
187
|
+
};
|
|
188
|
+
// Spread only what is present: an `undefined` key would be typed as present and dropped by
|
|
189
|
+
// JSON.stringify anyway, and the read path must see exactly what was written.
|
|
190
|
+
for (const [key, value] of Object.entries(optional))
|
|
191
|
+
if (value !== undefined)
|
|
192
|
+
candidate[key] = value;
|
|
193
|
+
const parsed = auditEntrySchema.safeParse(candidate);
|
|
194
|
+
if (!parsed.success || clean(parsed.data.action) === undefined) {
|
|
195
|
+
this.warnOnce('write', new Error(`not an auditable operation: ${parsed.error?.issues[0]?.message ?? 'secret-shaped action'}`));
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
return parsed.data;
|
|
199
|
+
}
|
|
200
|
+
knownSecrets() {
|
|
201
|
+
const values = new Set(collectSecretValues());
|
|
202
|
+
for (const value of this.callerSecrets())
|
|
203
|
+
if (value.length >= MIN_KNOWN_SECRET_LEN)
|
|
204
|
+
values.add(value);
|
|
205
|
+
return [...values].sort((a, b) => b.length - a.length);
|
|
206
|
+
}
|
|
207
|
+
warnOnce(what, err) {
|
|
208
|
+
if (this.warned)
|
|
209
|
+
return;
|
|
210
|
+
this.warned = true;
|
|
211
|
+
// The code only — an error message can carry a path, and a path is not for logs either.
|
|
212
|
+
const code = err?.code ?? err?.name ?? 'error';
|
|
213
|
+
this.warn(`xezar: audit trail ${what} failed (${code}); operations continue unaudited`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/** One door's recorder. The origin is fixed at construction and no operation can change it. */
|
|
217
|
+
export class AuditChannel {
|
|
218
|
+
trail;
|
|
219
|
+
origin;
|
|
220
|
+
constructor(trail, origin) {
|
|
221
|
+
this.trail = trail;
|
|
222
|
+
this.origin = origin;
|
|
223
|
+
}
|
|
224
|
+
/** Record an operation that has already settled. */
|
|
225
|
+
record(op, settlement) {
|
|
226
|
+
return this.trail.write(this.origin, op, settlement);
|
|
227
|
+
}
|
|
228
|
+
/** Record an operation that settled through an HTTP route, by its status. */
|
|
229
|
+
recordStatus(op, status) {
|
|
230
|
+
return this.record(op, settlementForStatus(status));
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Run `effect` and record how it settled. An `AuditRejection` means "refused before any effect"
|
|
234
|
+
* and records `rejected`; any other throw may have come after the effect began, so it records
|
|
235
|
+
* `unverified`. Either error is rethrown unchanged: auditing never changes the operation's own
|
|
236
|
+
* answer. `resourceOf` names a resource the effect created.
|
|
237
|
+
*/
|
|
238
|
+
async run(op, effect, resourceOf) {
|
|
239
|
+
let value;
|
|
240
|
+
try {
|
|
241
|
+
value = await effect();
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
this.record(op, err instanceof AuditRejection
|
|
245
|
+
? { outcome: 'rejected', errorCode: err.code }
|
|
246
|
+
: { outcome: 'unverified', errorCode: 'effect_failed' });
|
|
247
|
+
throw err;
|
|
248
|
+
}
|
|
249
|
+
this.record(op, { outcome: 'ok', resource: resourceOf?.(value) });
|
|
250
|
+
return value;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* D-06 § 5.4's canonical form: object keys sorted by code unit at every level, arrays in order,
|
|
255
|
+
* `undefined` keys dropped, and a byte blob replaced by `{ sha256, bytes }` so the digest covers
|
|
256
|
+
* bytes, never a path. A non-finite number is refused (zod refuses it upstream).
|
|
257
|
+
*/
|
|
258
|
+
export function canonicalJson(value) {
|
|
259
|
+
return JSON.stringify(canonicalize(value));
|
|
260
|
+
}
|
|
261
|
+
/** SHA-256, lowercase hex, of `canonicalJson(payload)`. */
|
|
262
|
+
export function payloadDigest(payload) {
|
|
263
|
+
return createHash('sha256').update(canonicalJson(payload)).digest('hex');
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* The wall-clock prefix of a D-02.3 fencing token, `<ms>-<UUIDv4>` — the number D-06 § 10.2 keeps.
|
|
267
|
+
* D-02 names human-readable audit as the prefix's one consumer; the random half is what the fence
|
|
268
|
+
* compares, so it is dropped here. Anything not shaped like a token yields nothing.
|
|
269
|
+
*/
|
|
270
|
+
function fencingTokenMs(token) {
|
|
271
|
+
const match = token === undefined ? null : /^(\d{1,15})-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.exec(token);
|
|
272
|
+
return match ? Number(match[1]) : undefined;
|
|
273
|
+
}
|
|
274
|
+
function safeDigest(payload) {
|
|
275
|
+
try {
|
|
276
|
+
return payloadDigest(payload);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function canonicalize(value) {
|
|
283
|
+
if (value instanceof Uint8Array) {
|
|
284
|
+
return { bytes: value.byteLength, sha256: createHash('sha256').update(value).digest('hex') };
|
|
285
|
+
}
|
|
286
|
+
if (typeof value === 'number' && !Number.isFinite(value))
|
|
287
|
+
throw new Error('non-finite number in payload');
|
|
288
|
+
if (typeof value === 'bigint' || typeof value === 'function' || typeof value === 'symbol') {
|
|
289
|
+
throw new Error(`${typeof value} in payload`);
|
|
290
|
+
}
|
|
291
|
+
if (value === null || typeof value !== 'object')
|
|
292
|
+
return value;
|
|
293
|
+
const withJson = value;
|
|
294
|
+
if (typeof withJson.toJSON === 'function')
|
|
295
|
+
return canonicalize(withJson.toJSON());
|
|
296
|
+
if (Array.isArray(value))
|
|
297
|
+
return value.map((item) => (item === undefined ? null : canonicalize(item)));
|
|
298
|
+
const out = {};
|
|
299
|
+
for (const key of Object.keys(value).sort()) {
|
|
300
|
+
const item = value[key];
|
|
301
|
+
if (item !== undefined)
|
|
302
|
+
out[key] = canonicalize(item);
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=audit-trail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-trail.js","sourceRoot":"","sources":["../../src/mcp/audit-trail.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type McpTool } from './tool.ts';
|
|
2
|
+
/**
|
|
3
|
+
* `xez mcp`: the stdio MCP endpoint a coding agent spawns (D-01 § 1.1, § 1.7). A
|
|
4
|
+
* transport adapter with two legs — MCP on stdin/stdout facing the client, the
|
|
5
|
+
* project's Unix socket facing the running service — and nothing else: no store,
|
|
6
|
+
* no queue, no server, no port.
|
|
7
|
+
*
|
|
8
|
+
* ## One connection, one session (#302, D-02)
|
|
9
|
+
*
|
|
10
|
+
* The bridge keeps ONE connection to the service for its whole life and opens its MCP session on
|
|
11
|
+
* it when the client sends `initialize` — that is where D-02.6 puts acquisition. The service then
|
|
12
|
+
* sees the session open and, when this process exits or its client goes away, sees it close: the
|
|
13
|
+
* owner went away, observably, in milliseconds. That is what makes exclusive ownership enforceable;
|
|
14
|
+
* the one-connection-per-call bridge this replaces gave the service no session to observe.
|
|
15
|
+
*
|
|
16
|
+
* What one-connection-per-call was load-bearing FOR, and how each guarantee is kept:
|
|
17
|
+
*
|
|
18
|
+
* - **N-07 / D-01 § 5 — xezar not running.** Every call re-resolved the target and connected, so a
|
|
19
|
+
* cockpit started later was picked up on the next call with no client restart and no reconnect
|
|
20
|
+
* timer. Kept: `initialize` still succeeds with no service, and every call made without a live
|
|
21
|
+
* session tries to open one — the next call is still the next attempt, with no timer.
|
|
22
|
+
* - **Cockpit restart.** A fresh connection per call simply reached the new service. Now the old
|
|
23
|
+
* session ends with the old service (D-02 § 5): the FIRST call that reaches the new service is
|
|
24
|
+
* answered session-expired, so a write made under the old session is fenced rather than silently
|
|
25
|
+
* replayed, and the bridge has already opened a new session for the next call — no model turn and
|
|
26
|
+
* no human step.
|
|
27
|
+
* - **Failure containment.** A hung or garbled answer spoiled one call's connection and nothing
|
|
28
|
+
* else. Now answers are matched by request id on the shared connection: a call that times out
|
|
29
|
+
* is answered as a timeout and its late answer dropped, and a garbled frame fails the calls in
|
|
30
|
+
* flight — neither closes the session, so neither costs the project.
|
|
31
|
+
* - **A client that dies mid-call.** Its connections were destroyed and the service carried on
|
|
32
|
+
* with the operation. Unchanged: the service still runs every call it accepted to completion; the
|
|
33
|
+
* close now also frees the project, and still cancels no task (N-05).
|
|
34
|
+
* - **Two clients at once.** Both were served — the defect. Now the second is refused.
|
|
35
|
+
*
|
|
36
|
+
* Refusals are D-02 § 4's JSON-RPC errors, passed through as the service built them:
|
|
37
|
+
* `initialize` while another session owns the project answers project-occupied (`-32080`); a call
|
|
38
|
+
* from a session that does not own the project — refused, lost with a restart, or fenced — answers
|
|
39
|
+
* session-expired (`-32081`), and the next call is made on a new session. Nothing here lets one
|
|
40
|
+
* session end another (F-18).
|
|
41
|
+
*/
|
|
42
|
+
export type ServiceTarget = {
|
|
43
|
+
readonly kind: 'socket';
|
|
44
|
+
readonly path: string;
|
|
45
|
+
readonly project: {
|
|
46
|
+
readonly id: string;
|
|
47
|
+
readonly name: string;
|
|
48
|
+
};
|
|
49
|
+
} | {
|
|
50
|
+
readonly kind: 'unavailable';
|
|
51
|
+
readonly status: 'not-registered' | 'unsupported';
|
|
52
|
+
readonly message: string;
|
|
53
|
+
};
|
|
54
|
+
export interface BridgeOptions {
|
|
55
|
+
readonly input: NodeJS.ReadableStream;
|
|
56
|
+
readonly output: NodeJS.WritableStream;
|
|
57
|
+
readonly version: string;
|
|
58
|
+
/** The registry (`./tools/index.ts`). Listed here, executed in the service. */
|
|
59
|
+
readonly tools: readonly McpTool[];
|
|
60
|
+
/** Re-resolved whenever a session is opened, so registering or starting the project later just works. */
|
|
61
|
+
resolveTarget(): Promise<ServiceTarget>;
|
|
62
|
+
readonly requestTimeoutMs?: number;
|
|
63
|
+
}
|
|
64
|
+
/** Built into the bridge rather than the registry: it is how a client learns the service is down. */
|
|
65
|
+
export declare const HEALTH_TOOL: {
|
|
66
|
+
readonly name: 'health';
|
|
67
|
+
readonly title: 'xezar health';
|
|
68
|
+
readonly description: 'Report whether the xezar cockpit is running for the project this session was started in, and which project that is.';
|
|
69
|
+
readonly inputSchema: {
|
|
70
|
+
readonly type: 'object';
|
|
71
|
+
readonly properties: {};
|
|
72
|
+
readonly additionalProperties: false;
|
|
73
|
+
};
|
|
74
|
+
readonly annotations: {
|
|
75
|
+
readonly readOnlyHint: true;
|
|
76
|
+
readonly idempotentHint: true;
|
|
77
|
+
readonly openWorldHint: false;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
/** Serve MCP until `input` ends. Resolves then; the session closes and in-flight answers are dropped. */
|
|
81
|
+
export declare function runBridge(opts: BridgeOptions): Promise<void>;
|