humanish 0.19.1 → 0.20.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.
@@ -41,7 +41,7 @@ import { toErrorMessage } from "./command-failure.js";
41
41
  import { mapWithConcurrency } from "./concurrency.js";
42
42
  import { commandDigestOf, composeLaneInstructions, defaultPackLocalTree, provisionCloneSubject, provisionLocalTreeSubject, resolveLaneDevice, resolveSubjectState, runCuaLane } from "./cua-actor-lab.js";
43
43
  import { createDesktopSandbox, loadE2BDesktopModule } from "./e2b-desktop-launch.js";
44
- import { concurrentSharedWorldValidationReason } from "./lab-config.js";
44
+ import { concurrentSharedWorldValidationReason, externalPublicSharedWorldValidationReason } from "./lab-config.js";
45
45
  import { buildObserverData } from "./observer-data.js";
46
46
  import { attachObserverRuntimeStreamUrls, renderObserver } from "./observer.js";
47
47
  import { redactText } from "./redaction.js";
@@ -69,6 +69,58 @@ const SUBJECT_PROVISION_BUDGET_MS = 30 * 60_000;
69
69
  const DEFAULT_STATE_STEP_TIMEOUT_MS = 5 * 60_000;
70
70
  const DEFAULT_PROBER_CADENCE_MS = 1000;
71
71
  const DEFAULT_MISSION = "You are one of MANY users hitting a shared web application at the same time. The browser is already open at the app. Accomplish your role's task, then stop.";
