browser-broker 0.1.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.
Files changed (123) hide show
  1. package/.env.example +173 -0
  2. package/LICENSE +21 -0
  3. package/README.md +374 -0
  4. package/RELEASES.md +97 -0
  5. package/dist/package.json +58 -0
  6. package/dist/src/adapter/conformance/case.js +1 -0
  7. package/dist/src/adapter/conformance/cases.js +429 -0
  8. package/dist/src/adapter/conformance/discovery.js +156 -0
  9. package/dist/src/adapter/conformance/driver.js +1 -0
  10. package/dist/src/adapter/conformance/drivers.js +36 -0
  11. package/dist/src/adapter/conformance/run.js +224 -0
  12. package/dist/src/adapter/conformance/service-subject.js +165 -0
  13. package/dist/src/adapter/contract.js +24 -0
  14. package/dist/src/adapter/operations.js +114 -0
  15. package/dist/src/adapter/service-seam.js +1 -0
  16. package/dist/src/artifacts/names.js +229 -0
  17. package/dist/src/artifacts/store.js +174 -0
  18. package/dist/src/bin/broker-tool.js +63 -0
  19. package/dist/src/bin/broker.js +111 -0
  20. package/dist/src/browser/adoption.js +143 -0
  21. package/dist/src/browser/automation-probe.js +113 -0
  22. package/dist/src/browser/conformance/case.js +1 -0
  23. package/dist/src/browser/conformance/cases.js +192 -0
  24. package/dist/src/browser/conformance/run.js +102 -0
  25. package/dist/src/browser/conformance/subjects.js +19 -0
  26. package/dist/src/browser/discovery.js +226 -0
  27. package/dist/src/browser/driver.js +195 -0
  28. package/dist/src/browser/fake.js +585 -0
  29. package/dist/src/browser/launch.js +504 -0
  30. package/dist/src/browser/real.js +1425 -0
  31. package/dist/src/browser/setup.js +161 -0
  32. package/dist/src/capture/accounting.js +59 -0
  33. package/dist/src/capture/image.js +112 -0
  34. package/dist/src/capture/ladder.js +72 -0
  35. package/dist/src/capture/legibility.js +195 -0
  36. package/dist/src/capture/pipeline.js +153 -0
  37. package/dist/src/capture/tiers.js +166 -0
  38. package/dist/src/cli/adapter.js +233 -0
  39. package/dist/src/cli/commands.js +270 -0
  40. package/dist/src/cli/conformance-driver.js +119 -0
  41. package/dist/src/cli/diffs.js +122 -0
  42. package/dist/src/cli/image.js +274 -0
  43. package/dist/src/cli/index.js +895 -0
  44. package/dist/src/cli/login-command.js +401 -0
  45. package/dist/src/cli/operations-commands.js +186 -0
  46. package/dist/src/cli/reconcile-command.js +137 -0
  47. package/dist/src/cli/sign-in.js +134 -0
  48. package/dist/src/cli/telemetry.js +222 -0
  49. package/dist/src/config/environment.js +446 -0
  50. package/dist/src/diff/artifact-path.js +77 -0
  51. package/dist/src/diff/crops.js +102 -0
  52. package/dist/src/diff/geometry.js +122 -0
  53. package/dist/src/diff/image.js +132 -0
  54. package/dist/src/diff/mask.js +46 -0
  55. package/dist/src/diff/regions.js +263 -0
  56. package/dist/src/diff/settings.js +135 -0
  57. package/dist/src/doctor/checks.js +588 -0
  58. package/dist/src/doctor/report.js +152 -0
  59. package/dist/src/doctor/session.js +161 -0
  60. package/dist/src/errors.js +36 -0
  61. package/dist/src/feedback/read.js +119 -0
  62. package/dist/src/feedback/record.js +199 -0
  63. package/dist/src/operations/addresses.js +175 -0
  64. package/dist/src/operations/derive.js +109 -0
  65. package/dist/src/operations/ledger.js +194 -0
  66. package/dist/src/operations/status.js +197 -0
  67. package/dist/src/operations/telemetry.js +280 -0
  68. package/dist/src/report/document.js +419 -0
  69. package/dist/src/report/escape.js +68 -0
  70. package/dist/src/report/snapshot.js +97 -0
  71. package/dist/src/service/arbitration.js +537 -0
  72. package/dist/src/service/artifacts.js +85 -0
  73. package/dist/src/service/bridge.js +577 -0
  74. package/dist/src/service/broker.js +120 -0
  75. package/dist/src/service/browser-session.js +269 -0
  76. package/dist/src/service/capacity.js +62 -0
  77. package/dist/src/service/capture-seam.js +83 -0
  78. package/dist/src/service/capture-store.js +91 -0
  79. package/dist/src/service/comparison-store.js +101 -0
  80. package/dist/src/service/comparison.js +173 -0
  81. package/dist/src/service/events.js +93 -0
  82. package/dist/src/service/keys.js +68 -0
  83. package/dist/src/service/leases.js +147 -0
  84. package/dist/src/service/nudge.js +66 -0
  85. package/dist/src/service/operations/claim.js +692 -0
  86. package/dist/src/service/operations/give-back.js +131 -0
  87. package/dist/src/service/operations/pages.js +771 -0
  88. package/dist/src/service/operations/sign-in.js +915 -0
  89. package/dist/src/service/operations/status.js +62 -0
  90. package/dist/src/service/ownership.js +93 -0
  91. package/dist/src/service/pages.js +616 -0
  92. package/dist/src/service/pending-seeds.js +20 -0
  93. package/dist/src/service/queue.js +233 -0
  94. package/dist/src/service/reconcile.js +220 -0
  95. package/dist/src/service/refusals.js +262 -0
  96. package/dist/src/service/runtime.js +131 -0
  97. package/dist/src/service/signin-recovery.js +148 -0
  98. package/dist/src/service/storage-seed.js +239 -0
  99. package/dist/src/service/tabs.js +123 -0
  100. package/dist/src/store/budget.js +99 -0
  101. package/dist/src/store/location.js +42 -0
  102. package/dist/src/store/network-path.js +182 -0
  103. package/dist/src/store/network-volume.js +92 -0
  104. package/dist/src/store/open.js +226 -0
  105. package/dist/src/store/schema/step-001-initial.js +523 -0
  106. package/dist/src/store/schema/step-002-tab-budget.js +53 -0
  107. package/dist/src/store/schema/step-003-queue-order.js +110 -0
  108. package/dist/src/store/schema/step-004-tab-never-opened.js +100 -0
  109. package/dist/src/store/schema/step-005-storage-seed-event.js +90 -0
  110. package/dist/src/store/schema/step-006-signin-events.js +104 -0
  111. package/dist/src/store/schema/step-007-signin-without-process.js +92 -0
  112. package/dist/src/store/schema/step-008-signin-owner.js +76 -0
  113. package/dist/src/store/schema/step-009-named-browsers.js +138 -0
  114. package/dist/src/store/schema/step-010-signin-request.js +135 -0
  115. package/dist/src/store/schema/step.js +172 -0
  116. package/dist/src/store/schema/steps.js +58 -0
  117. package/dist/src/store/transaction.js +37 -0
  118. package/dist/src/tool/adapter.js +90 -0
  119. package/dist/src/tool/conformance-driver.js +184 -0
  120. package/dist/src/tool/protocol.js +310 -0
  121. package/dist/src/tool/session.js +351 -0
  122. package/dist/src/tool/tools.js +310 -0
  123. package/package.json +58 -0
