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.
- package/dist/computer-use-actor.d.ts +3 -0
- package/dist/computer-use-actor.js +2 -1
- package/dist/computer-use-actor.js.map +1 -1
- package/dist/computer-use.d.ts +9 -0
- package/dist/computer-use.js +5 -1
- package/dist/computer-use.js.map +1 -1
- package/dist/concurrent-shared-world-lab.d.ts +23 -1
- package/dist/concurrent-shared-world-lab.js +431 -41
- package/dist/concurrent-shared-world-lab.js.map +1 -1
- package/dist/cua-actor-lab.d.ts +7 -0
- package/dist/cua-actor-lab.js +4 -1
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/init-templates.js +52 -0
- package/dist/init-templates.js.map +1 -1
- package/dist/lab-config.d.ts +54 -3
- package/dist/lab-config.js +162 -9
- package/dist/lab-config.js.map +1 -1
- package/dist/run.d.ts +46 -2
- package/dist/run.js +226 -1
- package/dist/run.js.map +1 -1
- package/dist/shared-world-lab.d.ts +8 -0
- package/dist/shared-world-lab.js.map +1 -1
- package/docs/architecture/external-public-shared-world.md +117 -0
- package/docs/contracts/schemas.md +64 -5
- package/docs/goals/current.md +1 -1
- package/docs/principles/invariants-and-defaults.md +16 -0
- package/docs/product/cineguessr-3player-external-public.md +67 -0
- package/docs/ramp/README.md +1 -1
- package/docs/release/0.20.0-external-public-shared-world.md +43 -0
- package/package.json +1 -1
|
@@ -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
|
-
//
|
|
257
|
-
|
|
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
|
-
|
|
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
|
|
742
|
-
//
|
|
743
|
-
|
|
744
|
-
const
|
|
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:
|
|
769
|
-
? `Shared plane
|
|
770
|
-
:
|
|
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
|
-
|
|
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
|
|
925
|
-
//
|
|
926
|
-
|
|
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
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"}`};
|
|
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
|
-
?
|
|
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
|
-
:
|
|
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
|