72
+ // EXTERNAL-PUBLIC plane class: the honest-downgrade attribution ceiling. The concurrent family
73
+ // (an honest ceiling) PLUS the mandatory external-public disclosures — mirrored in run.ts's required
74
+ // set (CONCURRENT_ATTRIBUTION_LIMITS + EXTERNAL_PUBLIC_EXTRA_LIMITS). Verify fails closed on a missing one.
75
+ export const EXTERNAL_PUBLIC_ATTRIBUTION_LIMITS = [
76
+ ...CONCURRENT_ATTRIBUTION_LIMITS,
77
+ "external-public-plane",
78
+ "operator-attested-target-not-harness-controlled",
79
+ "no-synthetic-attestation",
80
+ "no-authoritative-shared-state-proof",
81
+ "concurrency-by-temporal-co-occupancy-only"
82
+ ];
83
+ // The default host-first handoff barrier deadline (ms). The host seat must surface a shared-session
84
+ // (/lobby/CODE) URL within this budget or the run fails closed and no follower opens.
85
+ const DEFAULT_HANDOFF_DEADLINE_MS = 120_000;
86
+ /**
87
+ * The cineguessr (and general "/lobby/CODE") shared-session URL matcher. A code is exactly 6 chars of
88
+ * the [A-Z2-9] class; a locale prefix (/en/lobby/…) and a query/hash suffix are tolerated. RUNTIME-ONLY
89
+ * input (a live location.href); only the extracted CODE is used, and it lands only as a digest.
90
+ */
91
+ export const LOBBY_CODE_PATTERN = /\/lobby\/([A-Z2-9]{6})(?:$|[/?#])/;
92
+ /** Extract the shared-session CODE from a (runtime-only) observed URL, or undefined. Exported for the
93
+ * handoff regex table test — pure, no side effects, never persists its input. */
94
+ export function extractLobbyCode(url) {
95
+ if (typeof url !== "string")
96
+ return undefined;
97
+ const match = url.match(LOBBY_CODE_PATTERN);
98
+ return match ? match[1] : undefined;
99
+ }
100
+ /** A minimal resolve-once latch for the host-first handoff barrier. */
101
+ function deferred() {
102
+ let resolve;
103
+ let reject;
104
+ let done = false;
105
+ const promise = new Promise((res, rej) => {
106
+ resolve = (value) => { if (!done) {
107
+ done = true;
108
+ res(value);
109
+ } };
110
+ reject = (reason) => { if (!done) {
111
+ done = true;
112
+ rej(reason);
113
+ } };
114
+ });
115
+ return { promise, resolve, reject, settled: () => done };
116
+ }
117
+ /** Marker error the host-first barrier rejects with when the deadline elapses (fail-closed). */
118
+ class HandoffTimeoutError extends Error {
119
+ constructor(deadlineMs) {
120
+ super(`the host never produced a /lobby/CODE URL within the ${deadlineMs}ms handoff deadline`);
121
+ this.name = "HandoffTimeoutError";
122
+ }
123
+ }
72
124
  function readPositiveInt(value, fallback) {
73
125
  if (value === undefined)
74
126
  return fallback;
@@ -188,6 +240,35 @@ function buildActorSpec(config, role, index) {
188
240
  traceArtifactPath: `actors/${streamId}.json`
189
241
  };
190
242
  }
243
+ /** Thread the host-yielded lobby CODE into a follower's mission at runtime (external-public route).
244
+ * The CODE flows into the follower's join instruction; it is persisted only as the composed prompt
245
+ * the model reads (never a raw bundle field), and the lab scrubs the CODE from all narration. The
246
+ * follower joins through the real UI (a direct /lobby/CODE visit does not auto-join a non-member). */
247
+ function withLobbyCodeMission(spec, code) {
248
+ return {
249
+ ...spec,
250
+ instructions: `${spec.instructions}\n\nThe multiplayer lobby code is ${code}. On the home screen choose Join, enter this lobby code, enter your name, and submit to join the shared game (do not open a lobby URL directly — go through the Join flow).`
251
+ };
252
+ }
253
+ /** A follower lane that failed closed at the host-first barrier (the host never yielded a /lobby/CODE
254
+ * within the deadline): it NEVER opened a browser, so it carries no session/screenshots — just the
255
+ * handoff-timeout reason. actorLanePassed(...) is false (no session), so it is honestly non-pass. */
256
+ function makeBlockedFollowerOutcome(spec, deadlineMs) {
257
+ return {
258
+ spec,
259
+ sessionError: `handoff barrier: the host never surfaced a /lobby/CODE URL within ${deadlineMs}ms; this follower failed closed WITHOUT opening (no wasted turns).`,
260
+ killed: false,
261
+ streamUrlPresent: false,
262
+ screenshots: [],
263
+ stateStepRecords: [],
264
+ phaseRecords: [],
265
+ warnings: [],
266
+ noEngagement: true,
267
+ selfReportedBlocker: false,
268
+ harnessError: false,
269
+ skippedReason: "handoff-timeout"
270
+ };
271
+ }
191
272
  async function writeConcurrentRunArtifacts(bundle, preparedRunPaths) {
192
273
  const runPaths = await validatePreparedRunArtifactPaths(preparedRunPaths);
193
274
  const publicBundle = {
@@ -253,11 +334,20 @@ export async function runConcurrentSharedWorld(options) {
253
334
  if (!descriptor || !isCuaActorDescriptor(descriptor)) {
254
335
  return fail("HUMANISH_CONCURRENT_SHARED_WORLD_LAB_ACTOR_UNSUPPORTED", `actors[0].type "${actorType}" is not a registered computer-use actor.`);
255
336
  }
256
- // Re-enforce the concurrent cross-validation (library API surface).
257
- const invalidReason = concurrentSharedWorldValidationReason(config);
337
+ // The PLANE-class discriminator (#164 phase 2): an app-url subject is the EXTERNAL-PUBLIC plane (a
338
+ // real operator-owned public deployment used directly as the shared plane — NO getHost, clone,
339
+ // subject sandbox, or seed); everything else is the historical provisioned-getHost plane.
340
+ const planeClass = config.subject.source === "app-url" ? "external-public" : "provisioned-getHost";
341
+ // Re-enforce the cross-validation (library API surface). The external-public branch NEVER touches
342
+ // the getHost synthetic gate — that gate exists because getHost is internet-reachable AND
343
+ // harness-owned; a public site the harness neither provisioned nor exposed has neither property.
344
+ const invalidReason = planeClass === "external-public"
345
+ ? externalPublicSharedWorldValidationReason(config)
346
+ : concurrentSharedWorldValidationReason(config);
258
347
  if (invalidReason) {
259
348
  return fail("HUMANISH_CONCURRENT_SHARED_WORLD_LAB_INVALID", invalidReason, descriptor.id);
260
349
  }
350
+ // provisioned-getHost fields (all absent on the external-public plane — forbidden at validation).
261
351
  const serve = config.subject.serve;
262
352
  const localTreeRoute = config.subject.source === "local-tree";
263
353
  const subjectRepo = config.subject.repos?.[0] ?? "";
@@ -315,6 +405,38 @@ export async function runConcurrentSharedWorld(options) {
315
405
  let snapshotIndex = 0;
316
406
  let liveObserver;
317
407
  const runtimeStreamUrls = [];
408
+ // EXTERNAL-PUBLIC plane state (#164 phase 2). publicAppUrl is the operator-declared shared plane;
409
+ // its ORIGIN is persisted digest-only (publicOriginDigest), never raw (the raw URL + the runtime
410
+ // observed lobby CODE never land — TENSION 3). The latch code is scrubbed from all narration.
411
+ const publicAppUrl = config.subject.appUrl ?? "";
412
+ // The operator-DECLARED origin (from subject.appUrl) — recorded for evidence/reference ONLY. The
413
+ // operator-OWNERSHIP claim rests on the subject.publicTarget.authorized attestation + this declared
414
+ // appUrl, NOT on digest equality (blocker 2): a normal cross-origin redirect (apex->www, http->https;
415
+ // cineguessr.com 307-redirects) makes the seats' OBSERVED origin differ from the declared one, which
416
+ // is expected and MUST NOT fail the run. Persisted digest-only (never the raw origin).
417
+ const declaredOriginDigest = planeClass === "external-public" && publicAppUrl
418
+ ? hostOriginDigest(publicAppUrl)
419
+ : undefined;
420
+ // The OBSERVED convergence origin — computed AFTER fan-out from what the seats ACTUALLY reached (the
421
+ // convergence proof is what the seats OBSERVED, not what was declared). Set iff every observing seat
422
+ // agrees on ONE origin; that agreement IS the convergence proof and becomes plane.publicOriginDigest.
423
+ let publicOriginDigest;
424
+ // Per-lane runtime-only observed state (never persisted raw): the last observed URL and the last
425
+ // observed /lobby/CODE per seat, fed by onObservedUrl. The URL is digested to ORIGIN for each seat's
426
+ // routeHostDigest (no code leaks); the codes drive the cross-seat lobby-convergence digest.
427
+ const observedFinalUrls = new Array(roles.length);
428
+ const observedLobbyCodes = new Array(roles.length);
429
+ let lobbyConvergenceDigest;
430
+ let handoffTimedOut = false;
431
+ // A closure that scrubs the latched lobby CODE from ANY persisted narration once the host resolves
432
+ // it (the 6-char code has no detectable secret shape, so shape-only redaction cannot catch it).
433
+ let latchedLobbyCode;
434
+ const scrubKnownValuesWithLobbyCode = (text) => {
435
+ const base = scrubKnownValues(text);
436
+ return latchedLobbyCode && latchedLobbyCode.length > 0
437
+ ? base.split(latchedLobbyCode).join("[REDACTED_LOBBY_CODE]")
438
+ : base;
439
+ };
318
440
  // Pack the working tree ONCE per run, on the host, BEFORE the subject sandbox is created
319
441
  // (mirrors the sequential route + the cua route's ordering): a packing failure fails the run
320
442
  // closed here, never spending sandbox cost. Dry-run packs nothing.
@@ -337,7 +459,11 @@ export async function runConcurrentSharedWorld(options) {
337
459
  return fail("HUMANISH_CONCURRENT_SHARED_WORLD_LAB_FAILED", `local-tree packing failed: ${redactText(scrubKnownValues(toErrorMessage(error)))}`, descriptor.id);
338
460
  }
339
461
  }
340
- if (!dryRun) {
462
+ if (!dryRun && planeClass === "provisioned-getHost") {
463
+ if (!serve) {
464
+ // Defense-in-depth: concurrentSharedWorldValidationReason already required serve above.
465
+ return fail("HUMANISH_CONCURRENT_SHARED_WORLD_LAB_INVALID", "the provisioned-getHost concurrent shared-world route requires `subject.serve`.", descriptor.id);
466
+ }
341
467
  let subjectModule;
342
468
  let subjectDesktop;
343
469
  // Background prober dispose signal (FIX-9: cleared in finally).
@@ -567,16 +693,222 @@ export async function runConcurrentSharedWorld(options) {
567
693
  }
568
694
  }
569
695
  }
570
- const subjectState = resolveSubjectState({ declared: config.subject.state, dryRun, executed: stateStepRecords });
696
+ // EXTERNAL-PUBLIC plane (#164 phase 2): NO subject sandbox, NO getHost, NO prober. The shared plane
697
+ // is the operator-declared public deployment (publicAppUrl); each seat opens it directly and reaches
698
+ // the shared session through the real UI. A host-first barrier extracts the /lobby/CODE from the host
699
+ // seat's CDP-observed URL (onObservedUrl) and threads it into the follower missions; a follower fails
700
+ // closed WITHOUT opening if the host never yields a code within the handoff deadline.
701
+ if (!dryRun && planeClass === "external-public") {
702
+ const cuaHooks = {
703
+ ...(hooks.loadDesktopModule ? { loadDesktopModule: hooks.loadDesktopModule } : {}),
704
+ ...(hooks.detachedTimers ? { detachedTimers: hooks.detachedTimers } : {}),
705
+ ...(hooks.env ? { env: hooks.env } : {}),
706
+ ...(hooks.prepareDesktop ? { prepareDesktop: (desktop) => hooks.prepareDesktop(desktop) } : {}),
707
+ onRuntimeStreamReady: (stream) => {
708
+ runtimeStreamUrls.push({ streamId: stream.streamId, url: stream.url });
709
+ if (liveObserver) {
710
+ attachObserverRuntimeStreamUrls(liveObserver, runtimeStreamUrls);
711
+ }
712
+ }
713
+ };
714
+ const baseActorDeps = {
715
+ config,
716
+ descriptor,
717
+ cloneRoute: false,
718
+ subjectEnvNames: [],
719
+ hasGithubToken: false,
720
+ env,
721
+ openaiApiKey,
722
+ e2bApiKey,
723
+ requestTimeoutMs,
724
+ perLaneSandboxMs: timeoutMs + SANDBOX_TIMEOUT_BUFFER_MS,
725
+ timeoutMs,
726
+ laneCount: roles.length,
727
+ artifactRoot: runPaths,
728
+ redactScreenshots,
729
+ // Scrub the latched lobby CODE (known once the host resolves it) from ALL narration.
730
+ scrubKnownValues: scrubKnownValuesWithLobbyCode,
731
+ runSession,
732
+ now,
733
+ hooks: cuaHooks,
734
+ screenMismatchPolicy: "record-evidence"
735
+ };
736
+ // Publish an attached live Observer BEFORE fan-out (mirrors the provisioned path).
737
+ if (options.onObserverReady) {
738
+ const inProgressBundle = buildConcurrentSharedWorldBundle({
739
+ config,
740
+ descriptor,
741
+ createdAt,
742
+ dryRun: false,
743
+ inProgress: true,
744
+ runId,
745
+ source,
746
+ roles,
747
+ actorSpecs,
748
+ actorResults: [],
749
+ stateSnapshots: [],
750
+ subject: { source: "app-url", envNames: [], state: { provenance: "external-public" } },
751
+ seedDigest,
752
+ planeClass: "external-public",
753
+ // Pre-fan-out snapshot: no seat has observed an origin yet, so the OBSERVED publicOriginDigest
754
+ // is not available; surface the DECLARED origin for the live Observer's reference.
755
+ ...(declaredOriginDigest === undefined ? {} : { declaredOriginDigest })
756
+ });
757
+ await writeConcurrentRunArtifacts(inProgressBundle, runPaths);
758
+ liveObserver = observerResultForConcurrentArtifacts(cwd, runId, artifactRoot, [
759
+ "Live external-public concurrent shared-world Observer is attached before final verification; stream auth URLs are runtime-only and are not persisted."
760
+ ]);
761
+ await options.onObserverReady(liveObserver);
762
+ }
763
+ // The host-first handoff barrier.
764
+ //
765
+ // TEMPORARY SHIM (tracked by #296): this CDP URL-relay handoff — reading the host's /lobby/CODE off
766
+ // its own browser and threading it into the follower missions — is a temporary coordination shim.
767
+ // It is to be augmented/replaced by the actor message bus (faux SMS/email invite) in #297: the
768
+ // human-realistic version is the HOST SENDING the invite link and followers RECEIVING and tapping
769
+ // it, rather than the orchestrator relaying the code out-of-band.
770
+ const lobbyCodeLatch = deferred();
771
+ const handoffDeadlineMs = hooks.handoffDeadlineMs ?? Math.min(DEFAULT_HANDOFF_DEADLINE_MS, timeoutMs);
772
+ let deadlineTimer;
773
+ const deadline = new Promise((_resolve, reject) => {
774
+ deadlineTimer = setTimeout(() => reject(new HandoffTimeoutError(handoffDeadlineMs)), handoffDeadlineMs);
775
+ });
776
+ deadline.catch(() => undefined); // never an unhandled rejection
777
+ const makeLaneObservedUrl = (laneIndex, isHost) => (url) => {
778
+ if (typeof url !== "string" || url.length === 0)
779
+ return;
780
+ observedFinalUrls[laneIndex] = url; // runtime-only; digested to origin, never persisted raw
781
+ const code = extractLobbyCode(url);
782
+ if (code !== undefined) {
783
+ observedLobbyCodes[laneIndex] = code;
784
+ if (isHost) {
785
+ latchedLobbyCode = code; // scrub it from any subsequent narration
786
+ if (deadlineTimer) {
787
+ clearTimeout(deadlineTimer);
788
+ deadlineTimer = undefined;
789
+ }
790
+ lobbyCodeLatch.resolve(code);
791
+ }
792
+ }
793
+ };
794
+ // The HOST lane (which yields the /lobby/CODE the followers wait on) runs on its OWN dedicated
795
+ // slot, and the FOLLOWERS run through a bounded pool of size concurrency-1 (blockers 1 & 4):
796
+ // followers block on `Promise.race([lobbyCodeLatch.promise, deadline])` while holding a worker
797
+ // slot, so if the host lane were scheduled INSIDE the same bounded pool it could be starved (never
798
+ // scheduled among the first `concurrency` workers) and the run would die with a spurious
799
+ // HANDOFF_TIMEOUT (e.g. lanes [p2,p3,host] with concurrency 2). Giving the host its own slot,
800
+ // started IMMEDIATELY and OUTSIDE the follower pool, guarantees it is ALWAYS schedulable regardless
801
+ // of its roster position or of concurrency vs lane count — while total in-flight paid desktops stay
802
+ // ≤ the declared concurrency (host + up to concurrency-1 followers), preserving the spend cap.
803
+ const runHostLane = async (spec, laneIndex) => {
804
+ const onObservedUrl = makeLaneObservedUrl(laneIndex, true);
805
+ const startedAt = now();
806
+ let outcome;
807
+ try {
808
+ outcome = await runCuaLane(spec, { ...baseActorDeps, appUrl: publicAppUrl, onObservedUrl });
809
+ }
810
+ finally {
811
+ // If the host finished without ever surfacing a code, release followers to fail closed
812
+ // immediately rather than wait the full deadline (a no-op if it already resolved).
813
+ lobbyCodeLatch.reject(new HandoffTimeoutError(handoffDeadlineMs));
814
+ }
815
+ const endedAt = now();
816
+ return { spec, outcome, startedAt, endedAt, route: observedFinalUrls[laneIndex] ?? publicAppUrl };
817
+ };
818
+ const runFollowerLane = async (spec, laneIndex) => {
819
+ const onObservedUrl = makeLaneObservedUrl(laneIndex, false);
820
+ // FOLLOWER: do NOT compose a mission or open the target until the host yields a lobby code.
821
+ let code;
822
+ try {
823
+ code = await Promise.race([lobbyCodeLatch.promise, deadline]);
824
+ }
825
+ catch {
826
+ // Fail closed WITHOUT opening (no wasted turns against a codeless home page).
827
+ handoffTimedOut = true;
828
+ const at = now();
829
+ return { spec, outcome: makeBlockedFollowerOutcome(spec, handoffDeadlineMs), startedAt: at, endedAt: at, route: publicAppUrl };
830
+ }
831
+ const followerSpec = withLobbyCodeMission(spec, code);
832
+ const startedAt = now();
833
+ const outcome = await runCuaLane(followerSpec, { ...baseActorDeps, appUrl: publicAppUrl, onObservedUrl });
834
+ const endedAt = now();
835
+ return { spec, outcome, startedAt, endedAt, route: observedFinalUrls[laneIndex] ?? publicAppUrl };
836
+ };
837
+ // Split the roster into the designated host lane and the followers, preserving each follower's
838
+ // ORIGINAL lane index so results land back in lane order (validation guarantees EXACTLY ONE host).
839
+ const hostLaneIndex = roles.findIndex((role) => role.host === true);
840
+ const followerEntries = actorSpecs
841
+ .map((spec, index) => ({ spec, index }))
842
+ .filter(({ index }) => index !== hostLaneIndex);
843
+ const laneResults = new Array(actorSpecs.length);
844
+ try {
845
+ const hostPromise = hostLaneIndex >= 0 && actorSpecs[hostLaneIndex] !== undefined
846
+ ? runHostLane(actorSpecs[hostLaneIndex], hostLaneIndex)
847
+ : undefined;
848
+ const followerResultsPromise = mapWithConcurrency(followerEntries, Math.max(1, concurrency - 1), ({ spec, index }) => runFollowerLane(spec, index));
849
+ const [hostResult, followerResults] = await Promise.all([hostPromise, followerResultsPromise]);
850
+ if (hostResult !== undefined && hostLaneIndex >= 0) {
851
+ laneResults[hostLaneIndex] = hostResult;
852
+ }
853
+ followerEntries.forEach((entry, i) => { laneResults[entry.index] = followerResults[i]; });
854
+ actorResults = laneResults;
855
+ }
856
+ catch (error) {
857
+ runError = redactText(scrubKnownValuesWithLobbyCode(toErrorMessage(error)));
858
+ warnings.push(`External-public concurrent shared-world run failed before completion: ${runError}`);
859
+ }
860
+ finally {
861
+ if (deadlineTimer) {
862
+ clearTimeout(deadlineTimer);
863
+ deadlineTimer = undefined;
864
+ }
865
+ }
866
+ // Observed-origin convergence proof (blocker 2): the convergence claim is about what the seats
867
+ // OBSERVED, not what was DECLARED. Digest each observing seat's origin and require they AGREE on
868
+ // ONE — that agreement IS the convergence proof and becomes plane.publicOriginDigest. A normal
869
+ // cross-origin redirect (declared apex -> observed www) is therefore tolerated: the seats still
870
+ // converge on ONE observed origin. Leave it undefined (verify fails closed) only if the seats did
871
+ // not converge on a single observed origin (or none observed one).
872
+ const observedOriginDigests = observedFinalUrls
873
+ .filter((url) => typeof url === "string" && url.length > 0)
874
+ .map((url) => hostOriginDigest(url));
875
+ const distinctObservedOrigins = new Set(observedOriginDigests);
876
+ publicOriginDigest = distinctObservedOrigins.size === 1
877
+ ? [...distinctObservedOrigins][0]
878
+ // NOTHING observed (e.g. a handoff-timeout run where no seat ever navigated): fall back to the
879
+ // DECLARED origin so a FAILED run's bundle stays structurally valid (every seat's route then
880
+ // digests to the declared origin too). The run still fails closed for its own reason (HANDOFF_
881
+ // TIMEOUT / no lobby convergence / no overlap-on-pass). GENUINE divergence (≥2 distinct observed
882
+ // origins) leaves it undefined so verify fails closed on the non-convergence.
883
+ : distinctObservedOrigins.size === 0
884
+ ? declaredOriginDigest
885
+ : undefined;
886
+ // Lobby-convergence proof: a digest of the shared /lobby/CODE path iff EVERY seat converged on the
887
+ // SAME code (a follower stuck on "/" yields no code → no false convergence). Digest-only. NOTE:
888
+ // observedLobbyCodes may be a SPARSE array (a seat that never observed a code leaves a hole), and
889
+ // Array.prototype.every SKIPS holes — so count the DEFINED codes explicitly, never rely on every().
890
+ const definedCodes = observedLobbyCodes.filter((code) => code !== undefined);
891
+ const distinctCodes = new Set(definedCodes);
892
+ if (distinctCodes.size === 1 && definedCodes.length === roles.length) {
893
+ lobbyConvergenceDigest = commandDigestOf(`/lobby/${[...distinctCodes][0]}`);
894
+ }
895
+ if (handoffTimedOut && runError === undefined) {
896
+ runError = `The host seat never produced a /lobby/CODE URL within the ${handoffDeadlineMs}ms handoff deadline; follower seats failed closed without opening.`;
897
+ }
898
+ }
899
+ // Subject provenance: external-public is the operator-declared, operator-owned public deployment
900
+ // (neither provisioned nor seeded); the provisioned path builds clone/local-tree provenance.
901
+ const subject = planeClass === "external-public"
902
+ ? { source: "app-url", envNames: [], state: { provenance: "external-public" } }
903
+ : buildSubjectProvenance({
904
+ localTreeRoute,
905
+ publicRepo,
906
+ subjectCommit: localTreeRoute ? localTreeArchive?.git?.commit : subjectCommit,
907
+ localTreeArchive,
908
+ subjectEnvNames,
909
+ state: resolveSubjectState({ declared: config.subject.state, dryRun, executed: stateStepRecords })
910
+ });
571
911
  const planeCommit = localTreeRoute ? localTreeArchive?.git?.commit : subjectCommit;
572
- const subject = buildSubjectProvenance({
573
- localTreeRoute,
574
- publicRepo,
575
- subjectCommit: planeCommit,
576
- localTreeArchive,
577
- subjectEnvNames,
578
- state: subjectState
579
- });
580
912
  // Collect per-actor warnings (each lane's own teardown/raw-screenshot notes).
581
913
  for (const result of actorResults) {
582
914
  warnings.push(...result.outcome.warnings);
@@ -594,8 +926,12 @@ export async function runConcurrentSharedWorld(options) {
594
926
  stateSnapshots,
595
927
  subject,
596
928
  seedDigest,
929
+ planeClass,
597
930
  ...(planeCommit === undefined ? {} : { subjectCommit: planeCommit }),
598
931
  ...(getHostUrl === undefined ? {} : { hostDigest: hostOriginDigest(getHostUrl) }),
932
+ ...(publicOriginDigest === undefined ? {} : { publicOriginDigest }),
933
+ ...(declaredOriginDigest === undefined ? {} : { declaredOriginDigest }),
934
+ ...(lobbyConvergenceDigest === undefined ? {} : { lobbyConvergenceDigest }),
599
935
  ...(runError === undefined ? {} : { runError })
600
936
  });
601
937
  const adapterWarnings = [];
@@ -673,6 +1009,13 @@ export async function runConcurrentSharedWorld(options) {
673
1009
  const errorResult = (() => {
674
1010
  if (ok)
675
1011
  return undefined;
1012
+ if (handoffTimedOut) {
1013
+ // Checked BEFORE the observer failure: the host never yielded a /lobby/CODE within the
1014
+ // deadline (followers failed closed without opening), which is the ROOT CAUSE — and it can
1015
+ // itself make the Observer unable to render a coherent run. Report the distinct, honest
1016
+ // handoff-timeout code rather than a generic observer/run failure.
1017
+ return { code: "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_HANDOFF_TIMEOUT", message: runError ?? "The host seat never produced a /lobby/CODE URL within the handoff deadline." };
1018
+ }
676
1019
  if (!observer.ok) {
677
1020
  return { code: "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_FAILED", message: observer.error?.message ?? "Observer failed for the concurrent shared-world run." };
678
1021
  }
@@ -735,13 +1078,15 @@ function actorLanePassed(result) {
735
1078
  export function buildConcurrentSharedWorldBundle(args) {
736
1079
  const { config, descriptor, createdAt, dryRun, actorSpecs, actorResults, roles } = args;
737
1080
  const inProgress = args.inProgress === true;
1081
+ const external = (args.planeClass ?? "provisioned-getHost") === "external-public";
738
1082
  const simulations = [];
739
1083
  const streams = [];
740
1084
  const events = [];
741
- // Public-safe label only — the raw getHost URL never lands in the bundle (it embeds the live
742
- // sandbox id + matches the e2b-URL redaction). The host identity is carried by plane.hostDigest.
743
- const appUrl = "[provisioned-subject]";
744
- const planeCommit = dryRun ? undefined : args.subjectCommit;
1085
+ // Public-safe label only — neither the raw getHost URL (provisioned) nor the raw public origin
1086
+ // (external-public) lands in the bundle. The plane identity is a DIGEST (plane.hostDigest on
1087
+ // getHost; plane.publicOriginDigest on external-public).
1088
+ const appUrl = external ? "[external-public-plane]" : "[provisioned-subject]";
1089
+ const planeCommit = external ? undefined : dryRun ? undefined : args.subjectCommit;
745
1090
  events.push({
746
1091
  id: "event-000-created",
747
1092
  at: createdAt,
@@ -760,14 +1105,20 @@ export function buildConcurrentSharedWorldBundle(args) {
760
1105
  ? `packed working tree (archiveSha256 ${args.subject.archiveSha256}${args.subject.dirty === true ? ", dirty working tree" : args.subject.dirty === false ? ", clean working tree" : ""})`
761
1106
  : "packed working tree (archive digest unresolved; provisioning failed before resolution)")
762
1107
  : `clone of ${args.subject.repo}${args.subjectCommit ? `@${args.subjectCommit}` : ""}`;
1108
+ // External-public plane provenance is HONESTLY different: an operator-declared, operator-OWNED
1109
+ // public deployment humanish neither provisioned nor seeded — NO getHost, NO clone, NO synthetic
1110
+ // attestation (claiming synthetic on a real site is a lie). The origin persists digest-only.
1111
+ const externalPlaneOwner = config.subject.publicTarget?.owner ?? "(operator-declared)";
763
1112
  events.push({
764
1113
  id: "event-001-plane",
765
1114
  at: createdAt,
766
1115
  level: "info",
767
1116
  type: "concurrent-shared-world.plane.provenance",
768
- message: dryRun
769
- ? `Shared plane declared: ${dryRunPlaneLabel}, served + getHost-exposed in-sandbox (dry-run contract; nothing ${args.subject.source === "local-tree" ? "packed" : "cloned"}). Seed recipe ${args.seedDigest}; SYNTHETIC subject (author-attested); env names: ${args.subject.envNames?.join(", ") || "none"} (values never persisted).`
770
- : `Shared plane: ${livePlaneLabel}, served + exposed at the harness-minted getHost URL; seed recipe ${args.seedDigest}; SYNTHETIC subject (author-attested); env names: ${args.subject.envNames?.join(", ") || "none"} (values never persisted).`,
1117
+ message: external
1118
+ ? `Shared plane: an EXTERNAL-PUBLIC deployment (operator-attested owner ${externalPlaneOwner}, authorized) used DIRECTLY as the shared plane — NO getHost, clone, subject sandbox, or seed. The harness OBSERVES that each seat reached the operator-declared origin (publicOriginDigest); it did NOT mint or control the plane. Author-trust ownership attestation, NOT a synthetic-data claim.`
1119
+ : dryRun
1120
+ ? `Shared plane declared: ${dryRunPlaneLabel}, served + getHost-exposed in-sandbox (dry-run contract; nothing ${args.subject.source === "local-tree" ? "packed" : "cloned"}). Seed recipe ${args.seedDigest}; SYNTHETIC subject (author-attested); env names: ${args.subject.envNames?.join(", ") || "none"} (values never persisted).`
1121
+ : `Shared plane: ${livePlaneLabel}, served + exposed at the harness-minted getHost URL; seed recipe ${args.seedDigest}; SYNTHETIC subject (author-attested); env names: ${args.subject.envNames?.join(", ") || "none"} (values never persisted).`,
771
1122
  simId: actorSpecs[0]?.simId ?? "sim-001",
772
1123
  streamId: actorSpecs[0]?.streamId ?? "stream-001"
773
1124
  });
@@ -780,7 +1131,8 @@ export function buildConcurrentSharedWorldBundle(args) {
780
1131
  const session = outcome?.session;
781
1132
  const screenshots = outcome?.screenshots ?? [];
782
1133
  const lastScreenshot = screenshots[screenshots.length - 1];
783
- const route = publicSafeRouteLabel(roles[index]?.entry); // public-safe (host redacted)
1134
+ // public-safe (origin redacted): external-public seats open the public plane; getHost seats a seat path.
1135
+ const route = external ? "[external-public-plane]" : publicSafeRouteLabel(roles[index]?.entry);
784
1136
  const status = session
785
1137
  ? session.status
786
1138
  : outcome?.sessionError
@@ -921,9 +1273,12 @@ export function buildConcurrentSharedWorldBundle(args) {
921
1273
  });
922
1274
  }
923
1275
  });
924
- // Build the concurrent shared-world evidence block. routeHostDigest is sha256-16 of the ORIGIN of
925
- // the getHost seat URL each actor drove (publish-safe; verify confirms it == plane.hostDigest).
926
- const fallbackHostDigest = args.hostDigest ?? commandDigestOf("[provisioned-subject]");
1276
+ // Build the concurrent shared-world evidence block. routeHostDigest is sha256-16 of the ORIGIN each
1277
+ // seat reached: on getHost the seat URL the actor drove (verify confirms == plane.hostDigest); on
1278
+ // external-public the seat's CDP-OBSERVED URL origin (verify confirms == plane.publicOriginDigest).
1279
+ const fallbackHostDigest = external
1280
+ ? (args.publicOriginDigest ?? commandDigestOf("[external-public-plane]"))
1281
+ : (args.hostDigest ?? commandDigestOf("[provisioned-subject]"));
927
1282
  const laneWindows = actorSpecs.map((spec, index) => {
928
1283
  const result = actorResults[index];
929
1284
  const session = result?.outcome.session;
@@ -943,9 +1298,14 @@ export function buildConcurrentSharedWorldBundle(args) {
943
1298
  seedDigest: args.seedDigest
944
1299
  };
945
1300
  });
946
- const stateSeries = dryRun
947
- ? [{ timestamp: 0, digest: declaredStateDigest(config) }]
948
- : [...args.stateSnapshots].sort((a, b) => a.timestamp - b.timestamp);
1301
+ // Option A (external-public): NO authoritative shared-state proof — OMIT stateSeries entirely (there
1302
+ // is no in-sandbox filesystem to digest; concurrency is proven by temporal co-occupancy + lobby
1303
+ // convergence). The provisioned-getHost plane keeps its authoritative in-sandbox checkpoint series.
1304
+ const stateSeries = external
1305
+ ? undefined
1306
+ : dryRun
1307
+ ? [{ timestamp: 0, digest: declaredStateDigest(config) }]
1308
+ : [...args.stateSnapshots].sort((a, b) => a.timestamp - b.timestamp);
949
1309
  const outcomes = actorSpecs.map((spec, index) => {
950
1310
  const result = actorResults[index];
951
1311
  const session = result?.outcome.session;
@@ -962,31 +1322,54 @@ export function buildConcurrentSharedWorldBundle(args) {
962
1322
  ok
963
1323
  };
964
1324
  });
965
- const sharedWorld = {
966
- schema: SHARED_WORLD_SCHEMA,
967
- topology: "shared-world",
968
- topologyMode: "concurrent",
969
- roleCount: actorSpecs.length,
970
- plane: {
1325
+ // The plane block is plane-class-specific. getHost: harness-minted hostDigest + synthetic
1326
+ // attestation. external-public: operator-declared publicOriginDigest, NO hostDigest, NO exposure
1327
+ // (claiming synthetic on a real site would be a lie — verify asserts both ABSENT there).
1328
+ const plane = external
1329
+ ? {
1330
+ seedDigest: args.seedDigest,
1331
+ envNames: [],
1332
+ // publicOriginDigest is the OBSERVED convergence origin; declaredOriginDigest records the
1333
+ // operator-declared origin for reference (a redirect makes them differ — not a failure).
1334
+ ...(args.publicOriginDigest === undefined ? {} : { publicOriginDigest: args.publicOriginDigest }),
1335
+ ...(args.declaredOriginDigest === undefined ? {} : { declaredOriginDigest: args.declaredOriginDigest })
1336
+ }
1337
+ : {
971
1338
  ...(planeCommit === undefined ? {} : { commit: planeCommit }),
972
1339
  seedDigest: args.seedDigest,
973
1340
  envNames: args.subject.envNames ?? [],
974
1341
  ...(args.hostDigest === undefined ? {} : { hostDigest: args.hostDigest }),
975
1342
  exposure: "synthetic"
976
- },
977
- attributionLimits: [...CONCURRENT_ATTRIBUTION_LIMITS],
1343
+ };
1344
+ const sharedWorld = {
1345
+ schema: SHARED_WORLD_SCHEMA,
1346
+ topology: "shared-world",
1347
+ topologyMode: "concurrent",
1348
+ // Byte-stable: the provisioned-getHost plane omits planeClass (absent == provisioned-getHost).
1349
+ ...(external ? { planeClass: "external-public" } : {}),
1350
+ roleCount: actorSpecs.length,
1351
+ plane,
1352
+ attributionLimits: external ? [...EXTERNAL_PUBLIC_ATTRIBUTION_LIMITS] : [...CONCURRENT_ATTRIBUTION_LIMITS],
978
1353
  laneWindows,
979
- stateSeries,
980
- outcomes
1354
+ // Option A: external-public carries NO stateSeries.
1355
+ ...(stateSeries === undefined ? {} : { stateSeries }),
1356
+ outcomes,
1357
+ ...(args.lobbyConvergenceDigest === undefined ? {} : { lobbyConvergenceDigest: args.lobbyConvergenceDigest })
981
1358
  };
982
1359
  const overlaps = actorWindowsOverlap(actorResults);
983
- const deltas = stateSeries.filter((snapshot, i) => i > 0 && snapshot.digest !== stateSeries[i - 1].digest).length;
1360
+ const deltas = (stateSeries ?? []).filter((snapshot, i) => i > 0 && snapshot.digest !== (stateSeries ?? [])[i - 1].digest).length;
1361
+ const stateSeriesLabel = external
1362
+ ? "stateSeries omitted (no authoritative shared-state proof on the external-public plane)"
1363
+ : `stateSeries ${(stateSeries ?? []).length} snapshot(s), ${deltas} delta(s)`;
1364
+ const convergenceLabel = external
1365
+ ? `; lobby convergence ${args.lobbyConvergenceDigest ? "PROVEN (all seats reached one /lobby/CODE)" : "not observed"}`
1366
+ : "";
984
1367
  events.push({
985
1368
  id: nextEventId("concurrency"),
986
1369
  at: createdAt,
987
1370
  level: "info",
988
1371
  type: "concurrent-shared-world.concurrency",
989
- message: `Concurrency: ${laneWindows.length} actor window(s)${dryRun ? " (dry-run contract; $0)" : `, overlap ${overlaps ? "PROVEN" : "not observed"}`}; stateSeries ${stateSeries.length} snapshot(s), ${deltas} delta(s). Attribution ceiling: ${sharedWorld.attributionLimits.join(", ")}. ${dryRun ? "This contract-only run proves no live concurrency, scale, or adoption." : "This run reports only its own observed overlap and state changes; it does not prove scale, repeatability, or adopter-harness replacement."}`
1372
+ message: `Concurrency: ${laneWindows.length} actor window(s)${dryRun ? " (dry-run contract; $0)" : `, overlap ${overlaps ? "PROVEN" : "not observed"}`}; ${stateSeriesLabel}${convergenceLabel}. Attribution ceiling: ${sharedWorld.attributionLimits.join(", ")}. ${dryRun ? "This contract-only run proves no live concurrency, scale, or adoption." : "This run reports only its own observed overlap and state changes; it does not prove scale, repeatability, or adopter-harness replacement."}`
990
1373
  });
991
1374
  // Concurrent verdict: dryRun → contract; else every actor produced a terminal, engaged PASSED
992
1375
  // session → pass; otherwise fail. Per-persona mission success is the M-of-N in outcomes[].
@@ -1002,11 +1385,18 @@ export function buildConcurrentSharedWorldBundle(args) {
1002
1385
  const review = {
1003
1386
  schema: REVIEW_SCHEMA,
1004
1387
  verdict,
1388
+ // Plane-class-aware: the external-public plane has NO getHost/clone/seed and carries NO
1389
+ // authoritative state series, so its summary must not claim a getHost-exposed plane (dry-run) nor
1390
+ // report "state delta(s) under load" (live) — it reports lobby convergence instead.
1005
1391
  summary: dryRun
1006
- ? `Dry-run concurrent shared-world contract: ${actorSpecs.length} persona(s) declared against ONE getHost-exposed plane (${descriptor.id}); no sandboxes launched, $0 spend.`
1392
+ ? external
1393
+ ? `Dry-run concurrent shared-world contract: ${actorSpecs.length} persona(s) declared against ONE external-public shared plane (a real public deployment used directly; no getHost/clone/seed); no sandboxes launched, $0 spend.`
1394
+ : `Dry-run concurrent shared-world contract: ${actorSpecs.length} persona(s) declared against ONE getHost-exposed plane (${descriptor.id}); no sandboxes launched, $0 spend.`
1007
1395
  : inProgress
1008
1396
  ? `In-progress concurrent shared-world Observer snapshot: ${actorSpecs.length} persona(s) running against ONE shared plane; final verification is pending.`
1009
- : `Concurrent shared-world (ONE plane, ${actorSpecs.length} simultaneous personas): swarm ${verdict === "pass" ? "ran coherently" : "did not run coherently"}; ${passedMissions}/${actorSpecs.length} reached their goal; overlap ${overlaps ? "proven" : "not observed"}; ${deltas} state delta(s) under load.`,
1397
+ : external
1398
+ ? `Concurrent shared-world (ONE external-public plane, ${actorSpecs.length} simultaneous personas): swarm ${verdict === "pass" ? "ran coherently" : "did not run coherently"}; ${passedMissions}/${actorSpecs.length} reached their goal; overlap ${overlaps ? "proven" : "not observed"}; ${args.lobbyConvergenceDigest ? `${actorSpecs.length} seats converged on one lobby` : "lobby convergence not observed"}.`
1399
+ : `Concurrent shared-world (ONE plane, ${actorSpecs.length} simultaneous personas): swarm ${verdict === "pass" ? "ran coherently" : "did not run coherently"}; ${passedMissions}/${actorSpecs.length} reached their goal; overlap ${overlaps ? "proven" : "not observed"}; ${deltas} state delta(s) under load.`,
1010
1400
  gaps: dryRun
1011
1401
  ? ["This dry-run launched no concurrent shared-world session; it proves contract shape only, not live behavior, scale, or adopter-harness replacement."]
1012
1402
  : inProgress