@@ -0,0 +1,62 @@
1
+ import { extendLease, resolveLease } from "../leases.js";
2
+ import { queueDepth, queuePosition, waitEstimateSeconds } from "../queue.js";
3
+ import { checkBackSeconds } from "./claim.js";
4
+ /**
5
+ * **This one takes no settings, and the absence is deliberate.** Every
6
+ * duration it reports comes off the lease's own row (§1.3): a renewal has to
7
+ * extend by the duration the caller was told, so re-reading the environment
8
+ * mid-lease would silently change a promise the caller has already acted on.
9
+ * Taking a settings argument it did not use would invite exactly that.
10
+ */
11
+ export function decideStatus(scope, input) {
12
+ const { db, adapter, swept } = scope;
13
+ const lease = resolveLease(db, input.key, {
14
+ adapter,
15
+ kind: 'claim_renewed',
16
+ recordRefusal: scope.recordRefusal,
17
+ });
18
+ // Row #14: the extension is the effect of the call, not a verb of its own.
19
+ const expiresAt = extendLease(db, lease, { adapter, now: swept.sweptAt });
20
+ const checkBack = checkBackSeconds(lease.ttlSeconds);
21
+ const advice = `Call in with this key at least every ${String(checkBack)} seconds. Any call carrying it extends the lease; there is no separate renew.` +
22
+ (lease.state === 'queued'
23
+ ? ` This place lives ${String(lease.ttlSeconds)} seconds, so check back at ${String(checkBack)} rather than at the deadline — a check made exactly at the deadline races the reclamation and loses about half the time.`
24
+ : '');
25
+ if (lease.state === 'queued') {
26
+ const position = queuePosition(db, lease.claimId);
27
+ const estimate = waitEstimateSeconds(db, position);
28
+ return {
29
+ value: {
30
+ claimId: lease.claimId,
31
+ state: 'queued',
32
+ browserId: lease.browserId,
33
+ purpose: lease.purpose,
34
+ expiresAt,
35
+ ttlSeconds: lease.ttlSeconds,
36
+ checkBackSeconds: checkBack,
37
+ checkBack: advice,
38
+ position,
39
+ queueDepth: queueDepth(db),
40
+ ...(estimate === undefined ? {} : { waitEstimateSeconds: estimate }),
41
+ },
42
+ };
43
+ }
44
+ const tab = db
45
+ .prepare(`SELECT id AS tabId FROM tabs
46
+ WHERE claim_id = @claimId AND state IN ('opening', 'open')
47
+ ORDER BY id LIMIT 1`)
48
+ .get({ claimId: lease.claimId });
49
+ return {
50
+ value: {
51
+ claimId: lease.claimId,
52
+ state: 'active',
53
+ browserId: lease.browserId,
54
+ purpose: lease.purpose,
55
+ expiresAt,
56
+ ttlSeconds: lease.ttlSeconds,
57
+ checkBackSeconds: checkBack,
58
+ checkBack: advice,
59
+ ...(tab === undefined ? {} : { tabId: tab.tabId }),
60
+ },
61
+ };
62
+ }
@@ -0,0 +1,93 @@
1
+ import { CallRefusal } from "./refusals.js";
2
+ /**
3
+ * `tab.owned` and `tab.open` (§7.1, `MILESTONES.md` #18) — every tab-addressed
4
+ * operation refuses a tab not owned by the key.
5
+ *
6
+ * ── One code for two rules, and the sharing is the point ────────────────
7
+ *
8
+ * §7.1 says it outright: an unowned tab gets **"the same refusal as an
9
+ * unknown tab, so probing cannot discover another lease's tabs"**. Two rules,
10
+ * two ledger rows, one code — **a caller able to tell them apart is a caller
11
+ * able to enumerate tabs it does not own.** So the ledger records which rule
12
+ * fired, because that question is asked by whoever is reading the record, and
13
+ * the caller is told the same sentence either way.
14
+ *
15
+ * ── Why this is a lookup and not a comparison the caller can influence ──
16
+ *
17
+ * §1.4: `claim_id` is **the ownership fact**, set once, never null, never
18
+ * changed. There is no state in which a tab has two owners, so the check is a
19
+ * single equality against a column the caller cannot write — not a rule
20
+ * maintaining an invariant, but a read of one the schema already keeps.
21
+ *
22
+ * **The tab identifier is not the security boundary** and is not treated as
23
+ * one: it is opaque so that holding one tells you nothing about any other,
24
+ * and ownership is checked against the lease key on every call regardless.
25
+ */
26
+ /**
27
+ * Resolve a tab the caller named, **refusing** anything the lease does not own.
28
+ *
29
+ * **Both refusals produce the same sentence and the same code**, and the
30
+ * sentence deliberately says nothing about whether the tab exists. A message
31
+ * distinguishing *"no such tab"* from *"not yours"* is an oracle: a caller
32
+ * could walk identifiers and learn which ones are real.
33
+ *
34
+ * ── This is the only tab resolver, and the name carries a warning ───────
35
+ *
36
+ * It resolves a tab in **any** state, throws a {@link CallRefusal} and writes
37
+ * a ledger row. The name says `OrRefuse` because that is the whole contract.
38
+ *
39
+ * A sibling that resolved only tabs in state `open` and answered `undefined`
40
+ * instead of throwing is a tempting thing to add, and it is a trap worth
41
+ * naming here so it is not rediscovered by experiment. **Under lazy opening a
42
+ * resolver requiring `open` returns nothing on every first call**, because
43
+ * the tab has not been opened yet — so a caller reaching for it to authorise
44
+ * an operation finds that no tab can ever be driven at all. If a call site
45
+ * seems to want that behaviour, the call site is the thing to look hard at.
46
+ */
47
+ export function resolveOwnedTabOrRefuse(db, lease, tabId, options) {
48
+ const row = db
49
+ .prepare(`SELECT id AS tabId, claim_id AS claimId, browser_id AS browserId,
50
+ driver_tab_id AS driverTabId, state
51
+ FROM tabs
52
+ WHERE id = @tabId`)
53
+ .get({ tabId });
54
+ // Unknown and unowned take the same branch on purpose: the guard recorded
55
+ // differs, the answer does not.
56
+ if (row === undefined || row.claimId !== lease.claimId) {
57
+ options.recordRefusal({
58
+ kind: options.kind,
59
+ outcome: 'deny',
60
+ guard: 'tab.owned',
61
+ adapter: options.adapter,
62
+ claimId: lease.claimId,
63
+ sessionId: lease.sessionId,
64
+ browserId: lease.browserId,
65
+ detail: { requested: tabId, known: row !== undefined },
66
+ });
67
+ throw new CallRefusal('tab_not_found', notFoundSentence(tabId));
68
+ }
69
+ if (row.state !== 'opening' && row.state !== 'open') {
70
+ options.recordRefusal({
71
+ kind: options.kind,
72
+ outcome: 'deny',
73
+ guard: 'tab.open',
74
+ adapter: options.adapter,
75
+ claimId: lease.claimId,
76
+ tabId: row.tabId,
77
+ sessionId: lease.sessionId,
78
+ browserId: row.browserId,
79
+ detail: { requested: tabId, state: row.state },
80
+ });
81
+ throw new CallRefusal('tab_not_found', notFoundSentence(tabId));
82
+ }
83
+ return { tabId: row.tabId, browserId: row.browserId, driverTabId: row.driverTabId };
84
+ }
85
+ /**
86
+ * The one sentence both refusals use.
87
+ *
88
+ * Written once so the two cannot drift apart. If they ever differ by a word,
89
+ * the difference is the oracle this rule exists to close.
90
+ */
91
+ function notFoundSentence(tabId) {
92
+ return `This lease has no open tab ${JSON.stringify(tabId)}. A tab belongs to exactly one lease and is only addressable by the key that holds it.`;
93
+ }