@tomflow/proflow-platform-host 0.1.16 → 0.1.17

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/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @tomflow/proflow-platform-host
2
+
3
+ ## 0.1.17
4
+
5
+ ### Patch Changes
6
+
7
+ - Harden Real-3 Browser Carrier reality: controlled ChatGPT composer submission, authoritative action-permission classification, bounded binding-race deferral, Carrier Attention web relay and restart recovery, and human-deny precedence across automatic recovery.
@@ -6,7 +6,7 @@ export declare const behaviorAdapter: {
6
6
  ok: true;
7
7
  status: "SUCCEEDED";
8
8
  moduleRef: "platform-host";
9
- moduleVersion: "0.1.16";
9
+ moduleVersion: "0.1.17";
10
10
  data: {
11
11
  endpoint: string;
12
12
  stateRoot: string;
@@ -24,7 +24,7 @@ export declare const behaviorAdapter: {
24
24
  readonly ok: true;
25
25
  readonly status: "SUCCEEDED";
26
26
  readonly moduleRef: "platform-host";
27
- readonly moduleVersion: "0.1.16";
27
+ readonly moduleVersion: "0.1.17";
28
28
  };
29
29
  observedEffects: string[];
30
30
  }>;
@@ -34,7 +34,7 @@ export declare const behaviorAdapter: {
34
34
  ok: true;
35
35
  status: "SUCCEEDED";
36
36
  moduleRef: "platform-host";
37
- moduleVersion: "0.1.16";
37
+ moduleVersion: "0.1.17";
38
38
  data: {
39
39
  setupStatus: "READY";
40
40
  runtimeStatus: "RUNNING" | "STOPPED";
@@ -55,7 +55,7 @@ export declare const behaviorAdapter: {
55
55
  ok: true;
56
56
  status: "SUCCEEDED";
57
57
  moduleRef: "platform-host";
58
- moduleVersion: "0.1.16";
58
+ moduleVersion: "0.1.17";
59
59
  data: {
60
60
  endpoint: string;
61
61
  stateRoot: string;
@@ -73,7 +73,7 @@ export declare const behaviorAdapter: {
73
73
  ok: true;
74
74
  status: "SUCCEEDED";
75
75
  moduleRef: "platform-host";
76
- moduleVersion: "0.1.16";
76
+ moduleVersion: "0.1.17";
77
77
  data: {
78
78
  docs: string;
79
79
  };
@@ -86,7 +86,7 @@ export declare const behaviorAdapter: {
86
86
  ok: true;
87
87
  status: "SUCCEEDED";
88
88
  moduleRef: "platform-host";
89
- moduleVersion: "0.1.16";
89
+ moduleVersion: "0.1.17";
90
90
  data: {
91
91
  host: string;
92
92
  port: number;
@@ -97,7 +97,7 @@ export declare const behaviorAdapter: {
97
97
  result: {
98
98
  contract: "deployment.result.v1";
99
99
  moduleRef: "platform-host";
100
- moduleVersion: "0.1.16";
100
+ moduleVersion: "0.1.17";
101
101
  ok: false;
102
102
  status: "FAILED";
103
103
  error: {
@@ -114,7 +114,7 @@ export declare const behaviorAdapter: {
114
114
  readonly ok: true;
115
115
  readonly status: "SUCCEEDED";
116
116
  readonly moduleRef: "platform-host";
117
- readonly moduleVersion: "0.1.16";
117
+ readonly moduleVersion: "0.1.17";
118
118
  };
119
119
  observedEffects: string[];
120
120
  } | {
@@ -123,11 +123,11 @@ export declare const behaviorAdapter: {
123
123
  readonly ok: true;
124
124
  readonly status: "SUCCEEDED";
125
125
  readonly moduleRef: "platform-host";
126
- readonly moduleVersion: "0.1.16";
126
+ readonly moduleVersion: "0.1.17";
127
127
  } | {
128
128
  contract: "deployment.result.v1";
129
129
  moduleRef: "platform-host";
130
- moduleVersion: "0.1.16";
130
+ moduleVersion: "0.1.17";
131
131
  ok: false;
132
132
  status: "FAILED";
133
133
  error: {
@@ -3,7 +3,7 @@ export declare const descriptor: {
3
3
  readonly contractVersion: "1.0.0";
4
4
  readonly moduleRef: "platform-host";
5
5
  readonly packageName: "@tomflow/proflow-platform-host";
6
- readonly moduleVersion: "0.1.16";
6
+ readonly moduleVersion: "0.1.17";
7
7
  readonly kind: "service";
8
8
  readonly templateVersion: "1.0.0";
9
9
  readonly platformCompatibility: ">=1.0.0 <2.0.0";
@@ -3,7 +3,7 @@ export const descriptor = {
3
3
  contractVersion: "1.0.0",
4
4
  moduleRef: "platform-host",
5
5
  packageName: "@tomflow/proflow-platform-host",
6
- moduleVersion: "0.1.16",
6
+ moduleVersion: "0.1.17",
7
7
  kind: "service",
8
8
  templateVersion: "1.0.0",
9
9
  platformCompatibility: ">=1.0.0 <2.0.0",
@@ -0,0 +1,36 @@
1
+ import { type RolePackageRef } from "./role-operations.ts";
2
+ export type BrowserPermissionRole = {
3
+ agentPackageRef: RolePackageRef;
4
+ registeredPackageVersion: string;
5
+ roleRef: string;
6
+ carrierUrl: string;
7
+ };
8
+ export type RoleCarrierValidation = {
9
+ agentPackageRef: string;
10
+ registeredPackageVersion: string;
11
+ roleRef: string;
12
+ carrierUrl: string;
13
+ gatewayUrl: string;
14
+ };
15
+ export type BrowserPermissionDecision = {
16
+ decision: "AUTO_ALLOW" | "DEFER" | "HUMAN_REQUIRED";
17
+ reason: "KNOWN_PROFLOW_ACTION" | "TASK_BINDING_PENDING" | "ROLE_VALIDATION_MISMATCH" | "GATEWAY_MISMATCH" | "OPERATION_NOT_AUTHORIZED" | "CONTEXT_MISMATCH";
18
+ };
19
+ export type BrowserPermissionContext = {
20
+ conversationLocator: string;
21
+ workerRef: string | null;
22
+ taskBinding: {
23
+ agentPackageRef: string;
24
+ roleRef: string;
25
+ workerRef: string | null;
26
+ conversationLocator: string | null;
27
+ } | null;
28
+ };
29
+ export declare function classifyBrowserPermission(input: {
30
+ role: BrowserPermissionRole;
31
+ validation: RoleCarrierValidation | null;
32
+ targetHost: string | null;
33
+ currentGatewayUrl: string | null;
34
+ operationId: string;
35
+ context: BrowserPermissionContext;
36
+ }): BrowserPermissionDecision;
@@ -0,0 +1,82 @@
1
+ import { roleOperations } from "./role-operations.js";
2
+ const loopbackHosts = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
3
+ function validWorkerConversation(role, context) {
4
+ const workerRef = context.workerRef;
5
+ if (!workerRef)
6
+ return false;
7
+ try {
8
+ const locator = new URL(context.conversationLocator);
9
+ const segments = locator.pathname.split("/").filter(Boolean);
10
+ return (locator.protocol === "https:" &&
11
+ locator.hostname === "chatgpt.com" &&
12
+ locator.username === "" &&
13
+ locator.password === "" &&
14
+ locator.search === "" &&
15
+ locator.hash === "" &&
16
+ segments.length === 4 &&
17
+ segments[0] === "g" &&
18
+ segments[1] === role.roleRef &&
19
+ segments[2] === "c" &&
20
+ segments[3] === workerRef);
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ function classifyContext(role, context) {
27
+ if (!validWorkerConversation(role, context))
28
+ return { decision: "HUMAN_REQUIRED", reason: "CONTEXT_MISMATCH" };
29
+ const binding = context.taskBinding;
30
+ if (binding === null ||
31
+ binding.agentPackageRef !== role.agentPackageRef ||
32
+ binding.roleRef !== role.roleRef)
33
+ return { decision: "HUMAN_REQUIRED", reason: "CONTEXT_MISMATCH" };
34
+ if (binding.workerRef === null && binding.conversationLocator === null)
35
+ return { decision: "DEFER", reason: "TASK_BINDING_PENDING" };
36
+ if (binding.workerRef !== context.workerRef ||
37
+ binding.conversationLocator !== context.conversationLocator)
38
+ return { decision: "HUMAN_REQUIRED", reason: "CONTEXT_MISMATCH" };
39
+ return { decision: "AUTO_ALLOW", reason: "KNOWN_PROFLOW_ACTION" };
40
+ }
41
+ export function classifyBrowserPermission(input) {
42
+ const validation = input.validation;
43
+ if (validation === null ||
44
+ validation.agentPackageRef !== input.role.agentPackageRef ||
45
+ validation.registeredPackageVersion !==
46
+ input.role.registeredPackageVersion ||
47
+ validation.roleRef !== input.role.roleRef ||
48
+ validation.carrierUrl !== input.role.carrierUrl)
49
+ return { decision: "HUMAN_REQUIRED", reason: "ROLE_VALIDATION_MISMATCH" };
50
+ let trustedHost;
51
+ try {
52
+ const gateway = new URL(validation.gatewayUrl);
53
+ const currentGateway = new URL(input.currentGatewayUrl ?? "");
54
+ const trustedTransport = gateway.protocol === "https:" ||
55
+ (gateway.protocol === "http:" && loopbackHosts.has(gateway.hostname));
56
+ if (!trustedTransport ||
57
+ currentGateway.origin !== gateway.origin ||
58
+ gateway.pathname !== "/" ||
59
+ gateway.search !== "" ||
60
+ gateway.hash !== "" ||
61
+ gateway.username !== "" ||
62
+ gateway.password !== "" ||
63
+ currentGateway.pathname !== "/" ||
64
+ currentGateway.search !== "" ||
65
+ currentGateway.hash !== "" ||
66
+ currentGateway.username !== "" ||
67
+ currentGateway.password !== "")
68
+ return { decision: "HUMAN_REQUIRED", reason: "GATEWAY_MISMATCH" };
69
+ trustedHost = gateway.hostname.toLowerCase();
70
+ }
71
+ catch {
72
+ return { decision: "HUMAN_REQUIRED", reason: "GATEWAY_MISMATCH" };
73
+ }
74
+ if (input.targetHost?.toLowerCase() !== trustedHost)
75
+ return { decision: "HUMAN_REQUIRED", reason: "GATEWAY_MISMATCH" };
76
+ if (!roleOperations[input.role.agentPackageRef].has(input.operationId))
77
+ return {
78
+ decision: "HUMAN_REQUIRED",
79
+ reason: "OPERATION_NOT_AUTHORIZED",
80
+ };
81
+ return classifyContext(input.role, input.context);
82
+ }
package/dist/src/index.js CHANGED
@@ -9,6 +9,7 @@ import { createTaskServices, publicOperationNames, } from "@tomflow/proflow-task
9
9
  import { SqliteTaskStore } from "@tomflow/proflow-task-store-sqlite";
10
10
  import { taskMigrations } from "@tomflow/proflow-task-store-sqlite/migrations";
11
11
  import { z } from "zod";
12
+ import { classifyBrowserPermission, } from "./browser-permission-policy.js";
12
13
  import { roleOperations, rolePackageRefs, } from "./role-operations.js";
13
14
  const loopbackHosts = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
14
15
  // Business owner calls are Promise-driven. This is only a last-resort transport
@@ -916,6 +917,47 @@ async function constructGraph(config, executionCredential, modelCredential) {
916
917
  throw new Error("ROLE_NOT_FOUND");
917
918
  return role;
918
919
  };
920
+ const browserPermissionRole = (roleRef) => {
921
+ const role = agent
922
+ .listRegisteredRoles()
923
+ .find((candidate) => candidate.roleRef === roleRef);
924
+ if (!role ||
925
+ !rolePackageRefs.includes(role.agentPackageRef))
926
+ return null;
927
+ return { ...role, agentPackageRef: role.agentPackageRef };
928
+ };
929
+ const roleCarrierValidation = async (agentPackageRef) => {
930
+ try {
931
+ const value = object(JSON.parse(await readFile(join(config.workspaceRoot, ".proflow", "state", "agent", "role-carrier-validation", `${encodeURIComponent(agentPackageRef)}.json`), "utf8")), "role carrier validation evidence");
932
+ if (value.contract !== "proflow.role-carrier-validation.v1")
933
+ return null;
934
+ return {
935
+ agentPackageRef: string(value.agentPackageRef, "agentPackageRef"),
936
+ registeredPackageVersion: string(value.registeredPackageVersion, "registeredPackageVersion"),
937
+ roleRef: string(value.roleRef, "roleRef"),
938
+ carrierUrl: string(value.carrierUrl, "carrierUrl"),
939
+ gatewayUrl: string(value.gatewayUrl, "gatewayUrl"),
940
+ };
941
+ }
942
+ catch {
943
+ return null;
944
+ }
945
+ };
946
+ const currentAgentGatewayUrl = async () => {
947
+ try {
948
+ const value = object(JSON.parse(await readFile(join(config.workspaceRoot, ".proflow", "runtime", "modules", "agent-gateway", "shared-facts.json"), "utf8")), "agent gateway shared facts");
949
+ if (value.contract !== "proflow.module-shared-facts.v1" ||
950
+ value.moduleRef !== "agent-gateway")
951
+ return null;
952
+ const facts = object(value.facts, "agent gateway facts");
953
+ return typeof facts.publicBaseUrl === "string"
954
+ ? facts.publicBaseUrl
955
+ : null;
956
+ }
957
+ catch {
958
+ return null;
959
+ }
960
+ };
919
961
  const roleManagement = Object.freeze({
920
962
  async invoke(operation, rawInput) {
921
963
  const value = object(rawInput, "role management input");
@@ -1122,6 +1164,41 @@ async function constructGraph(config, executionCredential, modelCredential) {
1122
1164
  const value = object(rawInput, "observer application input");
1123
1165
  if (operation === "task.projection")
1124
1166
  return taskDriverPorts.getTaskDriveProjection(string(value.taskId, "taskId"));
1167
+ if (operation === "browser.permission.classify") {
1168
+ const roleRef = string(value.roleRef, "roleRef");
1169
+ const taskId = string(value.taskId, "taskId");
1170
+ const operationId = string(value.operationId, "operationId");
1171
+ const conversationLocator = string(value.conversationLocator, "conversationLocator");
1172
+ const workerRef = typeof value.workerRef === "string" ? value.workerRef : null;
1173
+ const role = browserPermissionRole(roleRef);
1174
+ if (!role)
1175
+ return {
1176
+ decision: "HUMAN_REQUIRED",
1177
+ reason: "ROLE_VALIDATION_MISMATCH",
1178
+ };
1179
+ let taskBinding = null;
1180
+ try {
1181
+ const current = unwrap(task.queries.getTask({ taskId }));
1182
+ taskBinding =
1183
+ current.roleBindings.find((candidate) => candidate.roleRef === roleRef &&
1184
+ candidate.agentPackageRef === role.agentPackageRef) ?? null;
1185
+ }
1186
+ catch {
1187
+ taskBinding = null;
1188
+ }
1189
+ const [validation, currentGatewayUrl] = await Promise.all([
1190
+ roleCarrierValidation(role.agentPackageRef),
1191
+ currentAgentGatewayUrl(),
1192
+ ]);
1193
+ return classifyBrowserPermission({
1194
+ role,
1195
+ validation,
1196
+ targetHost: typeof value.targetHost === "string" ? value.targetHost : null,
1197
+ currentGatewayUrl,
1198
+ operationId,
1199
+ context: { conversationLocator, workerRef, taskBinding },
1200
+ });
1201
+ }
1125
1202
  if (operation === "browser.binding")
1126
1203
  return browserOwnerPorts.task.getWorkerBinding(string(value.taskId, "taskId"), string(value.roleRef, "roleRef"));
1127
1204
  if (operation === "browser.bindWorker") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomflow/proflow-platform-host",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -24,19 +24,19 @@
24
24
  ],
25
25
  "dependencies": {
26
26
  "zod": "4.1.12",
27
+ "@tomflow/proflow-task-migration-runner": "^0.1.11",
28
+ "@tomflow/proflow-execution-browser-extension": "^0.1.38",
29
+ "@tomflow/proflow-task-orchestration": "^0.1.10",
30
+ "@tomflow/proflow-task-store-sqlite": "^0.1.11",
27
31
  "@tomflow/proflow-agent-runtime": "^0.1.13",
28
32
  "@tomflow/proflow-module-contract": "^0.1.13",
29
- "@tomflow/proflow-execution-contracts": "^0.1.10",
30
- "@tomflow/proflow-task-orchestration": "^0.1.10",
31
- "@tomflow/proflow-task-migration-runner": "^0.1.11",
32
- "@tomflow/proflow-execution-browser-extension": "^0.1.32",
33
- "@tomflow/proflow-task-store-sqlite": "^0.1.11"
33
+ "@tomflow/proflow-execution-contracts": "^0.1.10"
34
34
  },
35
35
  "devDependencies": {
36
- "@tomflow/proflow-agent-gateway": "^0.1.15",
36
+ "@tomflow/proflow-model-runtime": "^0.1.22",
37
37
  "@tomflow/proflow-deployment-conformance": "^0.1.13",
38
- "@tomflow/proflow-model-runtime": "^0.1.21",
39
- "@tomflow/proflow-execution-runtime": "^0.1.16"
38
+ "@tomflow/proflow-agent-gateway": "^0.1.15",
39
+ "@tomflow/proflow-execution-runtime": "^0.1.17"
40
40
  },
41
41
  "description": "Provides the ProFlow local application composition root that binds Task, Agent, Execution and Model owner transports.",
42
42
  "keywords": [
@@ -3,7 +3,7 @@
3
3
  "contractVersion": "1.0.0",
4
4
  "moduleRef": "platform-host",
5
5
  "packageName": "@tomflow/proflow-platform-host",
6
- "moduleVersion": "0.1.16",
6
+ "moduleVersion": "0.1.17",
7
7
  "kind": "service",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",