cool-workflow 0.1.98 → 0.2.1
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +18 -7
- package/apps/architecture-review/app.json +2 -2
- package/apps/architecture-review/workflow.js +12 -12
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/cli/dispatch.js +236 -0
- package/dist/cli/entry.js +120 -0
- package/dist/cli/io.js +21 -4
- package/dist/cli/parseargv.js +157 -0
- package/dist/cli.js +6 -33
- package/dist/core/capability-table.js +3518 -0
- package/dist/core/format/help.js +314 -0
- package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
- package/dist/core/hash.js +137 -0
- package/dist/core/multi-agent/candidate-scoring.js +219 -0
- package/dist/core/multi-agent/collaboration.js +481 -0
- package/dist/core/multi-agent/coordinator.js +515 -0
- package/dist/core/multi-agent/eval-replay.js +306 -0
- package/dist/core/multi-agent/runtime.js +929 -0
- package/dist/core/multi-agent/topology.js +298 -0
- package/dist/core/multi-agent/trust-policy.js +197 -0
- package/dist/core/pipeline/commit-gate.js +320 -0
- package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
- package/dist/core/pipeline/dispatch.js +103 -0
- package/dist/core/pipeline/drive-decide.js +227 -0
- package/dist/core/pipeline/error-feedback.js +161 -0
- package/dist/core/pipeline/loop-expansion.js +124 -0
- package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
- package/dist/core/pipeline/runner.js +230 -0
- package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
- package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
- package/dist/core/state/node-projection.js +95 -0
- package/dist/core/state/node-snapshot.js +230 -0
- package/dist/core/state/run-paths.js +93 -0
- package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
- package/dist/core/state/schema.js +50 -0
- package/dist/core/state/state-explosion/digest.js +243 -0
- package/dist/core/state/state-explosion/graph.js +527 -0
- package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
- package/dist/core/state/state-explosion/report.js +187 -0
- package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
- package/dist/{state-node.js → core/state/state-node.js} +90 -113
- package/dist/core/state/types.js +19 -0
- package/dist/{validation.js → core/state/validation.js} +64 -131
- package/dist/core/trust/evidence-grounding.js +134 -0
- package/dist/core/trust/ledger.js +199 -0
- package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
- package/dist/core/trust/telemetry-ledger.js +127 -0
- package/dist/core/types.js +20 -0
- package/dist/core/version.js +16 -0
- package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
- package/dist/mcp/dispatch.js +104 -0
- package/dist/mcp/server.js +144 -0
- package/dist/mcp-server.js +6 -84
- package/dist/{agent-config.js → shell/agent-config.js} +118 -71
- package/dist/shell/app-run-cli.js +88 -0
- package/dist/shell/audit-cli.js +259 -0
- package/dist/shell/audit-provenance.js +83 -0
- package/dist/shell/candidate-scoring-io.js +459 -0
- package/dist/shell/collaboration-io.js +264 -0
- package/dist/shell/commit-summary.js +104 -0
- package/dist/shell/commit.js +290 -0
- package/dist/shell/coordinator-io.js +476 -0
- package/dist/shell/demo-cli.js +19 -0
- package/dist/shell/dispatch.js +162 -0
- package/dist/shell/doctor.js +292 -0
- package/dist/shell/drive.js +885 -0
- package/dist/shell/error-feedback-io.js +305 -0
- package/dist/shell/eval-io.js +473 -0
- package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
- package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
- package/dist/shell/exec-backend-cli.js +88 -0
- package/dist/shell/execution-backend/agent.js +507 -0
- package/dist/shell/execution-backend/ci.js +15 -0
- package/dist/shell/execution-backend/container.js +69 -0
- package/dist/shell/execution-backend/envelopes.js +55 -0
- package/dist/shell/execution-backend/local.js +113 -0
- package/dist/shell/execution-backend/probes.js +175 -0
- package/dist/shell/execution-backend/registry.js +402 -0
- package/dist/shell/execution-backend/remote.js +128 -0
- package/dist/shell/execution-backend/types.js +11 -0
- package/dist/shell/feedback-cli.js +81 -0
- package/dist/shell/feedback-operations.js +48 -0
- package/dist/shell/fs-atomic.js +276 -0
- package/dist/shell/harness.js +98 -0
- package/dist/shell/ledger-cli.js +212 -0
- package/dist/shell/ledger-io.js +169 -0
- package/dist/shell/man-cli.js +89 -0
- package/dist/shell/metrics-cli.js +100 -0
- package/dist/shell/multi-agent-cli.js +1002 -0
- package/dist/shell/multi-agent-host.js +563 -0
- package/dist/shell/multi-agent-io.js +387 -0
- package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
- package/dist/shell/node-store.js +124 -0
- package/dist/{observability/format.js → shell/observability-format.js} +7 -1
- package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
- package/dist/{observability.js → shell/observability.js} +159 -332
- package/dist/{onramp.js → shell/onramp.js} +11 -0
- package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
- package/dist/shell/operator-ux.js +431 -0
- package/dist/shell/orchestrator.js +231 -0
- package/dist/shell/pipeline-cli.js +667 -0
- package/dist/shell/pipeline.js +217 -0
- package/dist/shell/reclamation-io.js +1366 -0
- package/dist/shell/registry-cli.js +344 -0
- package/dist/{remote-source.js → shell/remote-source.js} +2 -2
- package/dist/shell/report-cli.js +101 -0
- package/dist/shell/report-view-cli.js +117 -0
- package/dist/{orchestrator → shell}/report.js +289 -282
- package/dist/shell/reporter.js +62 -0
- package/dist/shell/run-export-cli.js +106 -0
- package/dist/shell/run-export.js +680 -0
- package/dist/shell/run-registry-io.js +1014 -0
- package/dist/shell/run-store.js +164 -0
- package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
- package/dist/{scheduler.js → shell/scheduler-io.js} +257 -48
- package/dist/shell/scheduling-io.js +321 -0
- package/dist/shell/state-cli.js +181 -0
- package/dist/shell/state-explosion-cli.js +197 -0
- package/dist/shell/telemetry-cli.js +85 -0
- package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
- package/dist/shell/telemetry-ledger-io.js +132 -0
- package/dist/{term.js → shell/term.js} +36 -46
- package/dist/shell/topology-io.js +361 -0
- package/dist/shell/trust-audit.js +472 -0
- package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
- package/dist/shell/verifier.js +48 -0
- package/dist/shell/workbench-host.js +258 -0
- package/dist/shell/workbench-text.js +18 -0
- package/dist/shell/workbench.js +170 -0
- package/dist/shell/worker-cli.js +124 -0
- package/dist/shell/worker-isolation.js +852 -0
- package/dist/shell/workflow-app-loader.js +650 -0
- package/docs/agent-delegation-drive.7.md +4 -0
- package/docs/cli-mcp-parity.7.md +282 -219
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/durable-state-and-locking.7.md +4 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
- package/docs/multi-agent-eval-replay-harness.7.md +4 -0
- package/docs/multi-agent-operator-ux.7.md +4 -0
- package/docs/node-snapshot-diff-replay.7.md +4 -0
- package/docs/observability-cost-accounting.7.md +6 -1
- package/docs/project-index.md +137 -71
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +4 -0
- package/docs/release-tooling.7.md +4 -0
- package/docs/remote-source-review.7.md +4 -4
- package/docs/report-verifiable-bundle.7.md +1 -1
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +25 -0
- package/docs/security-trust-hardening.7.md +3 -1
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/web-desktop-workbench.7.md +15 -1
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +9 -13
- package/package.json +1 -1
- package/scripts/agents/agent-adapter-core.js +22 -2
- package/scripts/agents/claude-p-agent.js +8 -3
- package/scripts/agents/cw-attest-wrap.js +2 -2
- package/scripts/agents/gemini-agent.js +5 -1
- package/scripts/agents/opencode-agent.js +5 -1
- package/scripts/bump-version.js +4 -3
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-architecture-review.js +2 -3
- package/scripts/dogfood-release.js +3 -3
- package/scripts/gen-parity-doc.js +15 -2
- package/scripts/golden-path.js +4 -4
- package/scripts/onramp-check.js +1 -1
- package/scripts/parity-check.js +38 -21
- package/scripts/release-flow.js +2 -2
- package/scripts/sync-project-index.js +51 -27
- package/scripts/validate-run-state-schema.js +2 -2
- package/scripts/version-sync-check.js +30 -30
- package/dist/candidate-scoring.js +0 -729
- package/dist/capability-core.js +0 -1189
- package/dist/capability-registry.js +0 -885
- package/dist/cli/command-surface.js +0 -494
- package/dist/cli/format.js +0 -56
- package/dist/cli/handlers/audit.js +0 -82
- package/dist/cli/handlers/blackboard.js +0 -81
- package/dist/cli/handlers/candidate.js +0 -40
- package/dist/cli/handlers/clones.js +0 -34
- package/dist/cli/handlers/collaboration.js +0 -61
- package/dist/cli/handlers/eval.js +0 -40
- package/dist/cli/handlers/ledger.js +0 -169
- package/dist/cli/handlers/maintenance.js +0 -107
- package/dist/cli/handlers/multi-agent.js +0 -165
- package/dist/cli/handlers/node.js +0 -41
- package/dist/cli/handlers/operational.js +0 -155
- package/dist/cli/handlers/operator.js +0 -146
- package/dist/cli/handlers/registry.js +0 -68
- package/dist/cli/handlers/run.js +0 -153
- package/dist/cli/handlers/scheduling.js +0 -132
- package/dist/cli/handlers/workbench.js +0 -41
- package/dist/cli/handlers/worker.js +0 -45
- package/dist/cli/run-summary.js +0 -45
- package/dist/clones.js +0 -162
- package/dist/collaboration.js +0 -726
- package/dist/commit.js +0 -592
- package/dist/compare.js +0 -18
- package/dist/coordinator/classify.js +0 -45
- package/dist/coordinator/paths.js +0 -42
- package/dist/coordinator/util.js +0 -126
- package/dist/coordinator.js +0 -990
- package/dist/daemon.js +0 -44
- package/dist/dispatch.js +0 -250
- package/dist/doctor.js +0 -212
- package/dist/drive.js +0 -864
- package/dist/error-feedback.js +0 -419
- package/dist/evidence-grounding.js +0 -184
- package/dist/execution-backend/agent.js +0 -354
- package/dist/execution-backend/probes.js +0 -112
- package/dist/execution-backend/util.js +0 -47
- package/dist/execution-backend.js +0 -961
- package/dist/gates.js +0 -48
- package/dist/harness.js +0 -61
- package/dist/ledger.js +0 -313
- package/dist/loop-expansion.js +0 -60
- package/dist/mcp/tool-call.js +0 -470
- package/dist/mcp/tool-definitions.js +0 -1066
- package/dist/mcp-surface.js +0 -30
- package/dist/multi-agent/graph.js +0 -84
- package/dist/multi-agent/helpers.js +0 -141
- package/dist/multi-agent/ids.js +0 -20
- package/dist/multi-agent/paths.js +0 -22
- package/dist/multi-agent-eval/normalize.js +0 -51
- package/dist/multi-agent-eval.js +0 -678
- package/dist/multi-agent-host.js +0 -777
- package/dist/multi-agent.js +0 -984
- package/dist/node-projection.js +0 -59
- package/dist/node-snapshot.js +0 -260
- package/dist/operator-ux.js +0 -631
- package/dist/orchestrator/app-operations.js +0 -211
- package/dist/orchestrator/audit-operations.js +0 -182
- package/dist/orchestrator/candidate-operations.js +0 -117
- package/dist/orchestrator/cli-options.js +0 -294
- package/dist/orchestrator/collaboration-operations.js +0 -86
- package/dist/orchestrator/feedback-operations.js +0 -81
- package/dist/orchestrator/host-operations.js +0 -78
- package/dist/orchestrator/lifecycle-operations.js +0 -650
- package/dist/orchestrator/migration-operations.js +0 -44
- package/dist/orchestrator/multi-agent-operations.js +0 -362
- package/dist/orchestrator/topology-operations.js +0 -84
- package/dist/orchestrator.js +0 -925
- package/dist/pipeline-runner.js +0 -285
- package/dist/reclamation/hash.js +0 -72
- package/dist/reclamation.js +0 -812
- package/dist/reporter.js +0 -67
- package/dist/run-export.js +0 -815
- package/dist/run-registry/derive.js +0 -175
- package/dist/run-registry/format.js +0 -124
- package/dist/run-registry/gc.js +0 -251
- package/dist/run-registry/policy.js +0 -16
- package/dist/run-registry/queue.js +0 -115
- package/dist/run-registry.js +0 -850
- package/dist/run-state-schema.js +0 -68
- package/dist/scheduling.js +0 -184
- package/dist/state-explosion.js +0 -1014
- package/dist/state.js +0 -367
- package/dist/telemetry-ledger.js +0 -196
- package/dist/topology.js +0 -565
- package/dist/triggers.js +0 -184
- package/dist/trust-audit.js +0 -644
- package/dist/types/blackboard.js +0 -2
- package/dist/types/candidate.js +0 -2
- package/dist/types/collaboration.js +0 -2
- package/dist/types/core.js +0 -2
- package/dist/types/drive.js +0 -10
- package/dist/types/error-feedback.js +0 -2
- package/dist/types/evidence-reasoning.js +0 -2
- package/dist/types/execution-backend.js +0 -2
- package/dist/types/multi-agent.js +0 -2
- package/dist/types/observability.js +0 -2
- package/dist/types/pipeline.js +0 -2
- package/dist/types/reclamation.js +0 -8
- package/dist/types/report-bundle.js +0 -6
- package/dist/types/result.js +0 -2
- package/dist/types/run-registry.js +0 -2
- package/dist/types/run.js +0 -2
- package/dist/types/sandbox.js +0 -2
- package/dist/types/schedule.js +0 -2
- package/dist/types/state-node.js +0 -2
- package/dist/types/topology.js +0 -2
- package/dist/types/trust.js +0 -2
- package/dist/types/workbench.js +0 -2
- package/dist/types/worker.js +0 -2
- package/dist/types/workflow-app.js +0 -2
- package/dist/types.js +0 -44
- package/dist/util/fingerprint.js +0 -19
- package/dist/util/fingerprint.test.js +0 -27
- package/dist/verifier.js +0 -78
- package/dist/version.js +0 -8
- package/dist/workbench-host.js +0 -192
- package/dist/workbench.js +0 -192
- package/dist/worker-accept/acceptance.js +0 -114
- package/dist/worker-accept/blackboard-fanout.js +0 -80
- package/dist/worker-accept/blackboard-linkage.js +0 -19
- package/dist/worker-accept/context.js +0 -2
- package/dist/worker-accept/telemetry-ledger.js +0 -126
- package/dist/worker-accept/validation.js +0 -77
- package/dist/worker-accept/verifier-completion.js +0 -73
- package/dist/worker-isolation/helpers.js +0 -51
- package/dist/worker-isolation/paths.js +0 -46
- package/dist/worker-isolation.js +0 -656
- package/dist/workflow-api.js +0 -131
- /package/dist/{types → core/types}/boundary.js +0 -0
|
@@ -1,122 +1,56 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
// shell/trust-policy-io.ts — the impure wrapper around core/multi-agent/
|
|
3
|
+
// trust-policy.ts's pure policy evaluation: authorizeMultiAgentAction
|
|
4
|
+
// (records the permission + policy.violation audit events),
|
|
5
|
+
// recordBlackboardWriteAudit, recordMessageProvenanceAudit,
|
|
6
|
+
// recordJudgeRationaleAudit, summarizeMultiAgentTrust,
|
|
7
|
+
// hasAcceptedJudgeRationale.
|
|
8
|
+
//
|
|
9
|
+
// MILESTONE 9. Byte-exact port of the audit-recording half of the old
|
|
10
|
+
// build's src/multi-agent-trust.ts.
|
|
11
|
+
//
|
|
12
|
+
// Evidence: SPEC/multi-agent.md section D ("Trust policies"), "Trust
|
|
13
|
+
// denial reasons"; plugins/cool-workflow/src/multi-agent-trust.ts:129-415
|
|
14
|
+
// (byte-exact source).
|
|
2
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.policyForRole =
|
|
4
|
-
exports.policyForGroup = policyForGroup;
|
|
5
|
-
exports.policyForMembership = policyForMembership;
|
|
6
|
-
exports.recordRolePolicyAudit = recordRolePolicyAudit;
|
|
16
|
+
exports.policyForRole = exports.policyForMembership = exports.policyForGroup = void 0;
|
|
7
17
|
exports.authorizeMultiAgentAction = authorizeMultiAgentAction;
|
|
8
18
|
exports.assertMultiAgentActionAllowed = assertMultiAgentActionAllowed;
|
|
9
19
|
exports.recordBlackboardWriteAudit = recordBlackboardWriteAudit;
|
|
20
|
+
exports.hashText = hashText;
|
|
10
21
|
exports.recordMessageProvenanceAudit = recordMessageProvenanceAudit;
|
|
11
22
|
exports.recordJudgeRationaleAudit = recordJudgeRationaleAudit;
|
|
12
23
|
exports.summarizeMultiAgentTrust = summarizeMultiAgentTrust;
|
|
13
24
|
exports.hasAcceptedJudgeRationale = hasAcceptedJudgeRationale;
|
|
14
|
-
exports.
|
|
15
|
-
|
|
16
|
-
const
|
|
25
|
+
exports.recordRolePolicyAudit = recordRolePolicyAudit;
|
|
26
|
+
const runtime_1 = require("../core/multi-agent/runtime");
|
|
27
|
+
const trust_policy_1 = require("../core/multi-agent/trust-policy");
|
|
17
28
|
const trust_audit_1 = require("./trust-audit");
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
allowedBlackboardTopicIds: unique(role.topicIds || ["*"]),
|
|
29
|
-
allowedWriteOperations: unique([
|
|
30
|
-
"message",
|
|
31
|
-
"context",
|
|
32
|
-
"artifact",
|
|
33
|
-
...(isChair ? ["snapshot", "coordinator-decision"] : [])
|
|
34
|
-
]),
|
|
35
|
-
allowedCandidateOperations: isChair ? ["score", "select"] : ["score"],
|
|
36
|
-
allowedJudgeOperations: unique([
|
|
37
|
-
...(isJudge ? ["verdict", "rationale"] : []),
|
|
38
|
-
...(isChair ? ["rationale", "panel-decision"] : [])
|
|
39
|
-
]),
|
|
40
|
-
sandboxProfileHints: unique(role.sandboxProfileHints || []),
|
|
41
|
-
requiredEvidenceRefs: unique(role.requiredEvidence || []),
|
|
42
|
-
requiredEvidenceFor: {
|
|
43
|
-
"judge.rationale": ["judge rationale evidence"],
|
|
44
|
-
"judge.verdict": ["judge verdict evidence"],
|
|
45
|
-
"judge.panel-decision": ["judge messages", "score evidence", "coordinator decision"],
|
|
46
|
-
"candidate.select": ["score evidence", "judge rationale"]
|
|
47
|
-
},
|
|
48
|
-
deniedOperations: [],
|
|
49
|
-
metadata: { title: role.title, topologyRoleId: role.metadata?.topologyRoleId }
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
function policyForGroup(group) {
|
|
53
|
-
return {
|
|
54
|
-
schemaVersion: 1,
|
|
55
|
-
id: `${group.id}-policy`,
|
|
56
|
-
policyRef: `multiAgent.groups.${group.id}.policy`,
|
|
57
|
-
subjectKind: "group",
|
|
58
|
-
subjectId: group.id,
|
|
59
|
-
allowedBlackboardTopicIds: unique(group.topicIds || ["*"]),
|
|
60
|
-
allowedWriteOperations: ["message", "context", "artifact", "snapshot", "coordinator-decision"],
|
|
61
|
-
allowedCandidateOperations: ["register", "score", "select"],
|
|
62
|
-
allowedJudgeOperations: ["verdict", "rationale", "panel-decision"],
|
|
63
|
-
sandboxProfileHints: [],
|
|
64
|
-
requiredEvidenceRefs: [],
|
|
65
|
-
deniedOperations: [],
|
|
66
|
-
metadata: { phase: group.phase }
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
function policyForMembership(membership, role) {
|
|
70
|
-
const source = role?.policy || (role ? policyForRole(role) : undefined);
|
|
71
|
-
return {
|
|
72
|
-
...(source || {
|
|
73
|
-
schemaVersion: 1,
|
|
74
|
-
id: `${membership.id}-policy`,
|
|
75
|
-
policyRef: `multiAgent.memberships.${membership.id}.policy`,
|
|
76
|
-
subjectKind: "membership",
|
|
77
|
-
subjectId: membership.id,
|
|
78
|
-
allowedBlackboardTopicIds: unique(membership.topicIds || ["*"]),
|
|
79
|
-
allowedWriteOperations: ["message", "context", "artifact"],
|
|
80
|
-
allowedCandidateOperations: [],
|
|
81
|
-
allowedJudgeOperations: [],
|
|
82
|
-
sandboxProfileHints: [],
|
|
83
|
-
requiredEvidenceRefs: [],
|
|
84
|
-
deniedOperations: []
|
|
85
|
-
}),
|
|
86
|
-
id: `${membership.id}-policy`,
|
|
87
|
-
policyRef: `multiAgent.memberships.${membership.id}.policy`,
|
|
88
|
-
subjectKind: "membership",
|
|
89
|
-
subjectId: membership.id
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
function recordRolePolicyAudit(run, role) {
|
|
93
|
-
return (0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
94
|
-
kind: "multi-agent.role-policy",
|
|
95
|
-
decision: "recorded",
|
|
96
|
-
source: "runtime-derived",
|
|
97
|
-
multiAgentRunId: role.multiAgentRunId,
|
|
98
|
-
agentRoleId: role.id,
|
|
99
|
-
blackboardId: role.blackboardId,
|
|
100
|
-
policyRef: role.policy?.policyRef,
|
|
101
|
-
metadata: role.policy
|
|
102
|
-
});
|
|
29
|
+
const hash_1 = require("../core/hash");
|
|
30
|
+
function policyRunId(run, roleId, groupId, membershipId) {
|
|
31
|
+
const membership = membershipId ? (0, runtime_1.getAgentMembership)(run, membershipId) : undefined;
|
|
32
|
+
if (membership)
|
|
33
|
+
return membership.multiAgentRunId;
|
|
34
|
+
const role = roleId ? (0, runtime_1.getAgentRole)(run, roleId) : undefined;
|
|
35
|
+
if (role)
|
|
36
|
+
return role.multiAgentRunId;
|
|
37
|
+
const group = groupId ? (0, runtime_1.getAgentGroup)(run, groupId) : undefined;
|
|
38
|
+
return group?.multiAgentRunId;
|
|
103
39
|
}
|
|
104
40
|
function authorizeMultiAgentAction(run, input) {
|
|
105
41
|
const roleId = input.agentRoleId || (input.actor?.kind === "role" ? input.actor.id : undefined);
|
|
106
42
|
const membershipId = input.agentMembershipId || (input.actor?.kind === "membership" ? input.actor.id : undefined);
|
|
107
43
|
const groupId = input.agentGroupId || (input.actor?.kind === "group" ? input.actor.id : undefined);
|
|
108
|
-
const policy =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
...(input.metadata || {})
|
|
116
|
-
};
|
|
44
|
+
const policy = (0, trust_policy_1.resolvePolicy)({ roleId, membershipId, groupId }, {
|
|
45
|
+
membership: (id) => (0, runtime_1.getAgentMembership)(run, id),
|
|
46
|
+
role: (id) => (0, runtime_1.getAgentRole)(run, id),
|
|
47
|
+
group: (id) => (0, runtime_1.getAgentGroup)(run, id),
|
|
48
|
+
});
|
|
49
|
+
const core = (0, trust_policy_1.evaluateMultiAgentAction)(policy, input.operation, input.blackboardTopicId, input.evidenceRefs || []);
|
|
50
|
+
const metadata = { operation: input.operation, reason: core.reason, policyRef: policy?.policyRef, ...(input.metadata || {}) };
|
|
117
51
|
const event = (0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
118
52
|
kind: "multi-agent.permission",
|
|
119
|
-
decision: allowed ? "allowed" : "denied",
|
|
53
|
+
decision: core.allowed ? "allowed" : "denied",
|
|
120
54
|
source: "cw-validated",
|
|
121
55
|
actor: input.actor?.id,
|
|
122
56
|
multiAgentRunId: input.multiAgentRunId || policyRunId(run, roleId, groupId, membershipId),
|
|
@@ -140,9 +74,9 @@ function authorizeMultiAgentAction(run, input) {
|
|
|
140
74
|
evidence: input.evidence,
|
|
141
75
|
evidenceRefs: input.evidenceRefs,
|
|
142
76
|
policyRef: policy?.policyRef,
|
|
143
|
-
metadata
|
|
77
|
+
metadata,
|
|
144
78
|
});
|
|
145
|
-
if (!allowed) {
|
|
79
|
+
if (!core.allowed) {
|
|
146
80
|
(0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
147
81
|
kind: "policy.violation",
|
|
148
82
|
decision: "denied",
|
|
@@ -159,18 +93,10 @@ function authorizeMultiAgentAction(run, input) {
|
|
|
159
93
|
evidenceRefs: input.evidenceRefs,
|
|
160
94
|
parentEventIds: [event.id],
|
|
161
95
|
policyRef: policy?.policyRef,
|
|
162
|
-
metadata
|
|
96
|
+
metadata,
|
|
163
97
|
});
|
|
164
98
|
}
|
|
165
|
-
return {
|
|
166
|
-
allowed,
|
|
167
|
-
decision: allowed ? "allowed" : "denied",
|
|
168
|
-
reason: reason || "allowed by explicit multi-agent policy",
|
|
169
|
-
policyRef: policy?.policyRef,
|
|
170
|
-
policy,
|
|
171
|
-
missingEvidenceRefs: missingEvidence(policy, input.operation, input.evidenceRefs || []),
|
|
172
|
-
event
|
|
173
|
-
};
|
|
99
|
+
return { allowed: core.allowed, decision: core.decision, reason: core.reason, policyRef: core.policyRef, policy: core.policy, missingEvidenceRefs: core.missingEvidenceRefs, event };
|
|
174
100
|
}
|
|
175
101
|
function assertMultiAgentActionAllowed(run, input) {
|
|
176
102
|
const decision = authorizeMultiAgentAction(run, input);
|
|
@@ -178,6 +104,17 @@ function assertMultiAgentActionAllowed(run, input) {
|
|
|
178
104
|
throw new Error(decision.reason);
|
|
179
105
|
return decision;
|
|
180
106
|
}
|
|
107
|
+
function sourceForActor(actor) {
|
|
108
|
+
if (!actor)
|
|
109
|
+
return "operator-recorded";
|
|
110
|
+
if (actor.kind === "worker")
|
|
111
|
+
return "host-attested";
|
|
112
|
+
if (actor.kind === "operator")
|
|
113
|
+
return "operator-recorded";
|
|
114
|
+
if (actor.kind === "runtime" || actor.kind === "coordinator" || actor.kind === "verifier")
|
|
115
|
+
return "runtime-derived";
|
|
116
|
+
return "cw-validated";
|
|
117
|
+
}
|
|
181
118
|
function recordBlackboardWriteAudit(run, input) {
|
|
182
119
|
return (0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
183
120
|
kind: "blackboard.write",
|
|
@@ -200,9 +137,12 @@ function recordBlackboardWriteAudit(run, input) {
|
|
|
200
137
|
evidenceRefs: input.evidenceRefs,
|
|
201
138
|
parentEventIds: input.parentEventIds,
|
|
202
139
|
policyRef: input.policyRef,
|
|
203
|
-
metadata: { operation: input.operation, status: input.status, ...(input.metadata || {}) }
|
|
140
|
+
metadata: { operation: input.operation, status: input.status, ...(input.metadata || {}) },
|
|
204
141
|
});
|
|
205
142
|
}
|
|
143
|
+
function hashText(value) {
|
|
144
|
+
return (0, hash_1.sha256)(value);
|
|
145
|
+
}
|
|
206
146
|
function recordMessageProvenanceAudit(run, input) {
|
|
207
147
|
return (0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
208
148
|
kind: "blackboard.message-provenance",
|
|
@@ -226,8 +166,8 @@ function recordMessageProvenanceAudit(run, input) {
|
|
|
226
166
|
summary: input.body.trim().slice(0, 120),
|
|
227
167
|
parentMessageIds: input.parentMessageIds || [],
|
|
228
168
|
topicScope: input.topicId,
|
|
229
|
-
locator: `${input.blackboardId}/messages/${input.messageId}
|
|
230
|
-
}
|
|
169
|
+
locator: `${input.blackboardId}/messages/${input.messageId}`,
|
|
170
|
+
},
|
|
231
171
|
});
|
|
232
172
|
}
|
|
233
173
|
function recordJudgeRationaleAudit(run, input) {
|
|
@@ -250,12 +190,13 @@ function recordJudgeRationaleAudit(run, input) {
|
|
|
250
190
|
evidenceRefs: input.evidenceRefs,
|
|
251
191
|
parentEventIds: input.parentEventIds,
|
|
252
192
|
policyRef: input.policyRef,
|
|
253
|
-
metadata: { rationale: input.rationale?.slice(0, 240) }
|
|
193
|
+
metadata: { rationale: input.rationale?.slice(0, 240) },
|
|
254
194
|
});
|
|
255
195
|
}
|
|
256
196
|
function summarizeMultiAgentTrust(run) {
|
|
257
197
|
const events = (0, trust_audit_1.listTrustAuditEvents)(run);
|
|
258
|
-
const
|
|
198
|
+
const roles = (run.multiAgent?.roles || []);
|
|
199
|
+
const rolePolicies = roles.map((role) => role.policy || (0, trust_policy_1.policyForRole)(role));
|
|
259
200
|
const byKind = (kind) => events.filter((event) => event.kind === kind);
|
|
260
201
|
const policyViolations = byKind("policy.violation");
|
|
261
202
|
return {
|
|
@@ -268,9 +209,7 @@ function summarizeMultiAgentTrust(run) {
|
|
|
268
209
|
judgeRationales: byKind("judge.rationale"),
|
|
269
210
|
panelDecisions: byKind("judge.panel-decision"),
|
|
270
211
|
policyViolations,
|
|
271
|
-
nextAction: policyViolations.length
|
|
272
|
-
? `node scripts/cw.js audit policy ${run.id}`
|
|
273
|
-
: `node scripts/cw.js audit multi-agent ${run.id} --json`
|
|
212
|
+
nextAction: policyViolations.length ? `node scripts/cw.js audit policy ${run.id}` : `node scripts/cw.js audit multi-agent ${run.id} --json`,
|
|
274
213
|
};
|
|
275
214
|
}
|
|
276
215
|
function hasAcceptedJudgeRationale(run, input = {}) {
|
|
@@ -280,87 +219,10 @@ function hasAcceptedJudgeRationale(run, input = {}) {
|
|
|
280
219
|
(!input.candidateId || event.candidateId === input.candidateId) &&
|
|
281
220
|
(!input.scoreId || !event.scoreId || event.scoreId === input.scoreId));
|
|
282
221
|
}
|
|
283
|
-
function
|
|
284
|
-
|
|
285
|
-
return "operator-recorded";
|
|
286
|
-
if (actor.kind === "worker")
|
|
287
|
-
return "host-attested";
|
|
288
|
-
if (actor.kind === "operator")
|
|
289
|
-
return "operator-recorded";
|
|
290
|
-
if (actor.kind === "runtime" || actor.kind === "coordinator" || actor.kind === "verifier")
|
|
291
|
-
return "runtime-derived";
|
|
292
|
-
return "cw-validated";
|
|
293
|
-
}
|
|
294
|
-
// Delegates to the shared execution-backend sha256 (F10 dedup). Byte-identical:
|
|
295
|
-
// both emit `sha256:<hex>` and Node's Hash.update(string) defaults to utf8, the
|
|
296
|
-
// same encoding the shared helper passes explicitly.
|
|
297
|
-
function hashText(value) {
|
|
298
|
-
return (0, execution_backend_1.sha256)(value);
|
|
299
|
-
}
|
|
300
|
-
function resolvePolicy(run, input) {
|
|
301
|
-
const membership = input.membershipId ? run.multiAgent?.memberships.find((entry) => entry.id === input.membershipId) : undefined;
|
|
302
|
-
if (membership?.policy)
|
|
303
|
-
return membership.policy;
|
|
304
|
-
const roleId = input.roleId || membership?.roleId;
|
|
305
|
-
const role = roleId ? run.multiAgent?.roles.find((entry) => entry.id === roleId) : undefined;
|
|
306
|
-
if (role?.policy)
|
|
307
|
-
return role.policy;
|
|
308
|
-
if (role)
|
|
309
|
-
return policyForRole(role);
|
|
310
|
-
const group = input.groupId ? run.multiAgent?.groups.find((entry) => entry.id === input.groupId) : undefined;
|
|
311
|
-
if (group?.policy)
|
|
312
|
-
return group.policy;
|
|
313
|
-
if (group)
|
|
314
|
-
return policyForGroup(group);
|
|
315
|
-
return undefined;
|
|
316
|
-
}
|
|
317
|
-
function evaluatePolicy(policy, operation, topicId, evidenceRefs) {
|
|
318
|
-
if (!policy)
|
|
319
|
-
return "missing role authority or policy";
|
|
320
|
-
const denied = policy.deniedOperations.find((entry) => entry.operation === operation);
|
|
321
|
-
if (denied)
|
|
322
|
-
return denied.reason;
|
|
323
|
-
if (topicId && policy.allowedBlackboardTopicIds.length && !policy.allowedBlackboardTopicIds.includes("*") && !policy.allowedBlackboardTopicIds.includes(topicId)) {
|
|
324
|
-
return `topic ${topicId} is outside policy ${policy.policyRef}`;
|
|
325
|
-
}
|
|
326
|
-
if (operation.startsWith("candidate.")) {
|
|
327
|
-
const op = operation.slice("candidate.".length);
|
|
328
|
-
if (!policy.allowedCandidateOperations.includes(op))
|
|
329
|
-
return `candidate operation ${op} is outside policy ${policy.policyRef}`;
|
|
330
|
-
}
|
|
331
|
-
else if (operation.startsWith("judge.")) {
|
|
332
|
-
const op = operation.slice("judge.".length);
|
|
333
|
-
if (!policy.allowedJudgeOperations.includes(op))
|
|
334
|
-
return `judge operation ${op} is outside policy ${policy.policyRef}`;
|
|
335
|
-
}
|
|
336
|
-
else if (!policy.allowedWriteOperations.includes(operation)) {
|
|
337
|
-
return `blackboard write operation ${operation} is outside policy ${policy.policyRef}`;
|
|
338
|
-
}
|
|
339
|
-
const missing = missingEvidence(policy, operation, evidenceRefs);
|
|
340
|
-
if (missing.length)
|
|
341
|
-
return `operation ${operation} requires evidence refs: ${missing.join(", ")}`;
|
|
342
|
-
return undefined;
|
|
343
|
-
}
|
|
344
|
-
function missingEvidence(policy, operation, evidenceRefs) {
|
|
345
|
-
if (!policy)
|
|
346
|
-
return [];
|
|
347
|
-
const required = unique([...(policy.requiredEvidenceFor?.[operation] || [])]);
|
|
348
|
-
if (!required.length)
|
|
349
|
-
return [];
|
|
350
|
-
if (evidenceRefs.length)
|
|
351
|
-
return [];
|
|
352
|
-
return required;
|
|
353
|
-
}
|
|
354
|
-
function policyRunId(run, roleId, groupId, membershipId) {
|
|
355
|
-
const membership = membershipId ? run.multiAgent?.memberships.find((entry) => entry.id === membershipId) : undefined;
|
|
356
|
-
if (membership)
|
|
357
|
-
return membership.multiAgentRunId;
|
|
358
|
-
const role = roleId ? run.multiAgent?.roles.find((entry) => entry.id === roleId) : undefined;
|
|
359
|
-
if (role)
|
|
360
|
-
return role.multiAgentRunId;
|
|
361
|
-
const group = groupId ? run.multiAgent?.groups.find((entry) => entry.id === groupId) : undefined;
|
|
362
|
-
return group?.multiAgentRunId;
|
|
363
|
-
}
|
|
364
|
-
function unique(values) {
|
|
365
|
-
return Array.from(new Set(values.filter(Boolean)));
|
|
222
|
+
function recordRolePolicyAudit(run, role) {
|
|
223
|
+
return (0, trust_audit_1.recordTrustAuditEvent)(run, { kind: "multi-agent.role-policy", decision: "recorded", source: "runtime-derived", multiAgentRunId: role.multiAgentRunId, agentRoleId: role.id, blackboardId: role.blackboardId, policyRef: role.policy?.policyRef, metadata: role.policy });
|
|
366
224
|
}
|
|
225
|
+
var trust_policy_2 = require("../core/multi-agent/trust-policy");
|
|
226
|
+
Object.defineProperty(exports, "policyForGroup", { enumerable: true, get: function () { return trust_policy_2.policyForGroup; } });
|
|
227
|
+
Object.defineProperty(exports, "policyForMembership", { enumerable: true, get: function () { return trust_policy_2.policyForMembership; } });
|
|
228
|
+
Object.defineProperty(exports, "policyForRole", { enumerable: true, get: function () { return trust_policy_2.policyForRole; } });
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// shell/verifier.ts — taskRequiresEvidence, the small task-level gate the
|
|
3
|
+
// worker-accept path and the commit gate both consult.
|
|
4
|
+
//
|
|
5
|
+
// MILESTONE 6+7 (combined). Byte-exact port of the old build's
|
|
6
|
+
// src/verifier.ts's `taskRequiresEvidence`. Pure logic; kept under
|
|
7
|
+
// shell/ only because its caller (worker-isolation.ts) is shell-side —
|
|
8
|
+
// the function itself takes no fs/clock/random input.
|
|
9
|
+
//
|
|
10
|
+
// Evidence: SPEC/pipeline-run.md "Commit gate" (verifierNodeRequiresEvidence
|
|
11
|
+
// reuses this exact predicate).
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.taskRequiresEvidence = taskRequiresEvidence;
|
|
14
|
+
exports.validateResultEnvelope = validateResultEnvelope;
|
|
15
|
+
const evidence_grounding_1 = require("../core/trust/evidence-grounding");
|
|
16
|
+
const schema_validate_1 = require("../core/state/schema-validate");
|
|
17
|
+
function taskRequiresEvidence(task) {
|
|
18
|
+
return Boolean(task.requiresEvidence || /^verify[:/]/i.test(task.id) || /^verdict[:/]/i.test(task.id) || /^synthesis[:/]/i.test(task.id));
|
|
19
|
+
}
|
|
20
|
+
function validateFinding(task, finding) {
|
|
21
|
+
if (!finding.id)
|
|
22
|
+
throw new Error(`Task ${task.id} has a finding without id`);
|
|
23
|
+
if (finding.classification && !["real", "conditional", "non-issue", "unknown"].includes(finding.classification)) {
|
|
24
|
+
throw new Error(`Task ${task.id} finding ${finding.id} has invalid classification`);
|
|
25
|
+
}
|
|
26
|
+
if (["P0", "P1", "P2"].includes(finding.severity || "") && !(0, evidence_grounding_1.hasGroundedEvidence)(finding.evidence)) {
|
|
27
|
+
throw new Error(`Task ${task.id} finding ${finding.id} severity ${finding.severity} requires grounded evidence (a path-like locator, URL, or namespace:value token)`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Validate an accepted result envelope against the task's declared contract:
|
|
31
|
+
* required grounded evidence, per-finding shape, and (opt-in) the task's
|
|
32
|
+
* declared output schema. Throws on violation (fail-closed — the drive parks
|
|
33
|
+
* the hop). Byte-behavior port of the old build's src/verifier.ts. */
|
|
34
|
+
function validateResultEnvelope(task, result) {
|
|
35
|
+
if (taskRequiresEvidence(task) && !(0, evidence_grounding_1.hasGroundedEvidence)(result.evidence)) {
|
|
36
|
+
throw new Error(`Task ${task.id} requires grounded cw:result evidence (a path-like locator, URL, or namespace:value token — not free text)`);
|
|
37
|
+
}
|
|
38
|
+
for (const finding of result.findings || []) {
|
|
39
|
+
validateFinding(task, finding);
|
|
40
|
+
}
|
|
41
|
+
const schema = task.schema;
|
|
42
|
+
if (schema) {
|
|
43
|
+
const violations = (0, schema_validate_1.validateAgainstSchema)(result, schema);
|
|
44
|
+
if (violations.length) {
|
|
45
|
+
throw new Error(`Task ${task.id} result violates declared schema: ${violations.slice(0, 5).join("; ")}${violations.length > 5 ? ` (+${violations.length - 5} more)` : ""}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// shell/workbench-host.ts — the localhost-only, read-only Workbench HTTP
|
|
3
|
+
// server: `cw workbench serve`.
|
|
4
|
+
//
|
|
5
|
+
// MILESTONE 11 (reporting/observability). Byte-exact port of the old
|
|
6
|
+
// build's src/workbench-host.ts's HTTP behavior: loopback bind only,
|
|
7
|
+
// GET-only, non-localhost Host header refused, optional bearer-token
|
|
8
|
+
// auth (timing-safe compare), pretty (2-space) JSON responses with
|
|
9
|
+
// `Cache-Control: no-store` + `X-Content-Type-Options: nosniff`, a fixed
|
|
10
|
+
// fallback index page when the UI is not installed, and `--once`/`--json`
|
|
11
|
+
// printing only the descriptor and starting nothing.
|
|
12
|
+
//
|
|
13
|
+
// CRITICAL: the MCP path (mcp/dispatch.ts) must NEVER start a persistent
|
|
14
|
+
// listener — it always forces `once: true` so `cw_workbench_serve` only
|
|
15
|
+
// ever returns the descriptor. Only the CLI's default (no `--once`, no
|
|
16
|
+
// `--json`) actually binds and blocks.
|
|
17
|
+
//
|
|
18
|
+
// Evidence: SPEC/reporting-ux.md "Workbench" (HTTP behavior, allowed
|
|
19
|
+
// hostnames, serve descriptor stdout line), invariant 11.
|
|
20
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
23
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
24
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
25
|
+
}
|
|
26
|
+
Object.defineProperty(o, k2, desc);
|
|
27
|
+
}) : (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
o[k2] = m[k];
|
|
30
|
+
}));
|
|
31
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
32
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
33
|
+
}) : function(o, v) {
|
|
34
|
+
o["default"] = v;
|
|
35
|
+
});
|
|
36
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
37
|
+
var ownKeys = function(o) {
|
|
38
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
39
|
+
var ar = [];
|
|
40
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
41
|
+
return ar;
|
|
42
|
+
};
|
|
43
|
+
return ownKeys(o);
|
|
44
|
+
};
|
|
45
|
+
return function (mod) {
|
|
46
|
+
if (mod && mod.__esModule) return mod;
|
|
47
|
+
var result = {};
|
|
48
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
49
|
+
__setModuleDefault(result, mod);
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
})();
|
|
53
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
exports.WorkbenchHost = void 0;
|
|
55
|
+
const crypto = __importStar(require("node:crypto"));
|
|
56
|
+
const fs = __importStar(require("node:fs"));
|
|
57
|
+
const http = __importStar(require("node:http"));
|
|
58
|
+
const path = __importStar(require("node:path"));
|
|
59
|
+
const node_url_1 = require("node:url");
|
|
60
|
+
const workbench_1 = require("./workbench");
|
|
61
|
+
const ALLOWED_HOSTNAMES = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
62
|
+
function timingSafeEqual(a, b) {
|
|
63
|
+
const bufA = Buffer.from(a, "utf8");
|
|
64
|
+
const bufB = Buffer.from(b, "utf8");
|
|
65
|
+
if (bufA.length !== bufB.length) {
|
|
66
|
+
// Compare against a same-length dummy so the check still takes
|
|
67
|
+
// constant time relative to the (wrong) input length, matching the
|
|
68
|
+
// old build's mismatched-length handling.
|
|
69
|
+
crypto.timingSafeEqual(bufA, bufA);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return crypto.timingSafeEqual(bufA, bufB);
|
|
73
|
+
}
|
|
74
|
+
function sendJson(res, status, body) {
|
|
75
|
+
const text = `${JSON.stringify(body, null, 2)}\n`;
|
|
76
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
|
|
77
|
+
res.end(text);
|
|
78
|
+
}
|
|
79
|
+
function contentTypeFor(file) {
|
|
80
|
+
const ext = path.extname(file).toLowerCase();
|
|
81
|
+
const map = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".json": "application/json; charset=utf-8", ".svg": "image/svg+xml" };
|
|
82
|
+
return map[ext] || "application/octet-stream";
|
|
83
|
+
}
|
|
84
|
+
function fallbackIndexHtml(descriptor) {
|
|
85
|
+
const routeLines = descriptor.routes.map((r) => `<li><code>${r.path}</code> — ${r.description}</li>`).join("\n ");
|
|
86
|
+
return `<!doctype html>
|
|
87
|
+
<html>
|
|
88
|
+
<head><meta charset="utf-8"><title>Cool Workflow Workbench</title></head>
|
|
89
|
+
<body>
|
|
90
|
+
<h1>Cool Workflow Workbench</h1>
|
|
91
|
+
<p>The Workbench UI is not installed. Read-only JSON routes:</p>
|
|
92
|
+
<ul>
|
|
93
|
+
${routeLines}
|
|
94
|
+
</ul>
|
|
95
|
+
</body>
|
|
96
|
+
</html>
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
class WorkbenchHost {
|
|
100
|
+
args;
|
|
101
|
+
token;
|
|
102
|
+
server;
|
|
103
|
+
constructor(args = {}) {
|
|
104
|
+
this.args = args;
|
|
105
|
+
this.token = process.env.CW_WORKBENCH_TOKEN && process.env.CW_WORKBENCH_TOKEN.trim() ? process.env.CW_WORKBENCH_TOKEN.trim() : undefined;
|
|
106
|
+
}
|
|
107
|
+
/** The serve descriptor; `boundPort` is filled in only after `listen()`
|
|
108
|
+
* actually binds (an ephemeral `--port 0` resolves to its real port). */
|
|
109
|
+
descriptor(once, boundPort) {
|
|
110
|
+
return (0, workbench_1.buildWorkbenchServeDescriptor)({ ...this.args, once }, boundPort);
|
|
111
|
+
}
|
|
112
|
+
checkAuth(req, url) {
|
|
113
|
+
if (!this.token)
|
|
114
|
+
return true;
|
|
115
|
+
const header = req.headers.authorization;
|
|
116
|
+
const bearer = header && header.startsWith("Bearer ") ? header.slice("Bearer ".length) : undefined;
|
|
117
|
+
const queryToken = url.searchParams.get("token") || undefined;
|
|
118
|
+
const provided = bearer || queryToken;
|
|
119
|
+
if (!provided)
|
|
120
|
+
return false;
|
|
121
|
+
return timingSafeEqual(provided, this.token);
|
|
122
|
+
}
|
|
123
|
+
handleRequest(req, res) {
|
|
124
|
+
try {
|
|
125
|
+
const hostHeader = req.headers.host;
|
|
126
|
+
if (hostHeader) {
|
|
127
|
+
const hostname = hostHeader.split(":")[0];
|
|
128
|
+
if (!ALLOWED_HOSTNAMES.has(hostname)) {
|
|
129
|
+
sendJson(res, 403, { error: "forbidden: non-localhost Host header" });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (req.method !== "GET") {
|
|
134
|
+
res.setHeader("Allow", "GET");
|
|
135
|
+
sendJson(res, 405, { error: "read-only: only GET is permitted" });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
let url;
|
|
139
|
+
try {
|
|
140
|
+
url = new node_url_1.URL(req.url || "/", "http://127.0.0.1");
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
sendJson(res, 400, { error: "bad request: invalid URL" });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (!this.checkAuth(req, url)) {
|
|
147
|
+
sendJson(res, 401, { error: "unauthorized: token mismatch" });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// Decode the route path ONCE (old build's decodeRoutePath). Node's
|
|
151
|
+
// `new URL()` does NOT throw on a malformed percent-escape like
|
|
152
|
+
// `/%E0%A4%A`; only decodeURIComponent does — so this is where a
|
|
153
|
+
// malformed URL is caught as a 400 client error (not a 404, not a
|
|
154
|
+
// 500 crash). The decoded route is also what the traversal guard
|
|
155
|
+
// inspects, so an encoded `..%2f..%2f` unescapes to `../../` BEFORE
|
|
156
|
+
// path.resolve, letting it escape uiRoot and fail 403.
|
|
157
|
+
let route;
|
|
158
|
+
try {
|
|
159
|
+
route = decodeURIComponent(url.pathname);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
sendJson(res, 400, { error: "bad request: malformed URL path" });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (route === "/api/serve") {
|
|
166
|
+
sendJson(res, 200, this.descriptor(false));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (route === "/api/index") {
|
|
170
|
+
const scope = this.args.scope === "home" ? "home" : "repo";
|
|
171
|
+
sendJson(res, 200, (0, workbench_1.buildWorkbenchIndex)({ ...this.args, scope, ...Object.fromEntries(url.searchParams.entries()) }));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const runMatch = route.match(/^\/api\/run\/([^/]+)$/);
|
|
175
|
+
if (runMatch) {
|
|
176
|
+
const runId = runMatch[1];
|
|
177
|
+
sendJson(res, 200, (0, workbench_1.buildWorkbenchRunView)(runId, this.args));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (route === "/" || route.startsWith("/ui/")) {
|
|
181
|
+
this.serveUiAsset(route, res);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
sendJson(res, 404, { error: `no such read-only view: ${route}` });
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
serveUiAsset(pathname, res) {
|
|
191
|
+
const uiRoot = (0, workbench_1.workbenchUiRoot)();
|
|
192
|
+
const relative = pathname === "/" ? "index.html" : pathname.replace(/^\/ui\//, "");
|
|
193
|
+
const resolved = path.resolve(uiRoot, relative);
|
|
194
|
+
if (!resolved.startsWith(path.resolve(uiRoot) + path.sep) && resolved !== path.resolve(uiRoot)) {
|
|
195
|
+
sendJson(res, 403, { error: "forbidden: path traversal" });
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (pathname === "/" && !fs.existsSync(resolved)) {
|
|
199
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
|
|
200
|
+
res.end(fallbackIndexHtml(this.descriptor(false)));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (!fs.existsSync(resolved) || fs.statSync(resolved).isDirectory()) {
|
|
204
|
+
sendJson(res, 404, { error: `UI asset not installed: ${relative}` });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
res.writeHead(200, { "Content-Type": contentTypeFor(resolved), "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
|
|
208
|
+
res.end(fs.readFileSync(resolved));
|
|
209
|
+
}
|
|
210
|
+
/** Binds the loopback server and resolves once listening. Returns the
|
|
211
|
+
* actually-bound port (useful for `--port 0`). */
|
|
212
|
+
listen() {
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
const server = http.createServer((req, res) => this.handleRequest(req, res));
|
|
215
|
+
const requestedPort = this.args.port !== undefined ? Number(this.args.port) : undefined;
|
|
216
|
+
server.on("error", reject);
|
|
217
|
+
server.listen(requestedPort ?? 7717, "127.0.0.1", () => {
|
|
218
|
+
this.server = server;
|
|
219
|
+
const address = server.address();
|
|
220
|
+
resolve(typeof address === "object" && address ? address.port : requestedPort || 7717);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
close() {
|
|
225
|
+
return new Promise((resolve) => {
|
|
226
|
+
if (!this.server) {
|
|
227
|
+
resolve();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
this.server.close(() => resolve());
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/** `cw workbench serve` entry point: `--once`/`--json` prints ONLY the
|
|
234
|
+
* descriptor (compact JSON) and starts nothing; the default binds and
|
|
235
|
+
* blocks, printing one compact line (`{...descriptor, boundPort}`)
|
|
236
|
+
* once listening. `--require-token` is a strict opt-in: default behavior
|
|
237
|
+
* (unauthenticated local reads when no token is configured) is
|
|
238
|
+
* unchanged unless the caller explicitly asks to fail closed. */
|
|
239
|
+
async run() {
|
|
240
|
+
const requireToken = Boolean(this.args.requireToken || this.args["require-token"]);
|
|
241
|
+
if (requireToken && !this.token) {
|
|
242
|
+
process.stderr.write("workbench serve --require-token: CW_WORKBENCH_TOKEN is not set; refusing to start.\n");
|
|
243
|
+
process.exitCode = 1;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const once = Boolean(this.args.once || this.args.json);
|
|
247
|
+
if (once) {
|
|
248
|
+
process.stdout.write(`${JSON.stringify(this.descriptor(true))}\n`);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const boundPort = await this.listen();
|
|
252
|
+
const descriptor = this.descriptor(false, boundPort);
|
|
253
|
+
process.stdout.write(`${JSON.stringify({ ...descriptor, boundPort })}\n`);
|
|
254
|
+
// Block forever (until the process is killed) — a real serve.
|
|
255
|
+
await new Promise(() => { });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
exports.WorkbenchHost = WorkbenchHost;
|